From 5ffdf05a5a82e3914636a53a526bb614a38aed2b Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Wed, 2 Mar 2022 11:33:34 +0100 Subject: [PATCH 01/42] Remove build dir from repo --- .gitignore | 3 +- build/panolens.js | 9595 -------------------------------------- build/panolens.min.js | 202 - build/panolens.module.js | 9563 ------------------------------------- 4 files changed, 2 insertions(+), 19361 deletions(-) delete mode 100644 build/panolens.js delete mode 100644 build/panolens.min.js delete mode 100644 build/panolens.module.js diff --git a/.gitignore b/.gitignore index 590a7c84..b619fbff 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ bower_components ./**.DS_Store coverage/ .nyc_output -.vscode/ \ No newline at end of file +.vscode/ +build/ diff --git a/build/panolens.js b/build/panolens.js deleted file mode 100644 index a47d060c..00000000 --- a/build/panolens.js +++ /dev/null @@ -1,9595 +0,0 @@ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('three')) : - typeof define === 'function' && define.amd ? define(['exports', 'three'], factory) : - (global = global || self, factory(global.PANOLENS = {}, global.THREE)); -}(this, function (exports, THREE) { 'use strict'; - - const version="0.12.1";const dependencies={three:"^0.105.2"}; - - /** - * REVISION - * @module REVISION - * @example PANOLENS.REVISION - * @type {string} revision - */ - const REVISION = version.split( '.' )[ 1 ]; - - /** - * VERSION - * @module VERSION - * @example PANOLENS.VERSION - * @type {string} version - */ - const VERSION = version; - - /** - * THREEJS REVISION - * @module THREE_REVISION - * @example PANOLENS.THREE_REVISION - * @type {string} threejs revision - */ - const THREE_REVISION = dependencies.three.split( '.' )[ 1 ]; - - /** - * THREEJS VERSION - * @module THREE_VERSION - * @example PANOLENS.THREE_VERSION - * @type {string} threejs version - */ - const THREE_VERSION = dependencies.three.replace( /[^0-9.]/g, '' ); - - /** - * CONTROLS - * @module CONTROLS - * @example PANOLENS.CONTROLS.ORBIT - * @property {number} ORBIT 0 - * @property {number} DEVICEORIENTATION 1 - */ - const CONTROLS = { ORBIT: 0, DEVICEORIENTATION: 1 }; - - /** - * MODES - * @module MODES - * @example PANOLENS.MODES.UNKNOWN - * @property {number} UNKNOWN 0 - * @property {number} NORMAL 1 - * @property {number} CARDBOARD 2 - * @property {number} STEREO 3 - */ - const MODES = { UNKNOWN: 0, NORMAL: 1, CARDBOARD: 2, STEREO: 3 }; - - /** - * Data URI Images - * @module DataImage - * @example PANOLENS.DataImage.Info - * @property {string} Info Information Icon - * @property {string} Arrow Arrow Icon - * @property {string} FullscreenEnter Fullscreen Enter Icon - * @property {string} FullscreenLeave Fullscreen Leave Icon - * @property {string} VideoPlay Video Play Icon - * @property {string} VideoPause Video Pause Icon - * @property {string} WhiteTile White Tile Icon - * @property {string} Setting Settings Icon - * @property {string} ChevronRight Chevron Right Icon - * @property {string} Check Check Icon - * @property {string} ViewIndicator View Indicator Icon - */ - const DataImage = { - Info: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAABAAAAAQADq8/hgAAADBklEQVR42u2bP08UQRiHnzFaSYCI/xoksdBIqGwIiYWRUBISExpCQ0ej38FWOmlIKKhoMPEbaCxsrrHiYrQgOSlQEaICrT+LHSPZzNzt3s3c3Hn7lHvLzvv82L2dm30XKioqKgYY062BJF0HpoA7wARwBbhsPz4DjoEG8AnYNcZ8Sx1Op8IXJM1KWpdUV3nq9m9nJV1I7VNGfEzSM0mNNqR9NOwxx1L7NRMflbQm6SSgeJ4TO8Zoat+8/LKkg4jieQ4kLaf2RtKwpJ0uiufZkTScSn5S0l5C+b/sSZrstvyMpKPU5uc4kjTTjkvpeYCkaeA1/+7hvcIZMGuMqUULQNIU8Aa4ltrWwyHwyBizGzwASSPAe+B2assW7AH3jTE/i+xcZoa12Qfy2Bo3i+5cKABl99zF1GYlWFTBeULLS0DZrOsDcDNggTXgc27bLWA64BhfgHvGmB8dHUXZ1DM0S45xliKMs9bKr+klIOkqsBrwv9JtVq1DewEAT4Ch1BYdMGQdygeg7Df4SmqDAKyoyXpCszPgITCeuvoAjFuX0gE8jljUdv7bCtiOOJ7XpdUZ8L/gdXHOA5QtYH5NXXVgbrgWWn1nwFTqaiPgdPIFcDd1tRFwOl307DwRuZgXwLvctgfA04hjOp18AcReZ6sZY16e3yDpUuQxnU6+S2AkcjEpcDr1zxOXSPgCKLSa0mc4nXwB/EpdbQScTr4AGqmrjYDTyRfAx9TVRsDp5Aug8LJyH+F0cgZg58z11BUHpO5ruGh2G3ybuuqAeF2aBfAqddUB8bq0OgP2U1cegH3aOQOMMb+BrdTVB2DLupQLwLIOnKY26IBT6+ClaQDGmO/ARmqLDtiwDn7HVkcY+EdjNoTlCI+tYhO2iUppm6HKslPUq2qQKHpUe8AFsjaUXuUQWCgqXyoAG8IuME/WkNRrnAHzZfqDSgdgQ6gBc2Td3b3CMTBXtkOsIzTIjZLnQhjcVtlcEIPZLJ0LoVvt8s/Va+3yuSAG84UJRxB98cpM9dJURUVFxSDzBxKde4Lk3/h2AAAAAElFTkSuQmCC', - Arrow: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAABAAAAAQADq8/hgAAADPklEQVR42u2bMUscQRiG30/SRaJEI1ZKUiRErNIELRUbQYSAnX8hpVUgkDYp0wgWVjYW+QcJaQzYpLojJIXhtDDEKBpj65ti58ixmdmb2ZvZ7+T2AUHudmfmeXf2bnb3O6CmpqZmgJGqOiI5AWAWwEMA0wDuArht3r4CcAagBeAbgIaI/NQOp1fhIZKLJN+SbDKcptl3keSQtk+I+BjJVyRbJaRdtEybY9p+ReKjJN+QvIwonufS9DGq7ZuXXyd5nFA8zzHJdW1vkLxDcrdC8Ty7JO9oyc+QPFCUb3NAcqZq+TmSp9rmHZySnCvjErwOIPkUwHv8+w7vF64ALIrIfrIASM4C+ADgnratgxMACyLSiB4AyREAnwE80LbswgGAJyJy4bNxyApr6wbIw4xxy3djrwCYfeeuaZsFsEbPdULXU4DZqusLgMkEA21P05EEbf8A8FhEzos28pkBLxLKL5s/r/M1kEkz9vKQHGeatf05yfmOfubNa7G5JDle5NhtBjwHMBz5yFwAWBaRT+0XzP8pZsKwcQiH2fX8Ycojb+kzxUw4ZJn7CSQXqpRPHMKCq7+iZJ71Mvdy/DftXSQ6HcJdSDaqPPKW/mPOBO+lcbvzCU35RCFM2PpwnQKzZQfdgfe0dxH5dLA6uQJ4pC2fIASrkyuA6X6QjxyC1ckVQNn7bNHlI4ZgdXIFUObiJJl8pBCsTjGfuIwA2Cv4FN7xbYjkjqsRAHuIePXoCiDF1Zk2VidXAL+1R5sAq5MrgJb2aBNgdXIF8FV7tAmwOrkCCFs73wysTtYATHFCU3vEEWm6Ci6KvgY/ao86Ik6XogDeaY86Ik6XbjPgSHvkEThCwQy45XpDRK5JbgN4GWkgUyR9H65MRQxgW0SunZ5FezK7pfwd8e8MV8UfAPdF5Jdrg8JrAbPjprZFD2wWyQP6j8ZSEufRmGlgQ9umBBvd5IOgbjFUKLu+XnWBhG+rpsFVZGUo/coJgFVf+aAATAgNACvICpL6jSsAKyH1QcEBmBD2ASwhq+7uF84ALIVWiPUEB7lQsiOEwS2VzQUxmMXSuRCqKpd/zX4rl88FMZg/mLAEcSN+MlP/aKqmpqZmkPkL0hSjwOpNKxwAAAAASUVORK5CYII=', - FullscreenEnter: 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4KICAgIDxwYXRoIGQ9Ik03IDE0SDV2NWg1di0ySDd2LTN6bS0yLTRoMlY3aDNWNUg1djV6bTEyIDdoLTN2Mmg1di01aC0ydjN6TTE0IDV2MmgzdjNoMlY1aC01eiIvPgo8L3N2Zz4=', - FullscreenLeave: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggc3R5bGU9ImZpbGw6I2ZmZiIgZD0iTTE0LDE0SDE5VjE2SDE2VjE5SDE0VjE0TTUsMTRIMTBWMTlIOFYxNkg1VjE0TTgsNUgxMFYxMEg1VjhIOFY1TTE5LDhWMTBIMTRWNUgxNlY4SDE5WiIgLz48L3N2Zz4=', - VideoPlay: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggc3R5bGU9ImZpbGw6I2ZmZiIgZD0iTTgsNS4xNFYxOS4xNEwxOSwxMi4xNEw4LDUuMTRaIiAvPjwvc3ZnPg==', - VideoPause: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggc3R5bGU9ImZpbGw6I2ZmZiIgZD0iTTE0LDE5LjE0SDE4VjUuMTRIMTRNNiwxOS4xNEgxMFY1LjE0SDZWMTkuMTRaIiAvPjwvc3ZnPg==', - WhiteTile: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIABAMAAAAGVsnJAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAB1WlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOkNvbXByZXNzaW9uPjE8L3RpZmY6Q29tcHJlc3Npb24+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlBob3RvbWV0cmljSW50ZXJwcmV0YXRpb24+MjwvdGlmZjpQaG90b21ldHJpY0ludGVycHJldGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KAtiABQAAACRQTFRFAAAAAAAABgYGBwcHHh4eKysrx8fHy8vLzMzM7OzsAAAABgYG+q7SZgAAAAp0Uk5TAP7+/v7+/v7+/iJx/a8AAAOwSURBVHja7d0hbsNAEAVQo6SFI6XEcALDcgNLvUBvEBQVhpkWVYWlhSsVFS7t5QIshRt695lEASZP+8c7a1kzDL1fz+/zyuvzp6FbvoddrL6uDd1yGZ5eXldeb18N3fIx7A+58prmhm65DfvDcd0952lu6JabFbD/zVprZj1lzcys+fj9z8xTZtbT8rv8yWlu6BYAIgAAAAAAAAAAAABAM6QXEAEAAAAAAAAAgJ2gnaAIiIA3Q2qAGgAAAAAAAAAAAAAAAAAAAAAAAAAAQJsADkVFAAAAAAA8Bj0GRUAEREAEREAEREAEREAEAAAAAAAAAAB2gnaCIiACPplRA9QANUAERAAAAEVQERQBERCBVlfAcZ3aeZobusUKMGBhV6KUElHGKBERJR6/fxExRkQZl9/lT8S1oVsuhqyYMmPKjCkzvfcCpsxohrwY0Q06EAEAAAAAAAAAAACgGdILiAAAAAAAAAAAwE7QTlAERMCbITVADQAAAAAAAAAAAAAAAAAAAAAAAAAAwKmwQ1ERAAAAAACPQY9BERABERABERABERABERABAAAAAAAAAICdoJ2gCIiAT2bUADVADRABEQAAQBFUBEVABERgEyvAlJm+V4ApM6bMmDJjyowpM6bMdN0LmDKjGfJiRDfoQAQAAAAAAAAAAACAZkgvIAIAAAAAAAAAADtBO0EREAFvhtQANQAAAAAAAAAAAAAAAAAAAAAAAAAAAKfCDkVFAAAAAAA8Bj0GRUAEREAEREAEREAEREAEAAAAAAAAAAB2gnaCIiACPplRA9QANUAERAAAAEVQERQBERCBTawAU2b6XgGmzJgyY8qMKTOmzJgy03UvYMqMZsiLEd2gAxEAAAAAAAAAAAAAmiG9gAgAAAAAAAAAAOwE7QRFQAS8GVID1AAAAAAAAAAAAAAAAAAAAAAAAAAAAJwKOxQVAQAAAADwGPQYFAEREAEREAEREAEREAERAAAAAAAAAADYCdoJioAI+GRGDVAD1AAREAEAABRBRVAEREAENrECTJnpewWYMmPKjCkzpsyYMmPKTNe9gCkzmiEvRnSDDkQAAAAAAAAAAAAAaIb0AiIAAAAAAAAAALATtBMUARHwZkgNUAMAAAAAAAAAAAAAAAAAAAAAAAAAAHAq7FBUBAAAAADAY9BjUAREQAREQAREQAREQAREAAAAAAAAAABgJ2gnKAIi4JMZNUANUANEQAQAAFAEFUEREAER2MQKMGWm7xVgyowpM50PWen9ugNGXz1XaocAFgAAAABJRU5ErkJggg==', - Setting: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAABAAAAAQADq8/hgAAADn0lEQVR42u2bzUsVURjGnyO6CPzAMnTjppAo3LTwH1CqTfaxbeOiRS37A0wXtROFVi1aRBs3LWohSIGbQAQXViBGRhG0UIRKUCpK7q/FnOB2uc6cOXNmRnGe3eW+H8/7zLln3vNxpQoVKlQ4wjBFJAFOSRqX1O7osivpvjHmU1nChBZglvSYLYJbS0EanCvIJzWK+gnsyH34/8OuMaYjb265jwCgz6N4SWq3vodbAEmnS/KtBDgoAgyU5BteAOAkMAPcBroc7PskDWfgN+wyDwBdltMMcDI3tYBnde/pHeARMNTErgd4APzweP834oeN1dMkz5DlsFNn/yyv4kdiSK4At4AO4CqwGaDwRmza2B0210qM7YhrXU59ANAq6bWkwQTTn5KO5fIE0uVYlXTeGLOXFMx1DrjlULwKKN41x6DlnIjEEQCckPRe0okCiguJr5LOGGO+xhm5jICJQ1i8LOeJJKPYEQAMKvrtt5ZdjSf2FM0Fq/sZJI2A6UNcvCz36TiDfUcAcE1SPu/U6Mm8k/TFfu6XdFb5iX3dGPM8lQfwNod3+TowBnQ3yddtv1vPIe+b1JIBiwEJ1IAJ208k5W21trWA+V/5CHAcmAtU/A2P/DcCiTAHHE8tgCVhgLvAXgYCk17Jo/yTGfLuWe7Zd72AC8CWB4n3OAz7mLytNkZabAEXMhfeQKYfWEpJZCxA3rGUOZeA/qDF15FpAz47EvlNk9neI2e3jeWCz0BbmvipNkSMMX8kuSZYM8Z8zyqAjbHmaN5mOeYjgIXrU93MWrxHrNQjrqiDkQMLHwG+OdqF3NN3jeXKzU8AoF1SzdH8XKhJUO7HZDXLMbwAwICkJUULFxe0SbqSVQAbw3Xi7Ze0ZLmGAzAKbHs0JGU1QtvAaIjCW4B7ZOvJy2qFa5a730RPtBiaz0CgnkiZi6F5fBZDVMvho7EhcuS3xJJ2hV9IupgTqaLw0hhzab8vq23xOG/r+LDsKjLgYVzxUnU0ltwK2wDezUyJmEwqXgp/PL4rvxthaeCSI+zxuA10J8ZkWdJNSb2SLkvayKHwDRu71+ZajrG941J8agALDQ3GU/a/IvMkYCPzmCbtLNEVmacNtgs5iP9fYVNEV1Q6Hez7yNZSL+J2SarTcpqiyV2iUkG0IvPFvbz5FbEn+KEk3wMjwMeSfCsBXFBdly9CAPk9ydyffpECuB5tZfVJjaKWueOSfinln6YK4lahQoUKRxd/AcRPGTcQCAUQAAAAAElFTkSuQmCC', - ChevronRight: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTguNTksMTYuNThMMTMuMTcsMTJMOC41OSw3LjQxTDEwLDZMMTYsMTJMMTAsMThMOC41OSwxNi41OFoiIC8+PC9zdmc+', - Check: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTIxLDdMOSwxOUwzLjUsMTMuNUw0LjkxLDEyLjA5TDksMTYuMTdMMTkuNTksNS41OUwyMSw3WiIgLz48L3N2Zz4=', - ViewIndicator: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBpZD0idmlldy1pbmRpY2F0b3IiIGhlaWdodD0iMzAiIHdpZHRoPSIzMCIgdmlld0JveD0iLTIuNSAtMSAzMCAzMCI+Cgk8c3R5bGUgdHlwZT0idGV4dC9jc3MiPi5zdDB7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLW1pdGVybGltaXQ6MTA7ZmlsbDpub25lO30uc3Qxe3N0cm9rZS13aWR0aDo2O3N0cm9rZS1taXRlcmxpbWl0OjEwO30KCTwvc3R5bGU+Cgk8Zz4KCQk8cGF0aCBjbGFzcz0ic3QwIiBkPSJNIDEyLjUgMCBBIDEyLjUgMTIuNSAwIDAgMCAtMTIuNSAwIEEgMTIuNSAxMi41IDAgMCAwIDEyLjUgMCIgdHJhbnNmb3JtPSJtYXRyaXgoMSwwLDAsMSwxMywxNS41KSI+PC9wYXRoPgoJCTxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0gMTMgMCBMIDEwIDIgTCAxNiAyIFoiPjwvcGF0aD4KCQk8cGF0aCBjbGFzcz0ic3QyIiBkPSJNIDIgMCBBIDIgMiAwIDAgMCAtMiAwIEEgMiAyIDAgMCAwIDIgMCIgdHJhbnNmb3JtPSJtYXRyaXgoMSwwLDAsMSwxMywxNS41KSI+PC9wYXRoPgoJCTxwYXRoIGNsYXNzPSJzdDEiIGlkPSJpbmRpY2F0b3IiIHRyYW5zZm9ybT0ibWF0cml4KDEsMCwwLDEsMTMsMTUuNSkiPjwvcGF0aD4KCTwvZz4KPC9zdmc+' - }; - - /** - * @module ImageLoader - * @description Image loader with progress based on {@link https://github.com/mrdoob/three.js/blob/master/src/loaders/ImageLoader.js} - */ - const ImageLoader = { - - /** - * Load image - * @example PANOLENS.ImageLoader.load( IMAGE_URL ) - * @method load - * @param {string} url - An image url - * @param {function} onLoad - On load callback - * @param {function} onProgress - In progress callback - * @param {function} onError - On error callback - */ - load: function ( url, onLoad = () => {}, onProgress = () => {}, onError = () => {} ) { - - // Enable cache - THREE.Cache.enabled = true; - - let cached, request, arrayBufferView, blob, urlCreator, image, reference; - - // Reference key - for (let iconName in DataImage) { - - if (DataImage.hasOwnProperty(iconName) && url === DataImage[iconName]) { - - reference = iconName; - - } - - } - - // Cached - cached = THREE.Cache.get(reference ? reference : url); - - if (cached !== undefined) { - - if (onLoad) { - - setTimeout(function () { - - onProgress({loaded: 1, total: 1}); - onLoad(cached); - - }, 0); - - } - - return cached; - - } - - // Construct a new XMLHttpRequest - urlCreator = window.URL || window.webkitURL; - image = document.createElementNS('http://www.w3.org/1999/xhtml', 'img'); - - // Add to cache - THREE.Cache.add(reference ? reference : url, image); - - const onImageLoaded = () => { - - urlCreator.revokeObjectURL(image.src); - onLoad(image); - - }; - - if (url.indexOf('data:') === 0) { - - image.addEventListener('load', onImageLoaded, false); - image.src = url; - return image; - } - - image.crossOrigin = this.crossOrigin !== undefined ? this.crossOrigin : ''; - - request = new window.XMLHttpRequest(); - request.open('GET', url, true); - if (process.env.npm_lifecycle_event !== 'test') { - request.onreadystatechange = function () { - if (this.readyState === 4 && this.status >= 400) { - onError(); - } - }; - } - request.responseType = 'arraybuffer'; - request.addEventListener( 'error', onError ); - request.addEventListener( 'progress', event => { - - if ( !event ) return; - - const { loaded, total, lengthComputable } = event; - - if ( lengthComputable ) { - - onProgress( { loaded, total } ); - - } - - } ); - - request.addEventListener( 'loadend', event => { - - if ( !event ) return; - const { currentTarget: { response } } = event; - - arrayBufferView = new Uint8Array( response ); - blob = new window.Blob( [ arrayBufferView ] ); - - image.addEventListener( 'load', onImageLoaded, false ); - image.src = urlCreator.createObjectURL( blob ); - - } ); - - request.send(null); - - } - - }; - - /** - * @module TextureLoader - * @description Texture loader based on {@link https://github.com/mrdoob/three.js/blob/master/src/loaders/TextureLoader.js} - */ - const TextureLoader = { - - /** - * Load image texture - * @example PANOLENS.TextureLoader.load( IMAGE_URL ) - * @method load - * @param {string} url - An image url - * @param {function} onLoad - On load callback - * @param {function} onProgress - In progress callback - * @param {function} onError - On error callback - * @return {THREE.Texture} - Image texture - */ - load: function ( url, onLoad = () => {}, onProgress, onError ) { - - var texture = new THREE.Texture(); - - ImageLoader.load( url, function ( image ) { - - texture.image = image; - - // JPEGs can't have an alpha channel, so memory can be saved by storing them as RGB. - const isJPEG = url.search( /\.(jpg|jpeg)$/ ) > 0 || url.search( /^data\:image\/jpeg/ ) === 0; - - texture.format = isJPEG ? THREE.RGBFormat : THREE.RGBAFormat; - texture.needsUpdate = true; - - onLoad( texture ); - - }, onProgress, onError ); - - return texture; - - } - - }; - - /** - * @module CubeTextureLoader - * @description Cube Texture Loader based on {@link https://github.com/mrdoob/three.js/blob/master/src/loaders/CubeTextureLoader.js} - */ - const CubeTextureLoader = { - - /** - * Load 6 images as a cube texture - * @example PANOLENS.CubeTextureLoader.load( [ 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' ] ) - * @method load - * @param {array} urls - array of 6 urls to images, one for each side of the CubeTexture. The urls should be specified in the following order: pos-x, neg-x, pos-y, neg-y, pos-z, neg-z - * @param {function} onLoad - On load callback - * @param {function} onProgress - In progress callback - * @param {function} onError - On error callback - * @return {THREE.CubeTexture} - Cube texture - */ - load: function ( urls, onLoad = () => {}, onProgress = () => {}, onError ) { - - var texture, loaded, progress, all, loadings; - - texture = new THREE.CubeTexture( [] ); - - loaded = 0; - progress = {}; - all = {}; - - urls.map( function ( url, index ) { - - ImageLoader.load( url, function ( image ) { - - texture.images[ index ] = image; - - loaded++; - - if ( loaded === 6 ) { - - texture.needsUpdate = true; - - onLoad( texture ); - - } - - }, function ( event ) { - - progress[ index ] = { loaded: event.loaded, total: event.total }; - - all.loaded = 0; - all.total = 0; - loadings = 0; - - for ( var i in progress ) { - - loadings++; - all.loaded += progress[ i ].loaded; - all.total += progress[ i ].total; - - } - - if ( loadings < 6 ) { - - all.total = all.total / loadings * 6; - - } - - onProgress( all ); - - }, onError ); - - } ); - - return texture; - - } - - }; - - /** - * @classdesc User Media - * @constructor - * @param {object} [constraints={ video: { width: { ideal: 1920 }, height: { ideal: 1080 }, facingMode: { exact: 'environment' } }, audio: false }] - */ - function Media ( constraints ) { - - const defaultConstraints = { video: { width: { ideal: 1920 }, height: { ideal: 1080 }, facingMode: { exact: 'environment' } }, audio: false }; - - this.constraints = Object.assign( defaultConstraints, constraints ); - - this.container = null; - this.scene = null; - this.element = null; - this.devices = []; - this.stream = null; - this.ratioScalar = 1; - this.videoDeviceIndex = 0; - - } - Media.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype ), { - - setContainer: function ( container ) { - - this.container = container; - - }, - - setScene: function ( scene ) { - - this.scene = scene; - - }, - - /** - * Enumerate devices - * @memberOf Media - * @instance - * @returns {Promise} - */ - enumerateDevices: function () { - - const devices = this.devices; - const resolvedPromise = new Promise( resolve => { resolve( devices ); } ); - - return devices.length > 0 ? resolvedPromise : window.navigator.mediaDevices.enumerateDevices(); - - }, - - /** - * Switch to next available video device - * @memberOf Media - * @instance - */ - switchNextVideoDevice: function () { - - const stop = this.stop.bind( this ); - const start = this.start.bind( this ); - const setVideDeviceIndex = this.setVideDeviceIndex.bind( this ); - - let index = this.videoDeviceIndex; - - this.getDevices( 'video' ) - .then( devices => { - stop(); - index++; - if ( index >= devices.length ) { - setVideDeviceIndex( 0 ); - index--; - } else { - setVideDeviceIndex( index ); - } - - start( devices[ index ] ); - - - } ); - - }, - - /** - * Get devices - * @param {string} type - type keyword to match device.kind - * @memberOf Media - * @instance - */ - getDevices: function ( type = 'video' ) { - - const devices = this.devices; - const validate = _devices => { - - return _devices.map( device => { - - if ( !devices.includes( device ) ) { devices.push( device ); } - return device; - - } ); - - }; - const filter = _devices => { - - const reg = new RegExp( type, 'i' ); - return _devices.filter( device => reg.test( device.kind ) ); - - }; - - return this.enumerateDevices() - .then( validate ) - .then( filter ); - - }, - - /** - * Get user media - * @param {MediaStreamConstraints} constraints - * @memberOf Media - * @instance - */ - getUserMedia: function ( constraints ) { - - const setMediaStream = this.setMediaStream.bind( this ); - const playVideo = this.playVideo.bind( this ); - const onCatchError = error => { console.warn( `PANOLENS.Media: ${error}` ); }; - - return window.navigator.mediaDevices.getUserMedia( constraints ) - .then( setMediaStream ) - .then( playVideo ) - .catch( onCatchError ); - - }, - - /** - * Set video device index - * @param {number} index - * @memberOf Media - * @instance - */ - setVideDeviceIndex: function ( index ) { - - this.videoDeviceIndex = index; - - }, - - /** - * Start streaming - * @param {MediaDeviceInfo} [targetDevice] - * @memberOf Media - * @instance - */ - start: function( targetDevice ) { - - const constraints = this.constraints; - const getUserMedia = this.getUserMedia.bind( this ); - const onVideoDevices = devices => { - - if ( !devices || devices.length === 0 ) { - - throw Error( 'no video device found' ); - - } - - const device = targetDevice || devices[ 0 ]; - constraints.video.deviceId = device.deviceId; - - return getUserMedia( constraints ); - - }; - - this.element = this.createVideoElement(); - - return this.getDevices().then( onVideoDevices ); - - }, - - /** - * Stop streaming - * @memberOf Media - * @instance - */ - stop: function () { - - const stream = this.stream; - - if ( stream && stream.active ) { - - const track = stream.getTracks()[ 0 ]; - - track.stop(); - - window.removeEventListener( 'resize', this.onWindowResize.bind( this ) ); - - this.element = null; - this.stream = null; - - } - - }, - - /** - * Set media stream - * @param {MediaStream} stream - * @memberOf Media - * @instance - */ - setMediaStream: function ( stream ) { - - this.stream = stream; - this.element.srcObject = stream; - - if ( this.scene ) { - - this.scene.background = this.createVideoTexture(); - - } - - window.addEventListener( 'resize', this.onWindowResize.bind( this ) ); - - }, - - /** - * Play video element - * @memberOf Media - * @instance - */ - playVideo: function () { - - const { element } = this; - - if ( element ) { - - element.play(); - this.dispatchEvent( { type: 'play' } ); - - } - - }, - - /** - * Pause video element - * @memberOf Media - * @instance - */ - pauseVideo: function () { - - const { element } = this; - - if ( element ) { - - element.pause(); - this.dispatchEvent( { type: 'pause' } ); - - } - - }, - - /** - * Create video texture - * @memberOf Media - * @instance - * @returns {THREE.VideoTexture} - */ - createVideoTexture: function () { - - const video = this.element; - const texture = new THREE.VideoTexture( video ); - - texture.generateMipmaps = false; - texture.minFilter = THREE.LinearFilter; - texture.magFilter = THREE.LinearFilter; - texture.format = THREE.RGBFormat; - texture.center.set( 0.5, 0.5 ); - - video.addEventListener( 'canplay', this.onWindowResize.bind( this ) ); - - return texture; - - }, - - /** - * Create video element - * @memberOf Media - * @instance - * @returns {HTMLVideoElement} - * @fires Media#canplay - */ - createVideoElement: function() { - - const dispatchEvent = this.dispatchEvent.bind( this ); - const video = document.createElement( 'video' ); - - /** - * Video can play event - * @type {object} - * @event Media#canplay - */ - const canPlay = () => dispatchEvent( { type: 'canplay' } ); - - video.setAttribute( 'autoplay', '' ); - video.setAttribute( 'muted', '' ); - video.setAttribute( 'playsinline', '' ); - - video.style.position = 'absolute'; - video.style.top = '0'; - video.style.left = '0'; - video.style.width = '100%'; - video.style.height = '100%'; - video.style.objectPosition = 'center'; - video.style.objectFit = 'cover'; - video.style.display = this.scene ? 'none' : ''; - - video.addEventListener( 'canplay', canPlay ); - - return video; - - }, - - /** - * On window resize event - * @param {Event} event - * @memberOf Media - * @instance - */ - onWindowResize: function () { - - if ( this.element && this.element.videoWidth && this.element.videoHeight && this.scene ) { - - const { clientWidth: width, clientHeight: height } = this.container; - const texture = this.scene.background; - const { videoWidth, videoHeight } = this.element; - const cameraRatio = videoHeight / videoWidth; - const viewportRatio = this.container ? width / height : 1.0; - const ratio = cameraRatio * viewportRatio * this.ratioScalar; - - if ( width > height ) { - texture.repeat.set( ratio, 1 ); - } else { - texture.repeat.set( 1, 1 / ratio ); - } - - } - - } - - } ); - - /** - * @classdesc Reticle 3D Sprite - * @constructor - * @param {THREE.Color} [color=0xffffff] - Color of the reticle sprite - * @param {boolean} [autoSelect=true] - Auto selection - * @param {number} [dwellTime=1500] - Duration for dwelling sequence to complete - */ - - function Reticle ( color = 0xffffff, autoSelect = true, dwellTime = 1500 ) { - - this.dpr = window.devicePixelRatio; - - const { canvas, context } = this.createCanvas(); - const material = new THREE.SpriteMaterial( { color, map: this.createCanvasTexture( canvas ) } ); - - THREE.Sprite.call( this, material ); - - this.canvasWidth = canvas.width; - this.canvasHeight = canvas.height; - this.context = context; - this.color = color instanceof THREE.Color ? color : new THREE.Color( color ); - - this.autoSelect = autoSelect; - this.dwellTime = dwellTime; - this.rippleDuration = 500; - this.position.z = -10; - this.center.set( 0.5, 0.5 ); - this.scale.set( 0.5, 0.5, 1 ); - - this.startTimestamp = null; - this.timerId = null; - this.callback = null; - - this.frustumCulled = false; - - this.updateCanvasArcByProgress( 0 ); - - } - Reticle.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { - - constructor: Reticle, - - /** - * Set material color - * @param {THREE.Color} color - * @memberOf Reticle - * @instance - */ - setColor: function ( color ) { - - this.material.color.copy( color instanceof THREE.Color ? color : new THREE.Color( color ) ); - - }, - - /** - * Create canvas texture - * @param {HTMLCanvasElement} canvas - * @memberOf Reticle - * @instance - * @returns {THREE.CanvasTexture} - */ - createCanvasTexture: function ( canvas ) { - - const texture = new THREE.CanvasTexture( canvas ); - texture.minFilter = THREE.LinearFilter; - texture.magFilter = THREE.LinearFilter; - texture.generateMipmaps = false; - - return texture; - - }, - - /** - * Create canvas element - * @memberOf Reticle - * @instance - * @returns {object} object - * @returns {HTMLCanvasElement} object.canvas - * @returns {CanvasRenderingContext2D} object.context - */ - createCanvas: function () { - - const width = 32; - const height = 32; - const canvas = document.createElement( 'canvas' ); - const context = canvas.getContext( '2d' ); - const dpr = this.dpr; - - canvas.width = width * dpr; - canvas.height = height * dpr; - context.scale( dpr, dpr ); - - context.shadowBlur = 5; - context.shadowColor = 'rgba(200,200,200,0.9)'; - - return { canvas, context }; - - }, - - /** - * Update canvas arc by progress - * @param {number} progress - * @memberOf Reticle - * @instance - */ - updateCanvasArcByProgress: function ( progress ) { - - const context = this.context; - const { canvasWidth, canvasHeight, material } = this; - const dpr = this.dpr; - const degree = progress * Math.PI * 2; - const color = this.color.getStyle(); - const x = canvasWidth * 0.5 / dpr; - const y = canvasHeight * 0.5 / dpr; - const lineWidth = 3; - - context.clearRect( 0, 0, canvasWidth, canvasHeight ); - context.beginPath(); - - if ( progress === 0 ) { - context.arc( x, y, canvasWidth / 16, 0, 2 * Math.PI ); - context.fillStyle = color; - context.fill(); - } else { - context.arc( x, y, canvasWidth / 4 - lineWidth, -Math.PI / 2, -Math.PI / 2 + degree ); - context.strokeStyle = color; - context.lineWidth = lineWidth; - context.stroke(); - } - - context.closePath(); - - material.map.needsUpdate = true; - - }, - - /** - * Ripple effect - * @memberOf Reticle - * @instance - * @fires Reticle#reticle-ripple-start - * @fires Reticle#reticle-ripple-end - */ - ripple: function () { - - const context = this.context; - const { canvasWidth, canvasHeight, material } = this; - const duration = this.rippleDuration; - const timestamp = performance.now(); - const color = this.color; - const dpr = this.dpr; - const x = canvasWidth * 0.5 / dpr; - const y = canvasHeight * 0.5 / dpr; - - const update = () => { - - const timerId = window.requestAnimationFrame( update ); - const elapsed = performance.now() - timestamp; - const progress = elapsed / duration; - const opacity = 1.0 - progress > 0 ? 1.0 - progress : 0; - const radius = progress * canvasWidth * 0.5 / dpr; - - context.clearRect( 0, 0, canvasWidth, canvasHeight ); - context.beginPath(); - context.arc( x, y, radius, 0, Math.PI * 2 ); - context.fillStyle = `rgba(${color.r * 255}, ${color.g * 255}, ${color.b * 255}, ${opacity})`; - context.fill(); - context.closePath(); - - if ( progress >= 1.0 ) { - - window.cancelAnimationFrame( timerId ); - this.updateCanvasArcByProgress( 0 ); - - /** - * Reticle ripple end event - * @type {object} - * @event Reticle#reticle-ripple-end - */ - this.dispatchEvent( { type: 'reticle-ripple-end' } ); - - } - - material.map.needsUpdate = true; - - }; - - /** - * Reticle ripple start event - * @type {object} - * @event Reticle#reticle-ripple-start - */ - this.dispatchEvent( { type: 'reticle-ripple-start' } ); - - update(); - - }, - - /** - * Make reticle visible - * @memberOf Reticle - * @instance - */ - show: function () { - - this.visible = true; - - }, - - /** - * Make reticle invisible - * @memberOf Reticle - * @instance - */ - hide: function () { - - this.visible = false; - - }, - - /** - * Start dwelling - * @param {function} callback - * @memberOf Reticle - * @instance - * @fires Reticle#reticle-start - */ - start: function ( callback ) { - - if ( !this.autoSelect ) { - - return; - - } - - /** - * Reticle start event - * @type {object} - * @event Reticle#reticle-start - */ - this.dispatchEvent( { type: 'reticle-start' } ); - - this.startTimestamp = performance.now(); - this.callback = callback; - this.update(); - - }, - - /** - * End dwelling - * @memberOf Reticle - * @instance - * @fires Reticle#reticle-end - */ - end: function(){ - - if ( !this.startTimestamp ) { return; } - - window.cancelAnimationFrame( this.timerId ); - - this.updateCanvasArcByProgress( 0 ); - this.callback = null; - this.timerId = null; - this.startTimestamp = null; - - /** - * Reticle end event - * @type {object} - * @event Reticle#reticle-end - */ - this.dispatchEvent( { type: 'reticle-end' } ); - - }, - - /** - * Update dwelling - * @memberOf Reticle - * @instance - * @fires Reticle#reticle-update - */ - update: function () { - - this.timerId = window.requestAnimationFrame( this.update.bind( this ) ); - - const elapsed = performance.now() - this.startTimestamp; - const progress = elapsed / this.dwellTime; - - this.updateCanvasArcByProgress( progress ); - - /** - * Reticle update event - * @type {object} - * @event Reticle#reticle-update - */ - this.dispatchEvent( { type: 'reticle-update', progress } ); - - if ( progress >= 1.0 ) { - - window.cancelAnimationFrame( this.timerId ); - if ( this.callback ) { this.callback(); } - this.end(); - this.ripple(); - - } - - } - - } ); - - function createCommonjsModule(fn, module) { - return module = { exports: {} }, fn(module, module.exports), module.exports; - } - - var Tween = createCommonjsModule(function (module, exports) { - /** - * Tween.js - Licensed under the MIT license - * https://github.com/tweenjs/tween.js - * ---------------------------------------------- - * - * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors. - * Thank you all, you're awesome! - */ - - - var _Group = function () { - this._tweens = {}; - this._tweensAddedDuringUpdate = {}; - }; - - _Group.prototype = { - getAll: function () { - - return Object.keys(this._tweens).map(function (tweenId) { - return this._tweens[tweenId]; - }.bind(this)); - - }, - - removeAll: function () { - - this._tweens = {}; - - }, - - add: function (tween) { - - this._tweens[tween.getId()] = tween; - this._tweensAddedDuringUpdate[tween.getId()] = tween; - - }, - - remove: function (tween) { - - delete this._tweens[tween.getId()]; - delete this._tweensAddedDuringUpdate[tween.getId()]; - - }, - - update: function (time, preserve) { - - var tweenIds = Object.keys(this._tweens); - - if (tweenIds.length === 0) { - return false; - } - - time = time !== undefined ? time : TWEEN.now(); - - // Tweens are updated in "batches". If you add a new tween during an update, then the - // new tween will be updated in the next batch. - // If you remove a tween during an update, it may or may not be updated. However, - // if the removed tween was added during the current batch, then it will not be updated. - while (tweenIds.length > 0) { - this._tweensAddedDuringUpdate = {}; - - for (var i = 0; i < tweenIds.length; i++) { - - var tween = this._tweens[tweenIds[i]]; - - if (tween && tween.update(time) === false) { - tween._isPlaying = false; - - if (!preserve) { - delete this._tweens[tweenIds[i]]; - } - } - } - - tweenIds = Object.keys(this._tweensAddedDuringUpdate); - } - - return true; - - } - }; - - var TWEEN = new _Group(); - - TWEEN.Group = _Group; - TWEEN._nextId = 0; - TWEEN.nextId = function () { - return TWEEN._nextId++; - }; - - - // Include a performance.now polyfill. - // In node.js, use process.hrtime. - if (typeof (self) === 'undefined' && typeof (process) !== 'undefined' && process.hrtime) { - TWEEN.now = function () { - var time = process.hrtime(); - - // Convert [seconds, nanoseconds] to milliseconds. - return time[0] * 1000 + time[1] / 1000000; - }; - } - // In a browser, use self.performance.now if it is available. - else if (typeof (self) !== 'undefined' && - self.performance !== undefined && - self.performance.now !== undefined) { - // This must be bound, because directly assigning this function - // leads to an invocation exception in Chrome. - TWEEN.now = self.performance.now.bind(self.performance); - } - // Use Date.now if it is available. - else if (Date.now !== undefined) { - TWEEN.now = Date.now; - } - // Otherwise, use 'new Date().getTime()'. - else { - TWEEN.now = function () { - return new Date().getTime(); - }; - } - - - TWEEN.Tween = function (object, group) { - this._object = object; - this._valuesStart = {}; - this._valuesEnd = {}; - this._valuesStartRepeat = {}; - this._duration = 1000; - this._repeat = 0; - this._repeatDelayTime = undefined; - this._yoyo = false; - this._isPlaying = false; - this._reversed = false; - this._delayTime = 0; - this._startTime = null; - this._easingFunction = TWEEN.Easing.Linear.None; - this._interpolationFunction = TWEEN.Interpolation.Linear; - this._chainedTweens = []; - this._onStartCallback = null; - this._onStartCallbackFired = false; - this._onUpdateCallback = null; - this._onRepeatCallback = null; - this._onCompleteCallback = null; - this._onStopCallback = null; - this._group = group || TWEEN; - this._id = TWEEN.nextId(); - - }; - - TWEEN.Tween.prototype = { - getId: function () { - return this._id; - }, - - isPlaying: function () { - return this._isPlaying; - }, - - to: function (properties, duration) { - - this._valuesEnd = Object.create(properties); - - if (duration !== undefined) { - this._duration = duration; - } - - return this; - - }, - - duration: function duration(d) { - this._duration = d; - return this; - }, - - start: function (time) { - - this._group.add(this); - - this._isPlaying = true; - - this._onStartCallbackFired = false; - - this._startTime = time !== undefined ? typeof time === 'string' ? TWEEN.now() + parseFloat(time) : time : TWEEN.now(); - this._startTime += this._delayTime; - - for (var property in this._valuesEnd) { - - // Check if an Array was provided as property value - if (this._valuesEnd[property] instanceof Array) { - - if (this._valuesEnd[property].length === 0) { - continue; - } - - // Create a local copy of the Array with the start value at the front - this._valuesEnd[property] = [this._object[property]].concat(this._valuesEnd[property]); - - } - - // If `to()` specifies a property that doesn't exist in the source object, - // we should not set that property in the object - if (this._object[property] === undefined) { - continue; - } - - // Save the starting value. - this._valuesStart[property] = this._object[property]; - - if ((this._valuesStart[property] instanceof Array) === false) { - this._valuesStart[property] *= 1.0; // Ensures we're using numbers, not strings - } - - this._valuesStartRepeat[property] = this._valuesStart[property] || 0; - - } - - return this; - - }, - - stop: function () { - - if (!this._isPlaying) { - return this; - } - - this._group.remove(this); - this._isPlaying = false; - - if (this._onStopCallback !== null) { - this._onStopCallback(this._object); - } - - this.stopChainedTweens(); - return this; - - }, - - end: function () { - - this.update(Infinity); - return this; - - }, - - stopChainedTweens: function () { - - for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { - this._chainedTweens[i].stop(); - } - - }, - - group: function (group) { - this._group = group; - return this; - }, - - delay: function (amount) { - - this._delayTime = amount; - return this; - - }, - - repeat: function (times) { - - this._repeat = times; - return this; - - }, - - repeatDelay: function (amount) { - - this._repeatDelayTime = amount; - return this; - - }, - - yoyo: function (yoyo) { - - this._yoyo = yoyo; - return this; - - }, - - easing: function (easingFunction) { - - this._easingFunction = easingFunction; - return this; - - }, - - interpolation: function (interpolationFunction) { - - this._interpolationFunction = interpolationFunction; - return this; - - }, - - chain: function () { - - this._chainedTweens = arguments; - return this; - - }, - - onStart: function (callback) { - - this._onStartCallback = callback; - return this; - - }, - - onUpdate: function (callback) { - - this._onUpdateCallback = callback; - return this; - - }, - - onRepeat: function onRepeat(callback) { - - this._onRepeatCallback = callback; - return this; - - }, - - onComplete: function (callback) { - - this._onCompleteCallback = callback; - return this; - - }, - - onStop: function (callback) { - - this._onStopCallback = callback; - return this; - - }, - - update: function (time) { - - var property; - var elapsed; - var value; - - if (time < this._startTime) { - return true; - } - - if (this._onStartCallbackFired === false) { - - if (this._onStartCallback !== null) { - this._onStartCallback(this._object); - } - - this._onStartCallbackFired = true; - } - - elapsed = (time - this._startTime) / this._duration; - elapsed = (this._duration === 0 || elapsed > 1) ? 1 : elapsed; - - value = this._easingFunction(elapsed); - - for (property in this._valuesEnd) { - - // Don't update properties that do not exist in the source object - if (this._valuesStart[property] === undefined) { - continue; - } - - var start = this._valuesStart[property] || 0; - var end = this._valuesEnd[property]; - - if (end instanceof Array) { - - this._object[property] = this._interpolationFunction(end, value); - - } else { - - // Parses relative end values with start as base (e.g.: +10, -3) - if (typeof (end) === 'string') { - - if (end.charAt(0) === '+' || end.charAt(0) === '-') { - end = start + parseFloat(end); - } else { - end = parseFloat(end); - } - } - - // Protect against non numeric properties. - if (typeof (end) === 'number') { - this._object[property] = start + (end - start) * value; - } - - } - - } - - if (this._onUpdateCallback !== null) { - this._onUpdateCallback(this._object, elapsed); - } - - if (elapsed === 1) { - - if (this._repeat > 0) { - - if (isFinite(this._repeat)) { - this._repeat--; - } - - // Reassign starting values, restart by making startTime = now - for (property in this._valuesStartRepeat) { - - if (typeof (this._valuesEnd[property]) === 'string') { - this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]); - } - - if (this._yoyo) { - var tmp = this._valuesStartRepeat[property]; - - this._valuesStartRepeat[property] = this._valuesEnd[property]; - this._valuesEnd[property] = tmp; - } - - this._valuesStart[property] = this._valuesStartRepeat[property]; - - } - - if (this._yoyo) { - this._reversed = !this._reversed; - } - - if (this._repeatDelayTime !== undefined) { - this._startTime = time + this._repeatDelayTime; - } else { - this._startTime = time + this._delayTime; - } - - if (this._onRepeatCallback !== null) { - this._onRepeatCallback(this._object); - } - - return true; - - } else { - - if (this._onCompleteCallback !== null) { - - this._onCompleteCallback(this._object); - } - - for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { - // Make the chained tweens start exactly at the time they should, - // even if the `update()` method was called way past the duration of the tween - this._chainedTweens[i].start(this._startTime + this._duration); - } - - return false; - - } - - } - - return true; - - } - }; - - - TWEEN.Easing = { - - Linear: { - - None: function (k) { - - return k; - - } - - }, - - Quadratic: { - - In: function (k) { - - return k * k; - - }, - - Out: function (k) { - - return k * (2 - k); - - }, - - InOut: function (k) { - - if ((k *= 2) < 1) { - return 0.5 * k * k; - } - - return - 0.5 * (--k * (k - 2) - 1); - - } - - }, - - Cubic: { - - In: function (k) { - - return k * k * k; - - }, - - Out: function (k) { - - return --k * k * k + 1; - - }, - - InOut: function (k) { - - if ((k *= 2) < 1) { - return 0.5 * k * k * k; - } - - return 0.5 * ((k -= 2) * k * k + 2); - - } - - }, - - Quartic: { - - In: function (k) { - - return k * k * k * k; - - }, - - Out: function (k) { - - return 1 - (--k * k * k * k); - - }, - - InOut: function (k) { - - if ((k *= 2) < 1) { - return 0.5 * k * k * k * k; - } - - return - 0.5 * ((k -= 2) * k * k * k - 2); - - } - - }, - - Quintic: { - - In: function (k) { - - return k * k * k * k * k; - - }, - - Out: function (k) { - - return --k * k * k * k * k + 1; - - }, - - InOut: function (k) { - - if ((k *= 2) < 1) { - return 0.5 * k * k * k * k * k; - } - - return 0.5 * ((k -= 2) * k * k * k * k + 2); - - } - - }, - - Sinusoidal: { - - In: function (k) { - - return 1 - Math.cos(k * Math.PI / 2); - - }, - - Out: function (k) { - - return Math.sin(k * Math.PI / 2); - - }, - - InOut: function (k) { - - return 0.5 * (1 - Math.cos(Math.PI * k)); - - } - - }, - - Exponential: { - - In: function (k) { - - return k === 0 ? 0 : Math.pow(1024, k - 1); - - }, - - Out: function (k) { - - return k === 1 ? 1 : 1 - Math.pow(2, - 10 * k); - - }, - - InOut: function (k) { - - if (k === 0) { - return 0; - } - - if (k === 1) { - return 1; - } - - if ((k *= 2) < 1) { - return 0.5 * Math.pow(1024, k - 1); - } - - return 0.5 * (- Math.pow(2, - 10 * (k - 1)) + 2); - - } - - }, - - Circular: { - - In: function (k) { - - return 1 - Math.sqrt(1 - k * k); - - }, - - Out: function (k) { - - return Math.sqrt(1 - (--k * k)); - - }, - - InOut: function (k) { - - if ((k *= 2) < 1) { - return - 0.5 * (Math.sqrt(1 - k * k) - 1); - } - - return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1); - - } - - }, - - Elastic: { - - In: function (k) { - - if (k === 0) { - return 0; - } - - if (k === 1) { - return 1; - } - - return -Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI); - - }, - - Out: function (k) { - - if (k === 0) { - return 0; - } - - if (k === 1) { - return 1; - } - - return Math.pow(2, -10 * k) * Math.sin((k - 0.1) * 5 * Math.PI) + 1; - - }, - - InOut: function (k) { - - if (k === 0) { - return 0; - } - - if (k === 1) { - return 1; - } - - k *= 2; - - if (k < 1) { - return -0.5 * Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI); - } - - return 0.5 * Math.pow(2, -10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI) + 1; - - } - - }, - - Back: { - - In: function (k) { - - var s = 1.70158; - - return k * k * ((s + 1) * k - s); - - }, - - Out: function (k) { - - var s = 1.70158; - - return --k * k * ((s + 1) * k + s) + 1; - - }, - - InOut: function (k) { - - var s = 1.70158 * 1.525; - - if ((k *= 2) < 1) { - return 0.5 * (k * k * ((s + 1) * k - s)); - } - - return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2); - - } - - }, - - Bounce: { - - In: function (k) { - - return 1 - TWEEN.Easing.Bounce.Out(1 - k); - - }, - - Out: function (k) { - - if (k < (1 / 2.75)) { - return 7.5625 * k * k; - } else if (k < (2 / 2.75)) { - return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75; - } else if (k < (2.5 / 2.75)) { - return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375; - } else { - return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375; - } - - }, - - InOut: function (k) { - - if (k < 0.5) { - return TWEEN.Easing.Bounce.In(k * 2) * 0.5; - } - - return TWEEN.Easing.Bounce.Out(k * 2 - 1) * 0.5 + 0.5; - - } - - } - - }; - - TWEEN.Interpolation = { - - Linear: function (v, k) { - - var m = v.length - 1; - var f = m * k; - var i = Math.floor(f); - var fn = TWEEN.Interpolation.Utils.Linear; - - if (k < 0) { - return fn(v[0], v[1], f); - } - - if (k > 1) { - return fn(v[m], v[m - 1], m - f); - } - - return fn(v[i], v[i + 1 > m ? m : i + 1], f - i); - - }, - - Bezier: function (v, k) { - - var b = 0; - var n = v.length - 1; - var pw = Math.pow; - var bn = TWEEN.Interpolation.Utils.Bernstein; - - for (var i = 0; i <= n; i++) { - b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i); - } - - return b; - - }, - - CatmullRom: function (v, k) { - - var m = v.length - 1; - var f = m * k; - var i = Math.floor(f); - var fn = TWEEN.Interpolation.Utils.CatmullRom; - - if (v[0] === v[m]) { - - if (k < 0) { - i = Math.floor(f = m * (1 + k)); - } - - return fn(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i); - - } else { - - if (k < 0) { - return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]); - } - - if (k > 1) { - return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]); - } - - return fn(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i); - - } - - }, - - Utils: { - - Linear: function (p0, p1, t) { - - return (p1 - p0) * t + p0; - - }, - - Bernstein: function (n, i) { - - var fc = TWEEN.Interpolation.Utils.Factorial; - - return fc(n) / fc(i) / fc(n - i); - - }, - - Factorial: (function () { - - var a = [1]; - - return function (n) { - - var s = 1; - - if (a[n]) { - return a[n]; - } - - for (var i = n; i > 1; i--) { - s *= i; - } - - a[n] = s; - return s; - - }; - - })(), - - CatmullRom: function (p0, p1, p2, p3, t) { - - var v0 = (p2 - p0) * 0.5; - var v1 = (p3 - p1) * 0.5; - var t2 = t * t; - var t3 = t * t2; - - return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (- 3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1; - - } - - } - - }; - - // UMD (Universal Module Definition) - (function (root) { - - { - - // Node.js - module.exports = TWEEN; - - } - - })(); - }); - - /** - * @classdesc Information spot attached to panorama - * @constructor - * @param {number} [scale=300] - Default scale - * @param {string} [imageSrc=PANOLENS.DataImage.Info] - Image overlay info - * @param {boolean} [animated=true] - Enable default hover animation - */ - function Infospot ( scale = 300, imageSrc, animated ) { - - const duration = 500, scaleFactor = 1.3; - - imageSrc = imageSrc || DataImage.Info; - - THREE.Sprite.call( this ); - - this.type = 'infospot'; - - this.animated = animated !== undefined ? animated : true; - this.isHovering = false; - - /* - * TODO: Three.js bug hotfix for sprite raycasting r104 - * https://github.com/mrdoob/three.js/issues/14624 - */ - this.frustumCulled = false; - - this.element = null; - this.toPanorama = null; - this.cursorStyle = null; - - this.mode = MODES.NORMAL; - - this.scale.set( scale, scale, 1 ); - this.rotation.y = Math.PI; - - this.container = null; - - this.originalRaycast = this.raycast; - - // Event Handler - this.HANDLER_FOCUS = null; - - this.material.side = THREE.DoubleSide; - this.material.depthTest = false; - this.material.transparent = true; - this.material.opacity = 0; - - this.scaleUpAnimation = new Tween.Tween(); - this.scaleDownAnimation = new Tween.Tween(); - - - const postLoad = function ( texture ) { - - if ( !this.material ) { return; } - - const ratio = texture.image.width / texture.image.height; - const textureScale = new THREE.Vector3(); - - texture.image.width = texture.image.naturalWidth || 64; - texture.image.height = texture.image.naturalHeight || 64; - - this.scale.set( ratio * scale, scale, 1 ); - - textureScale.copy( this.scale ); - - this.scaleUpAnimation = new Tween.Tween( this.scale ) - .to( { x: textureScale.x * scaleFactor, y: textureScale.y * scaleFactor }, duration ) - .easing( Tween.Easing.Elastic.Out ); - - this.scaleDownAnimation = new Tween.Tween( this.scale ) - .to( { x: textureScale.x, y: textureScale.y }, duration ) - .easing( Tween.Easing.Elastic.Out ); - - this.material.map = texture; - this.material.needsUpdate = true; - - }.bind( this ); - - // Add show and hide animations - this.showAnimation = new Tween.Tween( this.material ) - .to( { opacity: 1 }, duration ) - .onStart( this.enableRaycast.bind( this, true ) ) - .easing( Tween.Easing.Quartic.Out ); - - this.hideAnimation = new Tween.Tween( this.material ) - .to( { opacity: 0 }, duration ) - .onStart( this.enableRaycast.bind( this, false ) ) - .easing( Tween.Easing.Quartic.Out ); - - // Attach event listeners - this.addEventListener( 'click', this.onClick ); - this.addEventListener( 'hover', this.onHover ); - this.addEventListener( 'hoverenter', this.onHoverStart ); - this.addEventListener( 'hoverleave', this.onHoverEnd ); - this.addEventListener( 'panolens-dual-eye-effect', this.onDualEyeEffect ); - this.addEventListener( 'panolens-container', this.setContainer.bind( this ) ); - this.addEventListener( 'dismiss', this.onDismiss ); - this.addEventListener( 'panolens-infospot-focus', this.setFocusMethod ); - - TextureLoader.load( imageSrc, postLoad ); - - } - Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { - - constructor: Infospot, - - /** - * Set infospot container - * @param {HTMLElement|object} data - Data with container information - * @memberOf Infospot - * @instance - */ - setContainer: function ( data ) { - - let container; - - if ( data instanceof HTMLElement ) { - - container = data; - - } else if ( data && data.container ) { - - container = data.container; - - } - - // Append element if exists - if ( container && this.element ) { - - container.appendChild( this.element ); - - } - - this.container = container; - - }, - - /** - * Get container - * @memberOf Infospot - * @instance - * @return {HTMLElement} - The container of this infospot - */ - getContainer: function () { - - return this.container; - - }, - - /** - * This will be called by a click event - * Translate and lock the hovering element if any - * @param {object} event - Event containing mouseEvent with clientX and clientY - * @memberOf Infospot - * @instance - */ - onClick: function ( event ) { - - if ( this.element && this.getContainer() ) { - - this.onHoverStart( event ); - - // Lock element - this.lockHoverElement(); - - } - - }, - - /** - * Dismiss current element if any - * @param {object} event - Dismiss event - * @memberOf Infospot - * @instance - */ - onDismiss: function () { - - if ( this.element ) { - - this.unlockHoverElement(); - this.onHoverEnd(); - - } - - }, - - /** - * This will be called by a mouse hover event - * Translate the hovering element if any - * @param {object} event - Event containing mouseEvent with clientX and clientY - * @memberOf Infospot - * @instance - */ - onHover: function () {}, - - /** - * This will be called on a mouse hover start - * Sets cursor style to 'pointer', display the element and scale up the infospot - * @param {object} event - * @memberOf Infospot - * @instance - */ - onHoverStart: function ( event ) { - - if ( !this.getContainer() ) { return; } - - const cursorStyle = this.cursorStyle || ( this.mode === MODES.NORMAL ? 'pointer' : 'default' ); - const { scaleDownAnimation, scaleUpAnimation, element } = this; - - this.isHovering = true; - this.container.style.cursor = cursorStyle; - - if ( this.animated ) { - - scaleDownAnimation.stop(); - scaleUpAnimation.start(); - - } - - if ( element && event.mouseEvent.clientX >= 0 && event.mouseEvent.clientY >= 0 ) { - - const { left, right, style } = element; - - if ( this.mode === MODES.CARDBOARD || this.mode === MODES.STEREO ) { - - style.display = 'none'; - left.style.display = 'block'; - right.style.display = 'block'; - - // Store element width for reference - element._width = left.clientWidth; - element._height = left.clientHeight; - - } else { - - style.display = 'block'; - if ( left ) { left.style.display = 'none'; } - if ( right ) { right.style.display = 'none'; } - - // Store element width for reference - element._width = element.clientWidth; - element._height = element.clientHeight; - - } - - } - - }, - - /** - * This will be called on a mouse hover end - * Sets cursor style to 'default', hide the element and scale down the infospot - * @memberOf Infospot - * @instance - */ - onHoverEnd: function () { - - if ( !this.getContainer() ) { return; } - - const { scaleDownAnimation, scaleUpAnimation, element } = this; - - this.isHovering = false; - this.container.style.cursor = 'default'; - - if ( this.animated ) { - - scaleUpAnimation.stop(); - scaleDownAnimation.start(); - - } - - if ( element && !this.element.locked ) { - - const { left, right, style } = element; - - style.display = 'none'; - if ( left ) { left.style.display = 'none'; } - if ( right ) { right.style.display = 'none'; } - - this.unlockHoverElement(); - - } - - }, - - /** - * On dual eye effect handler - * Creates duplicate left and right element - * @param {object} event - panolens-dual-eye-effect event - * @memberOf Infospot - * @instance - */ - onDualEyeEffect: function ( event ) { - - if ( !this.getContainer() ) { return; } - - let element, halfWidth, halfHeight; - - this.mode = event.mode; - - element = this.element; - - halfWidth = this.container.clientWidth / 2; - halfHeight = this.container.clientHeight / 2; - - if ( !element ) { - - return; - - } - - if ( !element.left && !element.right ) { - - element.left = element.cloneNode( true ); - element.right = element.cloneNode( true ); - - } - - if ( this.mode === MODES.CARDBOARD || this.mode === MODES.STEREO ) { - - element.left.style.display = element.style.display; - element.right.style.display = element.style.display; - element.style.display = 'none'; - - } else { - - element.style.display = element.left.style.display; - element.left.style.display = 'none'; - element.right.style.display = 'none'; - - } - - // Update elements translation - this.translateElement( halfWidth, halfHeight ); - - this.container.appendChild( element.left ); - this.container.appendChild( element.right ); - - }, - - /** - * Translate the hovering element by css transform - * @param {number} x - X position on the window screen - * @param {number} y - Y position on the window screen - * @memberOf Infospot - * @instance - */ - translateElement: function ( x, y ) { - - if ( !this.element._width || !this.element._height || !this.getContainer() ) { - - return; - - } - - let left, top, element, width, height, delta, container; - - container = this.container; - element = this.element; - width = element._width / 2; - height = element._height / 2; - delta = element.verticalDelta !== undefined ? element.verticalDelta : 40; - - left = x - width; - top = y - height - delta; - - if ( ( this.mode === MODES.CARDBOARD || this.mode === MODES.STEREO ) - && element.left && element.right - && !( x === container.clientWidth / 2 && y === container.clientHeight / 2 ) ) { - - left = container.clientWidth / 4 - width + ( x - container.clientWidth / 2 ); - top = container.clientHeight / 2 - height - delta + ( y - container.clientHeight / 2 ); - - this.setElementStyle( 'transform', element.left, 'translate(' + left + 'px, ' + top + 'px)' ); - - left += container.clientWidth / 2; - - this.setElementStyle( 'transform', element.right, 'translate(' + left + 'px, ' + top + 'px)' ); - - } else { - - this.setElementStyle( 'transform', element, 'translate(' + left + 'px, ' + top + 'px)' ); - - } - - }, - - /** - * Set vendor specific css - * @param {string} type - CSS style name - * @param {HTMLElement} element - The element to be modified - * @param {string} value - Style value - * @memberOf Infospot - * @instance - */ - setElementStyle: function ( type, element, value ) { - - const style = element.style; - - if ( type === 'transform' ) { - - style.webkitTransform = style.msTransform = style.transform = value; - - } - - }, - - /** - * Set hovering text content - * @param {string} text - Text to be displayed - * @memberOf Infospot - * @instance - */ - setText: function ( text ) { - - if ( this.element ) { - - this.element.textContent = text; - - } - - }, - - /** - * Set cursor css style on hover - * @memberOf Infospot - * @instance - */ - setCursorHoverStyle: function ( style ) { - - this.cursorStyle = style; - - }, - - /** - * Add hovering text element - * @param {string} text - Text to be displayed - * @param {number} [delta=40] - Vertical delta to the infospot - * @memberOf Infospot - * @instance - */ - addHoverText: function ( text, delta = 40 ) { - - if ( !this.element ) { - - this.element = document.createElement( 'div' ); - this.element.style.display = 'none'; - this.element.style.color = '#fff'; - this.element.style.top = 0; - this.element.style.maxWidth = '50%'; - this.element.style.maxHeight = '50%'; - this.element.style.textShadow = '0 0 3px #000000'; - this.element.style.fontFamily = '"Trebuchet MS", Helvetica, sans-serif'; - this.element.style.position = 'absolute'; - this.element.classList.add( 'panolens-infospot' ); - this.element.verticalDelta = delta; - - } - - this.setText( text ); - - }, - - /** - * Add hovering element by cloning an element - * @param {HTMLDOMElement} el - Element to be cloned and displayed - * @param {number} [delta=40] - Vertical delta to the infospot - * @memberOf Infospot - * @instance - */ - addHoverElement: function ( el, delta = 40 ) { - - if ( !this.element ) { - - this.element = el.cloneNode( true ); - this.element.style.display = 'none'; - this.element.style.top = 0; - this.element.style.position = 'absolute'; - this.element.classList.add( 'panolens-infospot' ); - this.element.verticalDelta = delta; - - } - - }, - - /** - * Remove hovering element - * @memberOf Infospot - * @instance - */ - removeHoverElement: function () { - - if ( this.element ) { - - if ( this.element.left ) { - - this.container.removeChild( this.element.left ); - this.element.left = null; - - } - - if ( this.element.right ) { - - this.container.removeChild( this.element.right ); - this.element.right = null; - - } - - this.container.removeChild( this.element ); - this.element = null; - - } - - }, - - /** - * Lock hovering element - * @memberOf Infospot - * @instance - */ - lockHoverElement: function () { - - if ( this.element ) { - - this.element.locked = true; - - } - - }, - - /** - * Unlock hovering element - * @memberOf Infospot - * @instance - */ - unlockHoverElement: function () { - - if ( this.element ) { - - this.element.locked = false; - - } - - }, - - /** - * Enable raycasting - * @param {boolean} [enabled=true] - * @memberOf Infospot - * @instance - */ - enableRaycast: function ( enabled = true ) { - - if ( enabled ) { - - this.raycast = this.originalRaycast; - - } else { - - this.raycast = () => {}; - - } - - }, - - /** - * Show infospot - * @param {number} [delay=0] - Delay time to show - * @memberOf Infospot - * @instance - */ - show: function ( delay = 0 ) { - - const { animated, hideAnimation, showAnimation, material } = this; - - if ( animated ) { - - hideAnimation.stop(); - showAnimation.delay( delay ).start(); - - } else { - - this.enableRaycast( true ); - material.opacity = 1; - - } - - }, - - /** - * Hide infospot - * @param {number} [delay=0] - Delay time to hide - * @memberOf Infospot - * @instance - */ - hide: function ( delay = 0 ) { - - const { animated, hideAnimation, showAnimation, material, element } = this; - - if ( element ) { - const { style } = element; - style.display = 'none'; - } - - if ( animated ) { - - showAnimation.stop(); - hideAnimation.delay( delay ).start(); - - } else { - - this.enableRaycast( false ); - material.opacity = 0; - - } - - }, - - /** - * Set focus event handler - * @memberOf Infospot - * @instance - */ - setFocusMethod: function ( event ) { - - if ( event ) { - - this.HANDLER_FOCUS = event.method; - - } - - }, - - /** - * Focus camera center to this infospot - * @param {number} [duration=1000] - Duration to tween - * @param {function} [easing=TWEEN.Easing.Exponential.Out] - Easing function - * @memberOf Infospot - * @instance - */ - focus: function ( duration, easing ) { - - if ( this.HANDLER_FOCUS ) { - - this.HANDLER_FOCUS( this.position, duration, easing ); - this.onDismiss(); - - } - - }, - - /** - * Dispose - * @memberOf Infospot - * @instance - */ - dispose: function () { - - const { geometry, material } = this; - const { map } = material; - - this.removeHoverElement(); - - if ( this.parent ) { - - this.parent.remove( this ); - - } - - if ( map ) { map.dispose(); material.map = null; } - if ( geometry ) { geometry.dispose(); this.geometry = null; } - if ( material ) { material.dispose(); this.material = null; } - - } - - } ); - - /** - * @classdesc Widget for controls - * @constructor - * @param {HTMLElement} container - A domElement where default control widget will be attached to - */ - function Widget ( container ) { - - if ( !container ) { - - console.warn( 'PANOLENS.Widget: No container specified' ); - - } - - THREE.EventDispatcher.call( this ); - - this.DEFAULT_TRANSITION = 'all 0.27s ease'; - this.TOUCH_ENABLED = !!(( 'ontouchstart' in window ) || window.DocumentTouch && document instanceof DocumentTouch); - this.PREVENT_EVENT_HANDLER = function ( event ) { - event.preventDefault(); - event.stopPropagation(); - }; - - this.container = container; - - this.barElement = null; - this.fullscreenElement = null; - this.videoElement = null; - this.settingElement = null; - - this.mainMenu = null; - - this.activeMainItem = null; - this.activeSubMenu = null; - this.mask = null; - - } - - Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype ), { - - constructor: Widget, - - /** - * Add control bar - * @memberOf Widget - * @instance - */ - addControlBar: function () { - - if ( !this.container ) { - - console.warn( 'Widget container not set' ); - return; - } - - var scope = this, bar, styleTranslate, styleOpacity, gradientStyle; - - gradientStyle = 'linear-gradient(bottom, rgba(0,0,0,0.2), rgba(0,0,0,0))'; - - bar = document.createElement( 'div' ); - bar.style.width = '100%'; - bar.style.height = '44px'; - bar.style.float = 'left'; - bar.style.transform = bar.style.webkitTransform = bar.style.msTransform = 'translateY(-100%)'; - bar.style.background = '-webkit-' + gradientStyle; - bar.style.background = '-moz-' + gradientStyle; - bar.style.background = '-o-' + gradientStyle; - bar.style.background = '-ms-' + gradientStyle; - bar.style.background = gradientStyle; - bar.style.transition = this.DEFAULT_TRANSITION; - bar.style.pointerEvents = 'none'; - bar.isHidden = false; - bar.toggle = function () { - bar.isHidden = !bar.isHidden; - styleTranslate = bar.isHidden ? 'translateY(0)' : 'translateY(-100%)'; - styleOpacity = bar.isHidden ? 0 : 1; - bar.style.transform = bar.style.webkitTransform = bar.style.msTransform = styleTranslate; - bar.style.opacity = styleOpacity; - }; - - // Menu - var menu = this.createDefaultMenu(); - this.mainMenu = this.createMainMenu( menu ); - bar.appendChild( this.mainMenu ); - - // Mask - var mask = this.createMask(); - this.mask = mask; - this.container.appendChild( mask ); - - // Dispose - bar.dispose = function () { - - if ( scope.fullscreenElement ) { - - bar.removeChild( scope.fullscreenElement ); - scope.fullscreenElement.dispose(); - scope.fullscreenElement = null; - - } - - if ( scope.settingElement ) { - - bar.removeChild( scope.settingElement ); - scope.settingElement.dispose(); - scope.settingElement = null; - - } - - if ( scope.videoElement ) { - - bar.removeChild( scope.videoElement ); - scope.videoElement.dispose(); - scope.videoElement = null; - - } - - }; - - this.container.appendChild( bar ); - - // Mask events - this.mask.addEventListener( 'mousemove', this.PREVENT_EVENT_HANDLER, true ); - this.mask.addEventListener( 'mouseup', this.PREVENT_EVENT_HANDLER, true ); - this.mask.addEventListener( 'mousedown', this.PREVENT_EVENT_HANDLER, true ); - this.mask.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', function ( event ) { - - event.preventDefault(); - event.stopPropagation(); - - scope.mask.hide(); - scope.settingElement.deactivate(); - - }, false ); - - // Event listener - this.addEventListener( 'control-bar-toggle', bar.toggle ); - - this.barElement = bar; - - }, - - /** - * Create default menu - * @memberOf Widget - * @instance - */ - createDefaultMenu: function () { - - var scope = this, handler; - - handler = function ( method, data ) { - - return function () { - - scope.dispatchEvent( { - - type: 'panolens-viewer-handler', - method: method, - data: data - - } ); - - }; - - }; - - return [ - - { - title: 'Control', - subMenu: [ - { - title: this.TOUCH_ENABLED ? 'Touch' : 'Mouse', - handler: handler( 'enableControl', CONTROLS.ORBIT ) - }, - { - title: 'Sensor', - handler: handler( 'enableControl', CONTROLS.DEVICEORIENTATION ) - } - ] - }, - - { - title: 'Mode', - subMenu: [ - { - title: 'Normal', - handler: handler( 'disableEffect' ) - }, - { - title: 'Cardboard', - handler: handler( 'enableEffect', MODES.CARDBOARD ) - }, - { - title: 'Stereoscopic', - handler: handler( 'enableEffect', MODES.STEREO ) - } - ] - } - - ]; - - }, - - /** - * Add buttons on top of control bar - * @param {string} name - The control button name to be created - * @memberOf Widget - * @instance - */ - addControlButton: function ( name ) { - - let element; - - switch( name ) { - - case 'fullscreen': - - element = this.createFullscreenButton(); - this.fullscreenElement = element; - - break; - - case 'setting': - - element = this.createSettingButton(); - this.settingElement = element; - - break; - - case 'video': - - element = this.createVideoControl(); - this.videoElement = element; - - break; - - default: - - return; - - } - - if ( !element ) { - - return; - - } - - this.barElement.appendChild( element ); - - }, - - /** - * Create modal mask - * @memberOf Widget - * @instance - */ - createMask: function () { - - const element = document.createElement( 'div' ); - element.style.position = 'absolute'; - element.style.top = 0; - element.style.left = 0; - element.style.width = '100%'; - element.style.height = '100%'; - element.style.background = 'transparent'; - element.style.display = 'none'; - - element.show = function () { - - this.style.display = 'block'; - - }; - - element.hide = function () { - - this.style.display = 'none'; - - }; - - return element; - - }, - - /** - * Create Setting button to toggle menu - * @memberOf Widget - * @instance - */ - createSettingButton: function () { - - let scope = this, item; - - function onTap ( event ) { - - event.preventDefault(); - event.stopPropagation(); - - scope.mainMenu.toggle(); - - if ( this.activated ) { - - this.deactivate(); - - } else { - - this.activate(); - - } - - } - - item = this.createCustomItem( { - - style: { - - backgroundImage: 'url("' + DataImage.Setting + '")', - webkitTransition: this.DEFAULT_TRANSITION, - transition: this.DEFAULT_TRANSITION - - }, - - onTap: onTap - - } ); - - item.activate = function () { - - this.style.transform = 'rotate3d(0,0,1,90deg)'; - this.activated = true; - scope.mask.show(); - - }; - - item.deactivate = function () { - - this.style.transform = 'rotate3d(0,0,0,0)'; - this.activated = false; - scope.mask.hide(); - - if ( scope.mainMenu && scope.mainMenu.visible ) { - - scope.mainMenu.hide(); - - } - - if ( scope.activeSubMenu && scope.activeSubMenu.visible ) { - - scope.activeSubMenu.hide(); - - } - - if ( scope.mainMenu && scope.mainMenu._width ) { - - scope.mainMenu.changeSize( scope.mainMenu._width ); - scope.mainMenu.unslideAll(); - - } - - }; - - item.activated = false; - - return item; - - }, - - /** - * Create Fullscreen button - * @return {HTMLSpanElement} - The dom element icon for fullscreen - * @memberOf Widget - * @instance - * @fires Widget#panolens-viewer-handler - */ - createFullscreenButton: function () { - - let scope = this, item, isFullscreen = false, tapSkipped = true, stylesheetId; - - const { container } = this; - - stylesheetId = 'panolens-style-addon'; - - // Don't create button if no support - if ( !document.fullscreenEnabled && - !document.webkitFullscreenEnabled && - !document.mozFullScreenEnabled && - !document.msFullscreenEnabled ) { - return; - } - - function onTap ( event ) { - - event.preventDefault(); - event.stopPropagation(); - - tapSkipped = false; - - if ( !isFullscreen ) { - - if ( container.requestFullscreen ) { container.requestFullscreen(); } - if ( container.msRequestFullscreen ) { container.msRequestFullscreen(); } - if ( container.mozRequestFullScreen ) { container.mozRequestFullScreen(); } - if ( container.webkitRequestFullscreen ) { container.webkitRequestFullscreen( Element.ALLOW_KEYBOARD_INPUT ); } - - isFullscreen = true; - - } else { - - if ( document.exitFullscreen ) { document.exitFullscreen(); } - if ( document.msExitFullscreen ) { document.msExitFullscreen(); } - if ( document.mozCancelFullScreen ) { document.mozCancelFullScreen(); } - if ( document.webkitExitFullscreen ) { document.webkitExitFullscreen( ); } - - isFullscreen = false; - - } - - this.style.backgroundImage = ( isFullscreen ) - ? 'url("' + DataImage.FullscreenLeave + '")' - : 'url("' + DataImage.FullscreenEnter + '")'; - - } - - function onFullScreenChange () { - - if ( tapSkipped ) { - - isFullscreen = !isFullscreen; - - item.style.backgroundImage = ( isFullscreen ) - ? 'url("' + DataImage.FullscreenLeave + '")' - : 'url("' + DataImage.FullscreenEnter + '")'; - - } - - /** - * Viewer handler event - * @type {object} - * @event Widget#panolens-viewer-handler - * @property {string} method - 'onWindowResize' function call on Viewer - */ - scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onWindowResize' } ); - - tapSkipped = true; - - } - - document.addEventListener( 'fullscreenchange', onFullScreenChange, false ); - document.addEventListener( 'webkitfullscreenchange', onFullScreenChange, false ); - document.addEventListener( 'mozfullscreenchange', onFullScreenChange, false ); - document.addEventListener( 'MSFullscreenChange', onFullScreenChange, false ); - - item = this.createCustomItem( { - - style: { - - backgroundImage: 'url("' + DataImage.FullscreenEnter + '")' - - }, - - onTap: onTap - - } ); - - // Add fullscreen stlye if not exists - if ( !document.querySelector( stylesheetId ) ) { - const sheet = document.createElement( 'style' ); - sheet.id = stylesheetId; - sheet.innerHTML = ':-webkit-full-screen { width: 100% !important; height: 100% !important }'; - document.body.appendChild( sheet ); - } - - return item; - - }, - - /** - * Create video control container - * @memberOf Widget - * @instance - * @return {HTMLSpanElement} - The dom element icon for video control - */ - createVideoControl: function () { - - const item = document.createElement( 'span' ); - item.style.display = 'none'; - item.show = function () { - - item.style.display = ''; - - }; - - item.hide = function () { - - item.style.display = 'none'; - item.controlButton.paused = true; - item.controlButton.update(); - - }; - - item.controlButton = this.createVideoControlButton(); - item.seekBar = this.createVideoControlSeekbar(); - - item.appendChild( item.controlButton ); - item.appendChild( item.seekBar ); - - item.dispose = function () { - - item.removeChild( item.controlButton ); - item.removeChild( item.seekBar ); - - item.controlButton.dispose(); - item.controlButton = null; - - item.seekBar.dispose(); - item.seekBar = null; - - }; - - this.addEventListener( 'video-control-show', item.show ); - this.addEventListener( 'video-control-hide', item.hide ); - - return item; - - }, - - /** - * Create video control button - * @memberOf Widget - * @instance - * @return {HTMLSpanElement} - The dom element icon for video control - * @fires Widget#panolens-viewer-handler - */ - createVideoControlButton: function () { - - const scope = this; - - function onTap ( event ) { - - event.preventDefault(); - event.stopPropagation(); - - /** - * Viewer handler event - * @type {object} - * @event Widget#panolens-viewer-handler - * @property {string} method - 'toggleVideoPlay' function call on Viewer - */ - scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'toggleVideoPlay', data: !this.paused } ); - - this.paused = !this.paused; - - item.update(); - - } - const item = this.createCustomItem( { - - style: { - - float: 'left', - backgroundImage: 'url("' + DataImage.VideoPlay + '")' - - }, - - onTap: onTap - - } ); - - item.paused = true; - - item.update = function ( paused ) { - - this.paused = paused !== undefined ? paused : this.paused; - - this.style.backgroundImage = 'url("' + ( this.paused - ? DataImage.VideoPlay - : DataImage.VideoPause ) + '")'; - - }; - - return item; - - }, - - /** - * Create video seekbar - * @memberOf Widget - * @instance - * @return {HTMLSpanElement} - The dom element icon for video seekbar - * @fires Widget#panolens-viewer-handler - */ - createVideoControlSeekbar: function () { - - let scope = this, item, progressElement, progressElementControl, - isDragging = false, mouseX, percentageNow, percentageNext; - - progressElement = document.createElement( 'div' ); - progressElement.style.width = '0%'; - progressElement.style.height = '100%'; - progressElement.style.backgroundColor = '#fff'; - - progressElementControl = document.createElement( 'div' ); - progressElementControl.style.float = 'right'; - progressElementControl.style.width = '14px'; - progressElementControl.style.height = '14px'; - progressElementControl.style.transform = 'translate(7px, -5px)'; - progressElementControl.style.borderRadius = '50%'; - progressElementControl.style.backgroundColor = '#ddd'; - - progressElementControl.addEventListener( 'mousedown', onMouseDown, { passive: true } ); - progressElementControl.addEventListener( 'touchstart', onMouseDown, { passive: true } ); - - function onMouseDown ( event ) { - - event.stopPropagation(); - - isDragging = true; - - mouseX = event.clientX || ( event.changedTouches && event.changedTouches[0].clientX ); - - percentageNow = parseInt( progressElement.style.width ) / 100; - - addControlListeners(); - } - - function onVideoControlDrag ( event ) { - - if( isDragging ){ - - const clientX = event.clientX || ( event.changedTouches && event.changedTouches[0].clientX ); - - percentageNext = ( clientX - mouseX ) / item.clientWidth; - - percentageNext = percentageNow + percentageNext; - - percentageNext = percentageNext > 1 ? 1 : ( ( percentageNext < 0 ) ? 0 : percentageNext ); - - item.setProgress ( percentageNext ); - - /** - * Viewer handler event - * @type {object} - * @event Widget#panolens-viewer-handler - * @property {string} method - 'setVideoCurrentTime' function call on Viewer - * @property {number} data - Percentage of current video. Range from 0.0 to 1.0 - */ - scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'setVideoCurrentTime', data: percentageNext } ); - - } - - } - - function onVideoControlStop ( event ) { - - event.stopPropagation(); - - isDragging = false; - - removeControlListeners(); - - } - - function addControlListeners () { - - scope.container.addEventListener( 'mousemove', onVideoControlDrag, { passive: true } ); - scope.container.addEventListener( 'mouseup', onVideoControlStop, { passive: true } ); - scope.container.addEventListener( 'touchmove', onVideoControlDrag, { passive: true } ); - scope.container.addEventListener( 'touchend', onVideoControlStop, { passive: true } ); - - - } - - function removeControlListeners () { - - scope.container.removeEventListener( 'mousemove', onVideoControlDrag, false ); - scope.container.removeEventListener( 'mouseup', onVideoControlStop, false ); - scope.container.removeEventListener( 'touchmove', onVideoControlDrag, false ); - scope.container.removeEventListener( 'touchend', onVideoControlStop, false ); - - } - - function onTap ( event ) { - - event.preventDefault(); - event.stopPropagation(); - - if ( event.target === progressElementControl ) { return; } - - const percentage = ( event.changedTouches && event.changedTouches.length > 0 ) - ? ( event.changedTouches[0].pageX - event.target.getBoundingClientRect().left ) / this.clientWidth - : event.offsetX / this.clientWidth; - - /** - * Viewer handler event - * @type {object} - * @property {string} method - 'setVideoCurrentTime' function call on Viewer - * @property {number} data - Percentage of current video. Range from 0.0 to 1.0 - */ - scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'setVideoCurrentTime', data: percentage } ); - - item.setProgress( event.offsetX / this.clientWidth ); - - } - function onDispose () { - - removeControlListeners(); - progressElement = null; - progressElementControl = null; - - } - - progressElement.appendChild( progressElementControl ); - - item = this.createCustomItem( { - - style: { - - float: 'left', - width: '30%', - height: '4px', - marginTop: '20px', - backgroundColor: 'rgba(188,188,188,0.8)' - - }, - - onTap: onTap, - onDispose: onDispose - - } ); - - item.appendChild( progressElement ); - - item.setProgress = function( percentage ) { - - progressElement.style.width = percentage * 100 + '%'; - - }; - - this.addEventListener( 'video-update', function ( event ) { - - item.setProgress( event.percentage ); - - } ); - - item.progressElement = progressElement; - item.progressElementControl = progressElementControl; - - return item; - - }, - - /** - * Create menu item - * @param {string} title - Title to display - * @memberOf Widget - * @instance - * @return {HTMLElement} - An anchor tag element - */ - createMenuItem: function ( title ) { - - const scope = this; - const item = document.createElement( 'a' ); - item.textContent = title; - item.style.display = 'block'; - item.style.padding = '10px'; - item.style.textDecoration = 'none'; - item.style.cursor = 'pointer'; - item.style.pointerEvents = 'auto'; - item.style.transition = this.DEFAULT_TRANSITION; - - item.slide = function ( right ) { - - this.style.transform = 'translateX(' + ( right ? '' : '-' ) + '100%)'; - - }; - - item.unslide = function () { - - this.style.transform = 'translateX(0)'; - - }; - - item.setIcon = function ( url ) { - - if ( this.icon ) { - - this.icon.style.backgroundImage = 'url(' + url + ')'; - - } - - }; - - item.setSelectionTitle = function ( title ) { - - if ( this.selection ) { - - this.selection.textContent = title; - - } - - }; - - item.addSelection = function ( name ) { - - const selection = document.createElement( 'span' ); - selection.style.fontSize = '13px'; - selection.style.fontWeight = '300'; - selection.style.float = 'right'; - - this.selection = selection; - this.setSelectionTitle( name ); - this.appendChild( selection ); - - return this; - - }; - - item.addIcon = function ( url = DataImage.ChevronRight, left = false, flip = false ) { - - const element = document.createElement( 'span' ); - element.style.float = left ? 'left' : 'right'; - element.style.width = '17px'; - element.style.height = '17px'; - element.style[ 'margin' + ( left ? 'Right' : 'Left' ) ] = '12px'; - element.style.backgroundSize = 'cover'; - - if ( flip ) { - - element.style.transform = 'rotateZ(180deg)'; - - } - - this.icon = element; - this.setIcon( url ); - this.appendChild( element ); - - return this; - - }; - - item.addSubMenu = function ( title, items ) { - - this.subMenu = scope.createSubMenu( title, items ); - - return this; - - }; - - item.addEventListener( 'mouseenter', function () { - - this.style.backgroundColor = '#e0e0e0'; - - }, false ); - - item.addEventListener( 'mouseleave', function () { - - this.style.backgroundColor = '#fafafa'; - - }, false ); - - return item; - - }, - - /** - * Create menu item header - * @param {string} title - Title to display - * @memberOf Widget - * @instance - * @return {HTMLElement} - An anchor tag element - */ - createMenuItemHeader: function ( title ) { - - const header = this.createMenuItem( title ); - - header.style.borderBottom = '1px solid #333'; - header.style.paddingBottom = '15px'; - - return header; - - }, - - /** - * Create main menu - * @param {array} menus - Menu array list - * @memberOf Widget - * @instance - * @return {HTMLElement} - A span element - */ - createMainMenu: function ( menus ) { - - let scope = this, menu = this.createMenu(); - - menu._width = 200; - menu.changeSize( menu._width ); - - function onTap ( event ) { - - event.preventDefault(); - event.stopPropagation(); - - let mainMenu = scope.mainMenu, subMenu = this.subMenu; - - function onNextTick () { - - mainMenu.changeSize( subMenu.clientWidth ); - subMenu.show(); - subMenu.unslideAll(); - - } - - mainMenu.hide(); - mainMenu.slideAll(); - mainMenu.parentElement.appendChild( subMenu ); - - scope.activeMainItem = this; - scope.activeSubMenu = subMenu; - - window.requestAnimationFrame( onNextTick ); - - } - for ( var i = 0; i < menus.length; i++ ) { - - var item = menu.addItem( menus[ i ].title ); - - item.style.paddingLeft = '20px'; - - item.addIcon() - .addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', onTap, false ); - - if ( menus[ i ].subMenu && menus[ i ].subMenu.length > 0 ) { - - var title = menus[ i ].subMenu[ 0 ].title; - - item.addSelection( title ) - .addSubMenu( menus[ i ].title, menus[ i ].subMenu ); - - } - - } - - return menu; - - }, - - /** - * Create sub menu - * @param {string} title - Sub menu title - * @param {array} items - Item array list - * @memberOf Widget - * @instance - * @return {HTMLElement} - A span element - */ - createSubMenu: function ( title, items ) { - - let scope = this, menu, subMenu = this.createMenu(); - - subMenu.items = items; - subMenu.activeItem = null; - - function onTap ( event ) { - - event.preventDefault(); - event.stopPropagation(); - - menu = scope.mainMenu; - menu.changeSize( menu._width ); - menu.unslideAll(); - menu.show(); - subMenu.slideAll( true ); - subMenu.hide(); - - if ( this.type !== 'header' ) { - - subMenu.setActiveItem( this ); - scope.activeMainItem.setSelectionTitle( this.textContent ); - - if ( this.handler ) { this.handler(); } - - } - - } - - subMenu.addHeader( title ).addIcon( undefined, true, true ).addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', onTap, false ); - - for ( let i = 0; i < items.length; i++ ) { - - const item = subMenu.addItem( items[ i ].title ); - - item.style.fontWeight = 300; - item.handler = items[ i ].handler; - item.addIcon( ' ', true ); - item.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', onTap, false ); - - if ( !subMenu.activeItem ) { - - subMenu.setActiveItem( item ); - - } - - } - - subMenu.slideAll( true ); - - return subMenu; - - }, - - /** - * Create general menu - * @memberOf Widget - * @instance - * @return {HTMLElement} - A span element - */ - createMenu: function () { - - const scope = this; - const menu = document.createElement( 'span' ); - const style = menu.style; - - style.padding = '5px 0'; - style.position = 'fixed'; - style.bottom = '100%'; - style.right = '14px'; - style.backgroundColor = '#fafafa'; - style.fontFamily = 'Helvetica Neue'; - style.fontSize = '14px'; - style.visibility = 'hidden'; - style.opacity = 0; - style.boxShadow = '0 0 12pt rgba(0,0,0,0.25)'; - style.borderRadius = '2px'; - style.overflow = 'hidden'; - style.willChange = 'width, height, opacity'; - style.pointerEvents = 'auto'; - style.transition = this.DEFAULT_TRANSITION; - - menu.visible = false; - - menu.changeSize = function ( width, height ) { - - if ( width ) { - - this.style.width = width + 'px'; - - } - - if ( height ) { - - this.style.height = height + 'px'; - - } - - }; - - menu.show = function () { - - this.style.opacity = 1; - this.style.visibility = 'visible'; - this.visible = true; - - }; - - menu.hide = function () { - - this.style.opacity = 0; - this.style.visibility = 'hidden'; - this.visible = false; - - }; - - menu.toggle = function () { - - if ( this.visible ) { - - this.hide(); - - } else { - - this.show(); - - } - - }; - - menu.slideAll = function ( right ) { - - for ( let i = 0; i < menu.children.length; i++ ){ - - if ( menu.children[ i ].slide ) { - - menu.children[ i ].slide( right ); - - } - - } - - }; - - menu.unslideAll = function () { - - for ( let i = 0; i < menu.children.length; i++ ){ - - if ( menu.children[ i ].unslide ) { - - menu.children[ i ].unslide(); - - } - - } - - }; - - menu.addHeader = function ( title ) { - - const header = scope.createMenuItemHeader( title ); - header.type = 'header'; - - this.appendChild( header ); - - return header; - - }; - - menu.addItem = function ( title ) { - - const item = scope.createMenuItem( title ); - item.type = 'item'; - - this.appendChild( item ); - - return item; - - }; - - menu.setActiveItem = function ( item ) { - - if ( this.activeItem ) { - - this.activeItem.setIcon( ' ' ); - - } - - item.setIcon( DataImage.Check ); - - this.activeItem = item; - - }; - - menu.addEventListener( 'mousemove', this.PREVENT_EVENT_HANDLER, true ); - menu.addEventListener( 'mouseup', this.PREVENT_EVENT_HANDLER, true ); - menu.addEventListener( 'mousedown', this.PREVENT_EVENT_HANDLER, true ); - - return menu; - - }, - - /** - * Create custom item element - * @memberOf Widget - * @instance - * @return {HTMLSpanElement} - The dom element icon - */ - createCustomItem: function ( options = {} ) { - - const scope = this; - const item = options.element || document.createElement( 'span' ); - const { onDispose } = options; - - item.style.cursor = 'pointer'; - item.style.float = 'right'; - item.style.width = '44px'; - item.style.height = '100%'; - item.style.backgroundSize = '60%'; - item.style.backgroundRepeat = 'no-repeat'; - item.style.backgroundPosition = 'center'; - item.style.webkitUserSelect = - item.style.MozUserSelect = - item.style.userSelect = 'none'; - item.style.position = 'relative'; - item.style.pointerEvents = 'auto'; - - // White glow on icon - item.addEventListener( scope.TOUCH_ENABLED ? 'touchstart' : 'mouseenter', function() { - item.style.filter = - item.style.webkitFilter = 'drop-shadow(0 0 5px rgba(255,255,255,1))'; - }, { passive: true }); - item.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'mouseleave', function() { - item.style.filter = - item.style.webkitFilter = ''; - }, { passive: true }); - - this.mergeStyleOptions( item, options.style ); - - if ( options.onTap ) { - - item.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', options.onTap, false ); - - } - - item.dispose = function () { - - item.removeEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', options.onTap, false ); - - if ( onDispose ) { options.onDispose(); } - - }; - - return item; - - }, - - /** - * Merge item css style - * @param {HTMLElement} element - The element to be merged with style - * @param {object} options - The style options - * @memberOf Widget - * @instance - * @return {HTMLElement} - The same element with merged styles - */ - mergeStyleOptions: function ( element, options = {} ) { - - for ( let property in options ){ - - if ( options.hasOwnProperty( property ) ) { - - element.style[ property ] = options[ property ]; - - } - - } - - return element; - - }, - - /** - * Dispose widgets by detaching dom elements from container - * @memberOf Widget - * @instance - */ - dispose: function () { - - if ( this.barElement ) { - this.container.removeChild( this.barElement ); - this.barElement.dispose(); - this.barElement = null; - - } - - } - - } ); - - /** - * @classdesc Base Panorama - * @constructor - * @param {THREE.Geometry} geometry - The geometry for this panorama - * @param {THREE.Material} material - The material for this panorama - */ - function Panorama ( geometry, material ) { - - THREE.Mesh.call( this, geometry, material ); - - this.type = 'panorama'; - - this.ImageQualityLow = 1; - this.ImageQualityFair = 2; - this.ImageQualityMedium = 3; - this.ImageQualityHigh = 4; - this.ImageQualitySuperHigh = 5; - - this.animationDuration = 1000; - - this.defaultInfospotSize = 350; - - this.container = undefined; - - this.loaded = false; - - this.linkedSpots = []; - - this.isInfospotVisible = false; - - this.linkingImageURL = undefined; - this.linkingImageScale = undefined; - - this.material.side = THREE.BackSide; - this.material.opacity = 0; - - this.scale.x *= -1; - this.renderOrder = -1; - - this.active = false; - - this.infospotAnimation = new Tween.Tween( this ).to( {}, this.animationDuration / 2 ); - - this.addEventListener( 'load', this.fadeIn.bind( this ) ); - this.addEventListener( 'panolens-container', this.setContainer.bind( this ) ); - this.addEventListener( 'click', this.onClick.bind( this ) ); - - this.setupTransitions(); - - } - - Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { - - constructor: Panorama, - - /** - * Adding an object - * To counter the scale.x = -1, it will automatically add an - * empty object with inverted scale on x - * @memberOf Panorama - * @instance - * @param {THREE.Object3D} object - The object to be added - */ - add: function ( object ) { - - let invertedObject; - - if ( arguments.length > 1 ) { - - for ( var i = 0; i < arguments.length; i ++ ) { - - this.add( arguments[ i ] ); - - } - - return this; - - } - - // In case of infospots - if ( object instanceof Infospot ) { - - invertedObject = object; - - if ( object.dispatchEvent ) { - - const { container } = this; - - if ( container ) { object.dispatchEvent( { type: 'panolens-container', container } ); } - - object.dispatchEvent( { type: 'panolens-infospot-focus', method: function ( vector, duration, easing ) { - - /** - * Infospot focus handler event - * @type {object} - * @event Panorama#panolens-viewer-handler - * @property {string} method - Viewer function name - * @property {*} data - The argument to be passed into the method - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'tweenControlCenter', data: [ vector, duration, easing ] } ); - - - }.bind( this ) } ); - } - - } else { - - // Counter scale.x = -1 effect - invertedObject = new THREE.Object3D(); - invertedObject.scale.x = -1; - invertedObject.scalePlaceHolder = true; - invertedObject.add( object ); - - } - - THREE.Object3D.prototype.add.call( this, invertedObject ); - - }, - - load: function () { - - this.onLoad(); - - }, - - /** - * Click event handler - * @param {object} event - Click event - * @memberOf Panorama - * @instance - * @fires Infospot#dismiss - */ - onClick: function ( event ) { - - if ( event.intersects && event.intersects.length === 0 ) { - - this.traverse( function ( object ) { - - /** - * Dimiss event - * @type {object} - * @event Infospot#dismiss - */ - object.dispatchEvent( { type: 'dismiss' } ); - - } ); - - } - - }, - - /** - * Set container of this panorama - * @param {HTMLElement|object} data - Data with container information - * @memberOf Panorama - * @instance - * @fires Infospot#panolens-container - */ - setContainer: function ( data ) { - - let container; - - if ( data instanceof HTMLElement ) { - - container = data; - - } else if ( data && data.container ) { - - container = data.container; - - } - - if ( container ) { - - this.children.forEach( function ( child ) { - - if ( child instanceof Infospot && child.dispatchEvent ) { - - /** - * Set container event - * @type {object} - * @event Infospot#panolens-container - * @property {HTMLElement} container - The container of this panorama - */ - child.dispatchEvent( { type: 'panolens-container', container: container } ); - - } - - } ); - - this.container = container; - - } - - }, - - /** - * This will be called when panorama is loaded - * @memberOf Panorama - * @instance - * @fires Panorama#load - */ - onLoad: function () { - - this.loaded = true; - - /** - * Load panorama event - * @type {object} - * @event Panorama#load - */ - this.dispatchEvent( { type: 'load' } ); - - }, - - /** - * This will be called when panorama is in progress - * @memberOf Panorama - * @instance - * @fires Panorama#progress - */ - onProgress: function ( progress ) { - - /** - * Loading panorama progress event - * @type {object} - * @event Panorama#progress - * @property {object} progress - The progress object containing loaded and total amount - */ - this.dispatchEvent( { type: 'progress', progress: progress } ); - - }, - - /** - * This will be called when panorama loading has error - * @memberOf Panorama - * @instance - * @fires Panorama#error - */ - onError: function () { - - /** - * Loading panorama error event - * @type {object} - * @event Panorama#error - */ - this.dispatchEvent( { type: 'error' } ); - - }, - - /** - * Get zoom level based on window width - * @memberOf Panorama - * @instance - * @return {number} zoom level indicating image quality - */ - getZoomLevel: function () { - - let zoomLevel; - - if ( window.innerWidth <= 800 ) { - - zoomLevel = this.ImageQualityFair; - - } else if ( window.innerWidth > 800 && window.innerWidth <= 1280 ) { - - zoomLevel = this.ImageQualityMedium; - - } else if ( window.innerWidth > 1280 && window.innerWidth <= 1920 ) { - - zoomLevel = this.ImageQualityHigh; - - } else if ( window.innerWidth > 1920 ) { - - zoomLevel = this.ImageQualitySuperHigh; - - } else { - - zoomLevel = this.ImageQualityLow; - - } - - return zoomLevel; - - }, - - /** - * Update texture of a panorama - * @memberOf Panorama - * @instance - * @param {THREE.Texture} texture - Texture to be updated - */ - updateTexture: function ( texture ) { - - this.material.map = texture; - this.material.needsUpdate = true; - - }, - - /** - * Toggle visibility of infospots in this panorama - * @param {boolean} isVisible - Visibility of infospots - * @param {number} delay - Delay in milliseconds to change visibility - * @memberOf Panorama - * @instance - * @fires Panorama#infospot-animation-complete - */ - toggleInfospotVisibility: function ( isVisible, delay ) { - - delay = ( delay !== undefined ) ? delay : 0; - - const visible = ( isVisible !== undefined ) ? isVisible : ( this.isInfospotVisible ? false : true ); - - this.traverse( function ( object ) { - - if ( object instanceof Infospot ) { - - if ( visible ) { - - object.show( delay ); - - } else { - - object.hide( delay ); - - } - - } - - } ); - - this.isInfospotVisible = visible; - - // Animation complete event - this.infospotAnimation.onComplete( function () { - - /** - * Complete toggling infospot visibility - * @event Panorama#infospot-animation-complete - * @type {object} - */ - this.dispatchEvent( { type: 'infospot-animation-complete', visible: visible } ); - - }.bind( this ) ).delay( delay ).start(); - - }, - - /** - * Set image of this panorama's linking infospot - * @memberOf Panorama - * @instance - * @param {string} url - Url to the image asset - * @param {number} scale - Scale factor of the infospot - */ - setLinkingImage: function ( url, scale ) { - - this.linkingImageURL = url; - this.linkingImageScale = scale; - - }, - - /** - * Link one-way panorama - * @param {Panorama} pano - The panorama to be linked to - * @param {THREE.Vector3} position - The position of infospot which navigates to the pano - * @param {number} [imageScale=300] - Image scale of linked infospot - * @param {string} [imageSrc=DataImage.Arrow] - The image source of linked infospot - * @memberOf Panorama - * @instance - */ - link: function ( pano, position, imageScale, imageSrc ) { - - let scale, img; - - this.visible = true; - - if ( !position ) { - - console.warn( 'Please specify infospot position for linking' ); - - return; - - } - - // Infospot scale - if ( imageScale !== undefined ) { - - scale = imageScale; - - } else if ( pano.linkingImageScale !== undefined ) { - - scale = pano.linkingImageScale; - - } else { - - scale = 300; - - } - - - // Infospot image - if ( imageSrc ) { - - img = imageSrc; - - } else if ( pano.linkingImageURL ) { - - img = pano.linkingImageURL; - - } else { - - img = DataImage.Arrow; - - } - - // Creates a new infospot - const spot = new Infospot( scale, img ); - spot.position.copy( position ); - spot.toPanorama = pano; - spot.addEventListener( 'click', function () { - - /** - * Viewer handler event - * @type {object} - * @event Panorama#panolens-viewer-handler - * @property {string} method - Viewer function name - * @property {*} data - The argument to be passed into the method - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'setPanorama', data: pano } ); - - }.bind( this ) ); - - this.linkedSpots.push( spot ); - - this.add( spot ); - - this.visible = false; - - }, - - reset: function () { - - this.children.length = 0; - - }, - - setupTransitions: function () { - - this.fadeInAnimation = new Tween.Tween( this.material ) - .easing( Tween.Easing.Quartic.Out ) - .onStart( function () { - - this.visible = true; - // this.material.visible = true; - - /** - * Enter panorama fade in start event - * @event Panorama#enter-fade-start - * @type {object} - */ - this.dispatchEvent( { type: 'enter-fade-start' } ); - - }.bind( this ) ); - - this.fadeOutAnimation = new Tween.Tween( this.material ) - .easing( Tween.Easing.Quartic.Out ) - .onComplete( function () { - - this.visible = false; - // this.material.visible = true; - - /** - * Leave panorama complete event - * @event Panorama#leave-complete - * @type {object} - */ - this.dispatchEvent( { type: 'leave-complete' } ); - - }.bind( this ) ); - - this.enterTransition = new Tween.Tween( this ) - .easing( Tween.Easing.Quartic.Out ) - .onComplete( function () { - - /** - * Enter panorama and animation complete event - * @event Panorama#enter-complete - * @type {object} - */ - this.dispatchEvent( { type: 'enter-complete' } ); - - }.bind ( this ) ) - .start(); - - this.leaveTransition = new Tween.Tween( this ) - .easing( Tween.Easing.Quartic.Out ); - - }, - - onFadeAnimationUpdate: function () { - - const alpha = this.material.opacity; - const { uniforms } = this.material; - - if ( uniforms && uniforms.opacity ) { - uniforms.opacity.value = alpha; - } - - }, - - /** - * Start fading in animation - * @memberOf Panorama - * @instance - * @fires Panorama#enter-fade-complete - */ - fadeIn: function ( duration ) { - - duration = duration >= 0 ? duration : this.animationDuration; - - this.fadeOutAnimation.stop(); - this.fadeInAnimation - .to( { opacity: 1 }, duration ) - .onUpdate( this.onFadeAnimationUpdate.bind( this ) ) - .onComplete( function () { - - this.toggleInfospotVisibility( true, duration / 2 ); - - /** - * Enter panorama fade complete event - * @event Panorama#enter-fade-complete - * @type {object} - */ - this.dispatchEvent( { type: 'enter-fade-complete' } ); - - }.bind( this ) ) - .start(); - - }, - - /** - * Start fading out animation - * @memberOf Panorama - * @instance - */ - fadeOut: function ( duration ) { - - duration = duration >= 0 ? duration : this.animationDuration; - - this.fadeInAnimation.stop(); - this.fadeOutAnimation - .to( { opacity: 0 }, duration ) - .onUpdate( this.onFadeAnimationUpdate.bind( this ) ) - .start(); - - }, - - /** - * This will be called when entering a panorama - * @memberOf Panorama - * @instance - * @fires Panorama#enter - * @fires Panorama#enter-start - */ - onEnter: function () { - - const duration = this.animationDuration; - - this.leaveTransition.stop(); - this.enterTransition - .to( {}, duration ) - .onStart( function () { - - /** - * Enter panorama and animation starting event - * @event Panorama#enter-start - * @type {object} - */ - this.dispatchEvent( { type: 'enter-start' } ); - - if ( this.loaded ) { - - this.fadeIn( duration ); - - } else { - - this.load(); - - } - - }.bind( this ) ) - .start(); - - /** - * Enter panorama event - * @event Panorama#enter - * @type {object} - */ - this.dispatchEvent( { type: 'enter' } ); - - this.children.forEach( child => { - - child.dispatchEvent( { type: 'panorama-enter' } ); - - } ); - - this.active = true; - - }, - - /** - * This will be called when leaving a panorama - * @memberOf Panorama - * @instance - * @fires Panorama#leave - */ - onLeave: function () { - - const duration = this.animationDuration; - - this.enterTransition.stop(); - this.leaveTransition - .to( {}, duration ) - .onStart( function () { - - /** - * Leave panorama and animation starting event - * @event Panorama#leave-start - * @type {object} - */ - this.dispatchEvent( { type: 'leave-start' } ); - - this.fadeOut( duration ); - this.toggleInfospotVisibility( false ); - - }.bind( this ) ) - .start(); - - /** - * Leave panorama event - * @event Panorama#leave - * @type {object} - */ - this.dispatchEvent( { type: 'leave' } ); - - this.children.forEach( child => { - - child.dispatchEvent( { type: 'panorama-leave' } ); - - } ); - - this.active = false; - - }, - - /** - * Dispose panorama - * @memberOf Panorama - * @instance - */ - dispose: function () { - - this.infospotAnimation.stop(); - this.fadeInAnimation.stop(); - this.fadeOutAnimation.stop(); - this.enterTransition.stop(); - this.leaveTransition.stop(); - - /** - * On panorama dispose handler - * @type {object} - * @event Panorama#panolens-viewer-handler - * @property {string} method - Viewer function name - * @property {*} data - The argument to be passed into the method - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onPanoramaDispose', data: this } ); - - // recursive disposal on 3d objects - function recursiveDispose ( object ) { - - const { geometry, material } = object; - - for ( var i = object.children.length - 1; i >= 0; i-- ) { - - recursiveDispose( object.children[i] ); - object.remove( object.children[i] ); - - } - - if ( object instanceof Infospot ) { - - object.dispose(); - - } - - if ( geometry ) { geometry.dispose(); object.geometry = null; } - if ( material ) { material.dispose(); object.material = null; } - - } - - recursiveDispose( this ); - - if ( this.parent ) { - - this.parent.remove( this ); - - } - - } - - } ); - - /** - * @classdesc Equirectangular based image panorama - * @constructor - * @param {string} image - Image url or HTMLImageElement - */ - function ImagePanorama ( image, _geometry, _material ) { - - const radius = 5000; - const geometry = _geometry || new THREE.SphereBufferGeometry( radius, 60, 40 ); - const material = _material || new THREE.MeshBasicMaterial( { opacity: 0, transparent: true } ); - - Panorama.call( this, geometry, material ); - - this.src = image; - this.radius = radius; - - } - - ImagePanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { - - constructor: ImagePanorama, - - /** - * Load image asset - * @param {*} src - Url or image element - * @memberOf ImagePanorama - * @instance - */ - load: function ( src ) { - - src = src || this.src; - - if ( !src ) { - - console.warn( 'Image source undefined' ); - - return; - - } else if ( typeof src === 'string' ) { - - TextureLoader.load( src, this.onLoad.bind( this ), this.onProgress.bind( this ), this.onError.bind( this ) ); - - } else if ( src instanceof HTMLImageElement ) { - - this.onLoad( new THREE.Texture( src ) ); - - } - - }, - - /** - * This will be called when image is loaded - * @param {THREE.Texture} texture - Texture to be updated - * @memberOf ImagePanorama - * @instance - */ - onLoad: function ( texture ) { - - texture.minFilter = texture.magFilter = THREE.LinearFilter; - texture.needsUpdate = true; - - this.updateTexture( texture ); - - window.requestAnimationFrame( Panorama.prototype.onLoad.bind( this ) ); - - }, - - /** - * Reset - * @memberOf ImagePanorama - * @instance - */ - reset: function () { - - Panorama.prototype.reset.call( this ); - - }, - - /** - * Dispose - * @memberOf ImagePanorama - * @instance - */ - dispose: function () { - - const { material: { map } } = this; - - // Release cached image - THREE.Cache.remove( this.src ); - - if ( map ) { map.dispose(); } - - Panorama.prototype.dispose.call( this ); - - } - - } ); - - /** - * @classdesc Empty panorama - * @constructor - */ - function EmptyPanorama () { - - const geometry = new THREE.BufferGeometry(); - const material = new THREE.MeshBasicMaterial( { color: 0x000000, opacity: 0, transparent: true } ); - - geometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array(), 1 ) ); - - Panorama.call( this, geometry, material ); - - } - - EmptyPanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { - - constructor: EmptyPanorama - - } ); - - /** - * @classdesc Cubemap-based panorama - * @constructor - * @param {array} images - Array of 6 urls to images, one for each side of the CubeTexture. The urls should be specified in the following order: pos-x, neg-x, pos-y, neg-y, pos-z, neg-z - */ - function CubePanorama ( images = [] ){ - - const edgeLength = 10000; - const shader = Object.assign( {}, THREE.ShaderLib[ 'cube' ] ); - const geometry = new THREE.BoxBufferGeometry( edgeLength, edgeLength, edgeLength ); - const material = new THREE.ShaderMaterial( { - - fragmentShader: shader.fragmentShader, - vertexShader: shader.vertexShader, - uniforms: shader.uniforms, - side: THREE.BackSide, - transparent: true - - } ); - - Panorama.call( this, geometry, material ); - - this.images = images; - this.edgeLength = edgeLength; - this.material.uniforms.opacity.value = 0; - - } - - CubePanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { - - constructor: CubePanorama, - - /** - * Load 6 images and bind listeners - * @memberOf CubePanorama - * @instance - */ - load: function () { - - CubeTextureLoader.load( - - this.images, - - this.onLoad.bind( this ), - this.onProgress.bind( this ), - this.onError.bind( this ) - - ); - - }, - - /** - * This will be called when 6 textures are ready - * @param {THREE.CubeTexture} texture - Cube texture - * @memberOf CubePanorama - * @instance - */ - onLoad: function ( texture ) { - - this.material.uniforms[ 'tCube' ].value = texture; - - Panorama.prototype.onLoad.call( this ); - - }, - - /** - * Dispose - * @memberOf CubePanorama - * @instance - */ - dispose: function () { - - const { value } = this.material.uniforms.tCube; - - this.images.forEach( ( image ) => { THREE.Cache.remove( image ); } ); - - if ( value instanceof THREE.CubeTexture ) { - - value.dispose(); - - } - - Panorama.prototype.dispose.call( this ); - - } - - } ); - - /** - * @classdesc Basic panorama with 6 pre-defined grid images - * @constructor - */ - function BasicPanorama () { - - const images = []; - - for ( let i = 0; i < 6; i++ ) { - - images.push( DataImage.WhiteTile ); - - } - - CubePanorama.call( this, images ); - - } - - BasicPanorama.prototype = Object.assign( Object.create( CubePanorama.prototype ), { - - constructor: BasicPanorama - - } ); - - /** - * @classdesc Video Panorama - * @constructor - * @param {string} src - Equirectangular video url - * @param {object} [options] - Option for video settings - * @param {HTMLElement} [options.videoElement] - HTML5 video element contains the video - * @param {boolean} [options.loop=true] - Specify if the video should loop in the end - * @param {boolean} [options.muted=true] - Mute the video or not. Need to be true in order to autoplay on some browsers - * @param {boolean} [options.autoplay=false] - Specify if the video should auto play - * @param {boolean} [options.playsinline=true] - Specify if video should play inline for iOS. If you want it to auto play inline, set both autoplay and muted options to true - * @param {string} [options.crossOrigin="anonymous"] - Sets the cross-origin attribute for the video, which allows for cross-origin videos in some browsers (Firefox, Chrome). Set to either "anonymous" or "use-credentials". - * @param {number} [radius=5000] - The minimum radius for this panoram - */ - function VideoPanorama ( src, options = {} ) { - - const radius = 5000; - const geometry = new THREE.SphereBufferGeometry( radius, 60, 40 ); - const material = new THREE.MeshBasicMaterial( { opacity: 0, transparent: true } ); - - Panorama.call( this, geometry, material ); - - this.src = src; - - this.options = { - - videoElement: document.createElement( 'video' ), - loop: true, - muted: true, - autoplay: false, - playsinline: true, - crossOrigin: 'anonymous' - - }; - - Object.assign( this.options, options ); - - this.videoElement = this.options.videoElement; - this.videoProgress = 0; - this.radius = radius; - - this.addEventListener( 'leave', this.pauseVideo.bind( this ) ); - this.addEventListener( 'enter-fade-start', this.resumeVideoProgress.bind( this ) ); - this.addEventListener( 'video-toggle', this.toggleVideo.bind( this ) ); - this.addEventListener( 'video-time', this.setVideoCurrentTime.bind( this ) ); - - } - VideoPanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { - - constructor: VideoPanorama, - - isMobile: function () { - - let check = false; - (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})( window.navigator.userAgent || window.navigator.vendor || window.opera ); - return check; - - }, - - /** - * Load video panorama - * @memberOf VideoPanorama - * @instance - * @fires Panorama#panolens-viewer-handler - */ - load: function () { - - const { muted, loop, autoplay, playsinline, crossOrigin } = this.options; - const video = this.videoElement; - const material = this.material; - const onProgress = this.onProgress.bind( this ); - const onLoad = this.onLoad.bind( this ); - - video.loop = loop; - video.autoplay = autoplay; - video.playsinline = playsinline; - video.crossOrigin = crossOrigin; - video.muted = muted; - - if ( playsinline ) { - - video.setAttribute( 'playsinline', '' ); - video.setAttribute( 'webkit-playsinline', '' ); - - } - - const onloadeddata = function() { - - this.setVideoTexture( video ); - - if ( autoplay ) { - - /** - * Viewer handler event - * @type {object} - * @property {string} method - 'updateVideoPlayButton' - * @property {boolean} data - Pause video or not - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: false } ); - - } - - // For mobile silent autoplay - if ( this.isMobile() ) { - - video.pause(); - - if ( autoplay && muted ) { - - /** - * Viewer handler event - * @type {object} - * @property {string} method - 'updateVideoPlayButton' - * @property {boolean} data - Pause video or not - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: false } ); - - } else { - - /** - * Viewer handler event - * @type {object} - * @property {string} method - 'updateVideoPlayButton' - * @property {boolean} data - Pause video or not - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: true } ); - - } - - } - - const loaded = () => { - - // Fix for threejs r89 delayed update - material.map.needsUpdate = true; - - onProgress( { loaded: 1, total: 1 } ); - onLoad(); - - }; - - window.requestAnimationFrame( loaded ); - - }; - - /** - * Ready state of the audio/video element - * 0 = HAVE_NOTHING - no information whether or not the audio/video is ready - * 1 = HAVE_METADATA - metadata for the audio/video is ready - * 2 = HAVE_CURRENT_DATA - data for the current playback position is available, but not enough data to play next frame/millisecond - * 3 = HAVE_FUTURE_DATA - data for the current and at least the next frame is available - * 4 = HAVE_ENOUGH_DATA - enough data available to start playing - */ - if ( video.readyState > 2 ) { - - onloadeddata.call( this ); - - } else { - - if ( video.querySelectorAll( 'source' ).length === 0 ) { - - const source = document.createElement( 'source' ); - source.src = this.src; - video.appendChild( source ); - - } - - video.load(); - } - - video.addEventListener( 'loadeddata', onloadeddata.bind( this ) ); - - video.addEventListener( 'timeupdate', function () { - - this.videoProgress = video.duration >= 0 ? video.currentTime / video.duration : 0; - - /** - * Viewer handler event - * @type {object} - * @property {string} method - 'onVideoUpdate' - * @property {number} data - The percentage of video progress. Range from 0.0 to 1.0 - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onVideoUpdate', data: this.videoProgress } ); - - }.bind( this ) ); - - video.addEventListener( 'ended', function () { - - if ( !loop ) { - - this.resetVideo(); - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: true } ); - - } - - }.bind( this ), false ); - - }, - - /** - * Set video texture - * @memberOf VideoPanorama - * @instance - * @param {HTMLVideoElement} video - The html5 video element - * @fires Panorama#panolens-viewer-handler - */ - setVideoTexture: function ( video ) { - - if ( !video ) return; - - const videoTexture = new THREE.VideoTexture( video ); - videoTexture.minFilter = THREE.LinearFilter; - videoTexture.magFilter = THREE.LinearFilter; - videoTexture.format = THREE.RGBFormat; - - this.updateTexture( videoTexture ); - - }, - - /** - * Reset - * @memberOf VideoPanorama - * @instance - */ - reset: function () { - - this.videoElement = undefined; - - Panorama.prototype.reset.call( this ); - - }, - - /** - * Check if video is paused - * @memberOf VideoPanorama - * @instance - * @return {boolean} - is video paused or not - */ - isVideoPaused: function () { - - return this.videoElement.paused; - - }, - - /** - * Toggle video to play or pause - * @memberOf VideoPanorama - * @instance - */ - toggleVideo: function () { - - const video = this.videoElement; - - if ( !video ) { return; } - - video[ video.paused ? 'play' : 'pause' ](); - - }, - - /** - * Set video currentTime - * @memberOf VideoPanorama - * @instance - * @param {object} event - Event contains percentage. Range from 0.0 to 1.0 - */ - setVideoCurrentTime: function ( { percentage } ) { - - const video = this.videoElement; - - if ( video && !Number.isNaN( percentage ) && percentage !== 1 ) { - - video.currentTime = video.duration * percentage; - - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onVideoUpdate', data: percentage } ); - - } - - }, - - /** - * Play video - * @memberOf VideoPanorama - * @instance - * @fires VideoPanorama#play - * @fires VideoPanorama#play-error - */ - playVideo: function () { - - const video = this.videoElement; - const playVideo = this.playVideo.bind( this ); - const dispatchEvent = this.dispatchEvent.bind( this ); - const onSuccess = () => { - - /** - * Play event - * @type {object} - * @event VideoPanorama#play - * - */ - dispatchEvent( { type: 'play' } ); - - }; - const onError = ( error ) => { - - // Error playing video. Retry next frame. Possibly Waiting for user interaction - window.requestAnimationFrame( playVideo ); - - /** - * Play event - * @type {object} - * @event VideoPanorama#play-error - * - */ - dispatchEvent( { type: 'play-error', error } ); - - }; - - if ( video && video.paused ) { - - video.play().then( onSuccess ).catch( onError ); - - } - - }, - - /** - * Pause video - * @memberOf VideoPanorama - * @instance - * @fires VideoPanorama#pause - */ - pauseVideo: function () { - - const video = this.videoElement; - - if ( video && !video.paused ) { - - video.pause(); - - } - - /** - * Pause event - * @type {object} - * @event VideoPanorama#pause - * - */ - this.dispatchEvent( { type: 'pause' } ); - - }, - - /** - * Resume video - * @memberOf VideoPanorama - * @instance - */ - resumeVideoProgress: function () { - - const video = this.videoElement; - - if ( video.readyState >= 4 && video.autoplay && !this.isMobile() ) { - - this.playVideo(); - - /** - * Viewer handler event - * @type {object} - * @property {string} method - 'updateVideoPlayButton' - * @property {boolean} data - Pause video or not - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: false } ); - - } else { - - this.pauseVideo(); - - /** - * Viewer handler event - * @type {object} - * @property {string} method - 'updateVideoPlayButton' - * @property {boolean} data - Pause video or not - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: true } ); - - } - - this.setVideoCurrentTime( { percentage: this.videoProgress } ); - - }, - - /** - * Reset video at stating point - * @memberOf VideoPanorama - * @instance - */ - resetVideo: function () { - - const video = this.videoElement; - - if ( video ) { - - this.setVideoCurrentTime( { percentage: 0 } ); - - } - - }, - - /** - * Check if video is muted - * @memberOf VideoPanorama - * @instance - * @return {boolean} - is video muted or not - */ - isVideoMuted: function () { - - return this.videoElement.muted; - - }, - - /** - * Mute video - * @memberOf VideoPanorama - * @instance - */ - muteVideo: function () { - - const video = this.videoElement; - - if ( video && !video.muted ) { - - video.muted = true; - - } - - this.dispatchEvent( { type: 'volumechange' } ); - - }, - - /** - * Unmute video - * @memberOf VideoPanorama - * @instance - */ - unmuteVideo: function () { - - const video = this.videoElement; - - if ( video && this.isVideoMuted() ) { - - video.muted = false; - - } - - this.dispatchEvent( { type: 'volumechange' } ); - - }, - - /** - * Returns the video element - * @memberOf VideoPanorama - * @instance - * @returns {HTMLElement} - */ - getVideoElement: function () { - - return this.videoElement; - - }, - - /** - * Dispose video panorama - * @memberOf VideoPanorama - * @instance - */ - dispose: function () { - - const { material: { map } } = this; - - this.pauseVideo(); - - this.removeEventListener( 'leave', this.pauseVideo.bind( this ) ); - this.removeEventListener( 'enter-fade-start', this.resumeVideoProgress.bind( this ) ); - this.removeEventListener( 'video-toggle', this.toggleVideo.bind( this ) ); - this.removeEventListener( 'video-time', this.setVideoCurrentTime.bind( this ) ); - - if ( map ) { map.dispose(); } - - Panorama.prototype.dispose.call( this ); - - } - - } ); - - /** - * @classdesc Google Street View Loader - * @constructor - * @param {object} parameters - */ - function GoogleStreetviewLoader ( parameters = {} ) { - - this._parameters = parameters; - this._zoom = null; - this._panoId = null; - this._panoClient = new google.maps.StreetViewService(); - this._count = 0; - this._total = 0; - this._canvas = []; - this._ctx = []; - this._wc = 0; - this._hc = 0; - this.result = null; - this.rotation = 0; - this.copyright = ''; - this.onSizeChange = null; - this.onPanoramaLoad = null; - - this.levelsW = [ 1, 2, 4, 7, 13, 26 ]; - this.levelsH = [ 1, 1, 2, 4, 7, 13 ]; - - this.widths = [ 416, 832, 1664, 3328, 6656, 13312 ]; - this.heights = [ 416, 416, 832, 1664, 3328, 6656 ]; - - this.maxW = 6656; - this.maxH = 6656; - - let gl; - - try { - - const canvas = document.createElement( 'canvas' ); - - gl = canvas.getContext( 'experimental-webgl' ); - - if( !gl ) { - - gl = canvas.getContext( 'webgl' ); - - } - - } - catch ( error ) { - - } - - this.maxW = Math.max( gl.getParameter( gl.MAX_TEXTURE_SIZE ), this.maxW ); - this.maxH = Math.max( gl.getParameter( gl.MAX_TEXTURE_SIZE ), this.maxH ); - - } - - Object.assign( GoogleStreetviewLoader.prototype, { - - constructor: GoogleStreetviewLoader, - - /** - * Set progress - * @param {number} loaded - * @param {number} total - * @memberOf GoogleStreetviewLoader - * @instance - */ - setProgress: function ( loaded, total ) { - - if ( this.onProgress ) { - - this.onProgress( { loaded: loaded, total: total } ); - - } - - }, - - /** - * Adapt texture to zoom - * @memberOf GoogleStreetviewLoader - * @instance - */ - adaptTextureToZoom: function () { - - const w = this.widths [ this._zoom ]; - const h = this.heights[ this._zoom ]; - - const maxW = this.maxW; - const maxH = this.maxH; - - this._wc = Math.ceil( w / maxW ); - this._hc = Math.ceil( h / maxH ); - - for( let y = 0; y < this._hc; y++ ) { - for( let x = 0; x < this._wc; x++ ) { - const c = document.createElement( 'canvas' ); - if( x < ( this._wc - 1 ) ) c.width = maxW; else c.width = w - ( maxW * x ); - if( y < ( this._hc - 1 ) ) c.height = maxH; else c.height = h - ( maxH * y ); - this._canvas.push( c ); - this._ctx.push( c.getContext( '2d' ) ); - } - } - - }, - - /** - * Compose from tile - * @param {number} x - * @param {number} y - * @param {*} texture - * @memberOf GoogleStreetviewLoader - * @instance - */ - composeFromTile: function ( x, y, texture ) { - - const maxW = this.maxW; - const maxH = this.maxH; - - x *= 512; - y *= 512; - - const px = Math.floor( x / maxW ); - const py = Math.floor( y / maxH ); - - x -= px * maxW; - y -= py * maxH; - - this._ctx[ py * this._wc + px ].drawImage( texture, 0, 0, texture.width, texture.height, x, y, 512, 512 ); - - this.progress(); - - }, - - /** - * Progress - * @memberOf GoogleStreetviewLoader - * @instance - */ - progress: function() { - - this._count++; - - this.setProgress( this._count, this._total ); - - if ( this._count === this._total) { - - this.canvas = this._canvas; - this.panoId = this._panoId; - this.zoom = this._zoom; - - if ( this.onPanoramaLoad ) { - - this.onPanoramaLoad( this._canvas[ 0 ] ); - - } - - } - }, - - /** - * Compose panorama - * @memberOf GoogleStreetviewLoader - * @instance - */ - composePanorama: function () { - - this.setProgress( 0, 1 ); - - const w = this.levelsW[ this._zoom ]; - const h = this.levelsH[ this._zoom ]; - const self = this; - - this._count = 0; - this._total = w * h; - - const { useWebGL } = this._parameters; - - for( let y = 0; y < h; y++ ) { - for( let x = 0; x < w; x++ ) { - const url = 'https://geo0.ggpht.com/cbk?cb_client=maps_sv.tactile&authuser=0&hl=en&output=tile&zoom=' + this._zoom + '&x=' + x + '&y=' + y + '&panoid=' + this._panoId + '&nbt&fover=2'; - ( function( x, y ) { - if( useWebGL ) { - const texture = TextureLoader.load( url, null, function() { - self.composeFromTile( x, y, texture ); - } ); - } else { - const img = new Image(); - img.addEventListener( 'load', function() { - self.composeFromTile( x, y, this ); - } ); - img.crossOrigin = ''; - img.src = url; - } - } )( x, y ); - } - } - - }, - - /** - * Load - * @param {string} panoid - * @memberOf GoogleStreetviewLoader - * @instance - */ - load: function ( panoid ) { - - this.loadPano( panoid ); - - }, - - /** - * Load panorama - * @param {string} id - * @memberOf GoogleStreetviewLoader - * @instance - */ - loadPano: function( id ) { - - const self = this; - this._panoClient.getPanoramaById( id, function (result, status) { - if (status === google.maps.StreetViewStatus.OK) { - self.result = result; - self.copyright = result.copyright; - self._panoId = result.location.pano; - self.composePanorama(); - } - }); - - }, - - /** - * Set zoom level - * @param {number} z - * @memberOf GoogleStreetviewLoader - * @instance - */ - setZoom: function( z ) { - - this._zoom = z; - this.adaptTextureToZoom(); - } - - } ); - - /** - * @classdesc Google streetview panorama - * @description [How to get Panorama ID]{@link http://stackoverflow.com/questions/29916149/google-maps-streetview-how-to-get-panorama-id} - * @constructor - * @param {string} panoId - Panorama id from Google Streetview - * @param {string} [apiKey] - Google Street View API Key - */ - function GoogleStreetviewPanorama ( panoId, apiKey ) { - - ImagePanorama.call( this ); - - this.panoId = panoId; - - this.gsvLoader = null; - - this.loadRequested = false; - - this.setupGoogleMapAPI( apiKey ); - - } - - GoogleStreetviewPanorama.prototype = Object.assign( Object.create( ImagePanorama.prototype ), { - - constructor: GoogleStreetviewPanorama, - - /** - * Load Google Street View by panorama id - * @param {string} panoId - Gogogle Street View panorama id - * @memberOf GoogleStreetviewPanorama - * @instance - */ - load: function ( panoId ) { - - this.loadRequested = true; - - panoId = ( panoId || this.panoId ) || {}; - - if ( panoId && this.gsvLoader ) { - - this.loadGSVLoader( panoId ); - - } - - }, - - /** - * Setup Google Map API - * @param {string} apiKey - * @memberOf GoogleStreetviewPanorama - * @instance - */ - setupGoogleMapAPI: function ( apiKey ) { - - const script = document.createElement( 'script' ); - script.src = 'https://maps.googleapis.com/maps/api/js?'; - script.src += apiKey ? 'key=' + apiKey : ''; - script.onreadystatechange = this.setGSVLoader.bind( this ); - script.onload = this.setGSVLoader.bind( this ); - - document.querySelector( 'head' ).appendChild( script ); - - }, - - /** - * Set GSV Loader - * @memberOf GoogleStreetviewPanorama - * @instance - */ - setGSVLoader: function () { - - this.gsvLoader = new GoogleStreetviewLoader(); - - if ( this.loadRequested ) { - - this.load(); - - } - - }, - - /** - * Get GSV Loader - * @memberOf GoogleStreetviewPanorama - * @instance - * @return {GoogleStreetviewLoader} GSV Loader instance - */ - getGSVLoader: function () { - - return this.gsvLoader; - - }, - - /** - * Load GSV Loader - * @param {string} panoId - Gogogle Street View panorama id - * @memberOf GoogleStreetviewPanorama - * @instance - */ - loadGSVLoader: function ( panoId ) { - - this.loadRequested = false; - - this.gsvLoader.onProgress = this.onProgress.bind( this ); - - this.gsvLoader.onPanoramaLoad = this.onLoad.bind( this ); - - this.gsvLoader.setZoom( this.getZoomLevel() ); - - this.gsvLoader.load( panoId ); - - this.gsvLoader.loaded = true; - }, - - /** - * This will be called when panorama is loaded - * @param {HTMLCanvasElement} canvas - Canvas where the tiles have been drawn - * @memberOf GoogleStreetviewPanorama - * @instance - */ - onLoad: function ( canvas ) { - - ImagePanorama.prototype.onLoad.call( this, new THREE.Texture( canvas ) ); - - }, - - /** - * Reset - * @memberOf GoogleStreetviewPanorama - * @instance - */ - reset: function () { - - this.gsvLoader = undefined; - - ImagePanorama.prototype.reset.call( this ); - - } - - } ); - - /** - * Stereographic projection shader - * based on http://notlion.github.io/streetview-stereographic - * @author pchen66 - */ - - /** - * @description Stereograhpic Shader - * @module StereographicShader - * @property {object} uniforms - * @property {THREE.Texture} uniforms.tDiffuse diffuse map - * @property {number} uniforms.resolution image resolution - * @property {THREE.Matrix4} uniforms.transform transformation matrix - * @property {number} uniforms.zoom image zoom factor - * @property {number} uniforms.opacity image opacity - * @property {string} vertexShader vertex shader - * @property {string} fragmentShader fragment shader - */ - const StereographicShader = { - - uniforms: { - - 'tDiffuse': { value: new THREE.Texture() }, - 'resolution': { value: 1.0 }, - 'transform': { value: new THREE.Matrix4() }, - 'zoom': { value: 1.0 }, - 'opacity': { value: 1.0 } - - }, - - vertexShader: [ - - 'varying vec2 vUv;', - - 'void main() {', - - 'vUv = uv;', - 'gl_Position = vec4( position, 1.0 );', - - '}' - - ].join( '\n' ), - - fragmentShader: [ - - 'uniform sampler2D tDiffuse;', - 'uniform float resolution;', - 'uniform mat4 transform;', - 'uniform float zoom;', - 'uniform float opacity;', - - 'varying vec2 vUv;', - - 'const float PI = 3.141592653589793;', - - 'void main(){', - - 'vec2 position = -1.0 + 2.0 * vUv;', - - 'position *= vec2( zoom * resolution, zoom * 0.5 );', - - 'float x2y2 = position.x * position.x + position.y * position.y;', - 'vec3 sphere_pnt = vec3( 2. * position, x2y2 - 1. ) / ( x2y2 + 1. );', - - 'sphere_pnt = vec3( transform * vec4( sphere_pnt, 1.0 ) );', - - 'vec2 sampleUV = vec2(', - '(atan(sphere_pnt.y, sphere_pnt.x) / PI + 1.0) * 0.5,', - '(asin(sphere_pnt.z) / PI + 0.5)', - ');', - - 'gl_FragColor = texture2D( tDiffuse, sampleUV );', - - 'gl_FragColor.a *= opacity;', - - '}' - - ].join( '\n' ) - - }; - - /** - * @classdesc Little Planet - * @constructor - * @param {string} type - Type of little planet basic class - * @param {string} source - URL for the image source - * @param {number} [size=10000] - Size of plane geometry - * @param {number} [ratio=0.5] - Ratio of plane geometry's height against width - */ - function LittlePlanet ( type = 'image', source, size = 10000, ratio = 0.5 ) { - - if ( type === 'image' ) { - - ImagePanorama.call( this, source, this.createGeometry( size, ratio ), this.createMaterial( size ) ); - - } - - this.size = size; - this.ratio = ratio; - this.EPS = 0.000001; - this.frameId = null; - - this.dragging = false; - this.userMouse = new THREE.Vector2(); - - this.quatA = new THREE.Quaternion(); - this.quatB = new THREE.Quaternion(); - this.quatCur = new THREE.Quaternion(); - this.quatSlerp = new THREE.Quaternion(); - - this.vectorX = new THREE.Vector3( 1, 0, 0 ); - this.vectorY = new THREE.Vector3( 0, 1, 0 ); - - this.addEventListener( 'window-resize', this.onWindowResize ); - - } - - LittlePlanet.prototype = Object.assign( Object.create( ImagePanorama.prototype ), { - - constructor: LittlePlanet, - - add: function ( object ) { - - if ( arguments.length > 1 ) { - - for ( let i = 0; i < arguments.length; i ++ ) { - - this.add( arguments[ i ] ); - - } - - return this; - - } - - if ( object instanceof Infospot ) { - - object.material.depthTest = false; - - } - - ImagePanorama.prototype.add.call( this, object ); - - }, - - createGeometry: function ( size, ratio ) { - - return new THREE.PlaneBufferGeometry( size, size * ratio ); - - }, - - createMaterial: function ( size ) { - - const shader = Object.assign( {}, StereographicShader ), uniforms = shader.uniforms; - - uniforms.zoom.value = size; - uniforms.opacity.value = 0.0; - - return new THREE.ShaderMaterial( { - - uniforms: uniforms, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader, - side: THREE.BackSide, - transparent: true - - } ); - - }, - - registerMouseEvents: function () { - - this.container.addEventListener( 'mousedown', this.onMouseDown.bind( this ), { passive: true } ); - this.container.addEventListener( 'mousemove', this.onMouseMove.bind( this ), { passive: true } ); - this.container.addEventListener( 'mouseup', this.onMouseUp.bind( this ), { passive: true } ); - this.container.addEventListener( 'touchstart', this.onMouseDown.bind( this ), { passive: true } ); - this.container.addEventListener( 'touchmove', this.onMouseMove.bind( this ), { passive: true } ); - this.container.addEventListener( 'touchend', this.onMouseUp.bind( this ), { passive: true } ); - this.container.addEventListener( 'mousewheel', this.onMouseWheel.bind( this ), { passive: false } ); - this.container.addEventListener( 'DOMMouseScroll', this.onMouseWheel.bind( this ), { passive: false } ); - this.container.addEventListener( 'contextmenu', this.onContextMenu.bind( this ), { passive: true } ); - - }, - - unregisterMouseEvents: function () { - - this.container.removeEventListener( 'mousedown', this.onMouseDown.bind( this ), false ); - this.container.removeEventListener( 'mousemove', this.onMouseMove.bind( this ), false ); - this.container.removeEventListener( 'mouseup', this.onMouseUp.bind( this ), false ); - this.container.removeEventListener( 'touchstart', this.onMouseDown.bind( this ), false ); - this.container.removeEventListener( 'touchmove', this.onMouseMove.bind( this ), false ); - this.container.removeEventListener( 'touchend', this.onMouseUp.bind( this ), false ); - this.container.removeEventListener( 'mousewheel', this.onMouseWheel.bind( this ), false ); - this.container.removeEventListener( 'DOMMouseScroll', this.onMouseWheel.bind( this ), false ); - this.container.removeEventListener( 'contextmenu', this.onContextMenu.bind( this ), false ); - - }, - - onMouseDown: function ( event ) { - - const inputCount = ( event.touches && event.touches.length ) || 1 ; - - switch ( inputCount ) { - - case 1: - - const x = ( event.clientX >= 0 ) ? event.clientX : event.touches[ 0 ].clientX; - const y = ( event.clientY >= 0 ) ? event.clientY : event.touches[ 0 ].clientY; - - this.dragging = true; - this.userMouse.set( x, y ); - - break; - - case 2: - - const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; - const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; - const distance = Math.sqrt( dx * dx + dy * dy ); - this.userMouse.pinchDistance = distance; - - break; - - default: - - break; - - } - - this.onUpdateCallback(); - - }, - - onMouseMove: function ( event ) { - - const inputCount = ( event.touches && event.touches.length ) || 1 ; - - switch ( inputCount ) { - - case 1: - - const x = ( event.clientX >= 0 ) ? event.clientX : event.touches[ 0 ].clientX; - const y = ( event.clientY >= 0 ) ? event.clientY : event.touches[ 0 ].clientY; - - const angleX = THREE.Math.degToRad( x - this.userMouse.x ) * 0.4; - const angleY = THREE.Math.degToRad( y - this.userMouse.y ) * 0.4; - - if ( this.dragging ) { - this.quatA.setFromAxisAngle( this.vectorY, angleX ); - this.quatB.setFromAxisAngle( this.vectorX, angleY ); - this.quatCur.multiply( this.quatA ).multiply( this.quatB ); - this.userMouse.set( x, y ); - } - - break; - - case 2: - - const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; - const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; - const distance = Math.sqrt( dx * dx + dy * dy ); - - this.addZoomDelta( this.userMouse.pinchDistance - distance ); - - break; - - default: - - break; - - } - - }, - - onMouseUp: function () { - - this.dragging = false; - - }, - - onMouseWheel: function ( event ) { - - event.preventDefault(); - event.stopPropagation(); - - let delta = 0; - - if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 - - delta = event.wheelDelta; - - } else if ( event.detail !== undefined ) { // Firefox - - delta = - event.detail; - - } - - this.addZoomDelta( delta ); - this.onUpdateCallback(); - - }, - - addZoomDelta: function ( delta ) { - - const uniforms = this.material.uniforms; - const lowerBound = this.size * 0.1; - const upperBound = this.size * 10; - - uniforms.zoom.value += delta; - - if ( uniforms.zoom.value <= lowerBound ) { - - uniforms.zoom.value = lowerBound; - - } else if ( uniforms.zoom.value >= upperBound ) { - - uniforms.zoom.value = upperBound; - - } - - }, - - onUpdateCallback: function () { - - this.frameId = window.requestAnimationFrame( this.onUpdateCallback.bind( this ) ); - - this.quatSlerp.slerp( this.quatCur, 0.1 ); - - if ( this.material ) { - - this.material.uniforms.transform.value.makeRotationFromQuaternion( this.quatSlerp ); - - } - - if ( !this.dragging && 1.0 - this.quatSlerp.clone().dot( this.quatCur ) < this.EPS ) { - - window.cancelAnimationFrame( this.frameId ); - - } - - }, - - reset: function () { - - this.quatCur.set( 0, 0, 0, 1 ); - this.quatSlerp.set( 0, 0, 0, 1 ); - this.onUpdateCallback(); - - }, - - onLoad: function ( texture ) { - - this.material.uniforms.resolution.value = this.container.clientWidth / this.container.clientHeight; - - this.registerMouseEvents(); - this.onUpdateCallback(); - - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'disableControl' } ); - - ImagePanorama.prototype.onLoad.call( this, texture ); - - }, - - onLeave: function () { - - this.unregisterMouseEvents(); - - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'enableControl', data: CONTROLS.ORBIT } ); - - window.cancelAnimationFrame( this.frameId ); - - ImagePanorama.prototype.onLeave.call( this ); - - }, - - onWindowResize: function () { - - this.material.uniforms.resolution.value = this.container.clientWidth / this.container.clientHeight; - - }, - - onContextMenu: function () { - - this.dragging = false; - - }, - - dispose: function () { - - this.unregisterMouseEvents(); - - ImagePanorama.prototype.dispose.call( this ); - - } - - }); - - /** - * @classdesc Image Little Planet - * @constructor - * @param {string} source - URL for the image source - * @param {number} [size=10000] - Size of plane geometry - * @param {number} [ratio=0.5] - Ratio of plane geometry's height against width - */ - function ImageLittlePlanet ( source, size, ratio ) { - - LittlePlanet.call( this, 'image', source, size, ratio ); - - } - - ImageLittlePlanet.prototype = Object.assign( Object.create( LittlePlanet.prototype ), { - - constructor: ImageLittlePlanet, - - /** - * On loaded with texture - * @param {THREE.Texture} texture - * @memberOf ImageLittlePlanet - * @instance - */ - onLoad: function ( texture ) { - - this.updateTexture( texture ); - - LittlePlanet.prototype.onLoad.call( this, texture ); - - }, - - /** - * Update texture - * @param {THREE.Texture} texture - * @memberOf ImageLittlePlanet - * @instance - */ - updateTexture: function ( texture ) { - - texture.minFilter = texture.magFilter = THREE.LinearFilter; - - this.material.uniforms[ 'tDiffuse' ].value = texture; - - }, - - /** - * Dispose - * @memberOf ImageLittlePlanet - * @instance - */ - dispose: function () { - - const tDiffuse = this.material.uniforms[ 'tDiffuse' ]; - - if ( tDiffuse && tDiffuse.value ) { - - tDiffuse.value.dispose(); - - } - - LittlePlanet.prototype.dispose.call( this ); - - } - - } ); - - /** - * @classdesc Camera panorama - * @description See {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints|MediaStreamConstraints} for constraints - * @param {object} - camera constraints - * @constructor - */ - function CameraPanorama ( constraints ) { - - const radius = 5000; - const geometry = new THREE.SphereBufferGeometry( radius, 60, 40 ); - const material = new THREE.MeshBasicMaterial( { visible: false }); - - Panorama.call( this, geometry, material ); - - this.media = new Media( constraints ); - this.radius = radius; - - this.addEventListener( 'enter', this.start.bind( this ) ); - this.addEventListener( 'leave', this.stop.bind( this ) ); - this.addEventListener( 'panolens-container', this.onPanolensContainer.bind( this ) ); - this.addEventListener( 'panolens-scene', this.onPanolensScene.bind( this ) ); - - } - - CameraPanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { - - constructor: CameraPanorama, - - /** - * On container event - * @param {object} event - * @memberOf CameraPanorama - * @instance - */ - onPanolensContainer: function ( { container } ) { - - this.media.setContainer( container ); - - }, - - /** - * On scene event - * @param {object} event - * @memberOf CameraPanorama - * @instance - */ - onPanolensScene: function ( { scene } ) { - - this.media.setScene( scene ); - - }, - - /** - * Start camera streaming - * @memberOf CameraPanorama - * @instance - * @returns {Promise} - */ - start: function () { - - return this.media.start(); - - }, - - /** - * Stop camera streaming - * @memberOf CameraPanorama - * @instance - */ - stop: function () { - - this.media.stop(); - - }, - - } ); - - /** - * @classdesc Orbit Controls - * @constructor - * @external OrbitControls - * @param {THREE.Object} object - * @param {HTMLElement} domElement - */ - function OrbitControls ( object, domElement ) { - - this.object = object; - this.domElement = ( domElement !== undefined ) ? domElement : document; - this.frameId = null; - - // API - - // Set to false to disable this control - this.enabled = true; - - /* - * "target" sets the location of focus, where the control orbits around - * and where it pans with respect to. - */ - this.target = new THREE.Vector3(); - - // center is old, deprecated; use "target" instead - this.center = this.target; - - /* - * This option actually enables dollying in and out; left as "zoom" for - * backwards compatibility - */ - this.noZoom = false; - this.zoomSpeed = 1.0; - - // Limits to how far you can dolly in and out ( PerspectiveCamera only ) - this.minDistance = 0; - this.maxDistance = Infinity; - - // Limits to how far you can zoom in and out ( OrthographicCamera only ) - this.minZoom = 0; - this.maxZoom = Infinity; - - // Set to true to disable this control - this.noRotate = false; - this.rotateSpeed = -0.15; - - // Set to true to disable this control - this.noPan = true; - this.keyPanSpeed = 7.0; // pixels moved per arrow key push - - // Set to true to automatically rotate around the target - this.autoRotate = false; - this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 - - /* - * How far you can orbit vertically, upper and lower limits. - * Range is 0 to Math.PI radians. - */ - this.minPolarAngle = 0; // radians - this.maxPolarAngle = Math.PI; // radians - - // Momentum - this.momentumDampingFactor = 0.90; - this.momentumScalingFactor = -0.005; - this.momentumKeydownFactor = 20; - - // Fov - this.minFov = 30; - this.maxFov = 120; - - /* - * How far you can orbit horizontally, upper and lower limits. - * If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. - */ - this.minAzimuthAngle = - Infinity; // radians - this.maxAzimuthAngle = Infinity; // radians - - // Set to true to disable use of the keys - this.noKeys = false; - - // The four arrow keys - this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; - - // Mouse buttons - this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; - - /* - * ////////// - * internals - */ - - var scope = this; - - var EPS = 10e-8; - var MEPS = 10e-5; - - var rotateStart = new THREE.Vector2(); - var rotateEnd = new THREE.Vector2(); - var rotateDelta = new THREE.Vector2(); - - var panStart = new THREE.Vector2(); - var panEnd = new THREE.Vector2(); - var panDelta = new THREE.Vector2(); - var panOffset = new THREE.Vector3(); - - var offset = new THREE.Vector3(); - - var dollyStart = new THREE.Vector2(); - var dollyEnd = new THREE.Vector2(); - var dollyDelta = new THREE.Vector2(); - - var theta = 0; - var phi = 0; - var phiDelta = 0; - var thetaDelta = 0; - var scale = 1; - var pan = new THREE.Vector3(); - - var lastPosition = new THREE.Vector3(); - var lastQuaternion = new THREE.Quaternion(); - - var momentumLeft = 0, momentumUp = 0; - var eventPrevious; - var momentumOn = false; - - var keyUp, keyBottom, keyLeft, keyRight; - - var STATE = { NONE: -1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 }; - - var state = STATE.NONE; - - // for reset - - this.target0 = this.target.clone(); - this.position0 = this.object.position.clone(); - this.zoom0 = this.object.zoom; - - // so camera.up is the orbit axis - - var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); - var quatInverse = quat.clone().inverse(); - - // events - - var changeEvent = { type: 'change' }; - var startEvent = { type: 'start' }; - var endEvent = { type: 'end' }; - - this.setLastQuaternion = function ( quaternion ) { - lastQuaternion.copy( quaternion ); - scope.object.quaternion.copy( quaternion ); - }; - - this.getLastPosition = function () { - return lastPosition; - }; - - this.rotateLeft = function ( angle ) { - - if ( angle === undefined ) { - - angle = getAutoRotationAngle(); - - } - - thetaDelta -= angle; - - - }; - - this.rotateUp = function ( angle ) { - - if ( angle === undefined ) { - - angle = getAutoRotationAngle(); - - } - - phiDelta -= angle; - - }; - - // pass in distance in world space to move left - this.panLeft = function ( distance ) { - - var te = this.object.matrix.elements; - - // get X column of matrix - panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] ); - panOffset.multiplyScalar( - distance ); - - pan.add( panOffset ); - - }; - - // pass in distance in world space to move up - this.panUp = function ( distance ) { - - var te = this.object.matrix.elements; - - // get Y column of matrix - panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] ); - panOffset.multiplyScalar( distance ); - - pan.add( panOffset ); - - }; - - /* - * pass in x,y of change desired in pixel space, - * right and down are positive - */ - this.pan = function ( deltaX, deltaY ) { - - var element = scope.domElement === document ? scope.domElement.body : scope.domElement; - - if ( scope.object instanceof THREE.PerspectiveCamera ) { - - // perspective - var position = scope.object.position; - var offset = position.clone().sub( scope.target ); - var targetDistance = offset.length(); - - // half of the fov is center to top of screen - targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); - - // we actually don't use screenWidth, since perspective camera is fixed to screen height - scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight ); - scope.panUp( 2 * deltaY * targetDistance / element.clientHeight ); - - } else if ( scope.object instanceof THREE.OrthographicCamera ) { - - // orthographic - scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth ); - scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight ); - - } else { - - // camera neither orthographic or perspective - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); - - } - - }; - - this.momentum = function(){ - - if ( !momentumOn ) return; - - if ( Math.abs( momentumLeft ) < MEPS && Math.abs( momentumUp ) < MEPS ) { - - momentumOn = false; - return; - } - - momentumUp *= this.momentumDampingFactor; - momentumLeft *= this.momentumDampingFactor; - - thetaDelta -= this.momentumScalingFactor * momentumLeft; - phiDelta -= this.momentumScalingFactor * momentumUp; - - }; - - this.dollyIn = function ( dollyScale ) { - - if ( dollyScale === undefined ) { - - dollyScale = getZoomScale(); - - } - - if ( scope.object instanceof THREE.PerspectiveCamera ) { - - scale /= dollyScale; - - } else if ( scope.object instanceof THREE.OrthographicCamera ) { - - scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom * dollyScale ) ); - scope.object.updateProjectionMatrix(); - scope.dispatchEvent( changeEvent ); - - } else { - - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); - - } - - }; - - this.dollyOut = function ( dollyScale ) { - - if ( dollyScale === undefined ) { - - dollyScale = getZoomScale(); - - } - - if ( scope.object instanceof THREE.PerspectiveCamera ) { - - scale *= dollyScale; - - } else if ( scope.object instanceof THREE.OrthographicCamera ) { - - scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / dollyScale ) ); - scope.object.updateProjectionMatrix(); - scope.dispatchEvent( changeEvent ); - - } else { - - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); - - } - - }; - - this.update = function ( ignoreUpdate ) { - - var position = this.object.position; - - offset.copy( position ).sub( this.target ); - - // rotate offset to "y-axis-is-up" space - offset.applyQuaternion( quat ); - - // angle from z-axis around y-axis - - theta = Math.atan2( offset.x, offset.z ); - - // angle from y-axis - - phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); - - if ( this.autoRotate && state === STATE.NONE ) { - - this.rotateLeft( getAutoRotationAngle() ); - - } - - this.momentum(); - - theta += thetaDelta; - phi += phiDelta; - - // restrict theta to be between desired limits - theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) ); - - // restrict phi to be between desired limits - phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); - - // restrict phi to be betwee EPS and PI-EPS - phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); - - var radius = offset.length() * scale; - - // restrict radius to be between desired limits - radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); - - // move target to panned location - this.target.add( pan ); - - offset.x = radius * Math.sin( phi ) * Math.sin( theta ); - offset.y = radius * Math.cos( phi ); - offset.z = radius * Math.sin( phi ) * Math.cos( theta ); - - // rotate offset back to "camera-up-vector-is-up" space - offset.applyQuaternion( quatInverse ); - - position.copy( this.target ).add( offset ); - - this.object.lookAt( this.target ); - - thetaDelta = 0; - phiDelta = 0; - scale = 1; - pan.set( 0, 0, 0 ); - - /* - * update condition is: - * min(camera displacement, camera rotation in radians)^2 > EPS - * using small-angle approximation cos(x/2) = 1 - x^2 / 8 - */ - if ( lastPosition.distanceToSquared( this.object.position ) > EPS - || 8 * (1 - lastQuaternion.dot(this.object.quaternion)) > EPS ) { - - if ( ignoreUpdate !== true ) { this.dispatchEvent( changeEvent ); } - - lastPosition.copy( this.object.position ); - lastQuaternion.copy (this.object.quaternion ); - - } - - }; - - - this.reset = function () { - - state = STATE.NONE; - - this.target.copy( this.target0 ); - this.object.position.copy( this.position0 ); - this.object.zoom = this.zoom0; - - this.object.updateProjectionMatrix(); - this.dispatchEvent( changeEvent ); - - this.update(); - - }; - - this.getPolarAngle = function () { - - return phi; - - }; - - this.getAzimuthalAngle = function () { - - return theta; - - }; - - function getAutoRotationAngle() { - - return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; - - } - - function getZoomScale() { - - return Math.pow( 0.95, scope.zoomSpeed ); - - } - - function onMouseDown( event ) { - - momentumOn = false; - - momentumLeft = momentumUp = 0; - - if ( scope.enabled === false ) return; - event.preventDefault(); - - if ( event.button === scope.mouseButtons.ORBIT ) { - if ( scope.noRotate === true ) return; - - state = STATE.ROTATE; - - rotateStart.set( event.clientX, event.clientY ); - - } else if ( event.button === scope.mouseButtons.ZOOM ) { - if ( scope.noZoom === true ) return; - - state = STATE.DOLLY; - - dollyStart.set( event.clientX, event.clientY ); - - } else if ( event.button === scope.mouseButtons.PAN ) { - if ( scope.noPan === true ) return; - - state = STATE.PAN; - - panStart.set( event.clientX, event.clientY ); - - } - - if ( state !== STATE.NONE ) { - document.addEventListener( 'mousemove', onMouseMove, false ); - document.addEventListener( 'mouseup', onMouseUp, false ); - scope.dispatchEvent( startEvent ); - } - - scope.update(); - - } - - function onMouseMove( event ) { - - if ( scope.enabled === false ) return; - - event.preventDefault(); - - var element = scope.domElement === document ? scope.domElement.body : scope.domElement; - - if ( state === STATE.ROTATE ) { - - if ( scope.noRotate === true ) return; - - rotateEnd.set( event.clientX, event.clientY ); - rotateDelta.subVectors( rotateEnd, rotateStart ); - - // rotating across whole screen goes 360 degrees around - scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); - - // rotating up and down along whole screen attempts to go 360, but limited to 180 - scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); - - rotateStart.copy( rotateEnd ); - - if( eventPrevious ){ - momentumLeft = event.clientX - eventPrevious.clientX; - momentumUp = event.clientY - eventPrevious.clientY; - } - - eventPrevious = event; - - } else if ( state === STATE.DOLLY ) { - - if ( scope.noZoom === true ) return; - - dollyEnd.set( event.clientX, event.clientY ); - dollyDelta.subVectors( dollyEnd, dollyStart ); - - if ( dollyDelta.y > 0 ) { - - scope.dollyIn(); - - } else if ( dollyDelta.y < 0 ) { - - scope.dollyOut(); - - } - - dollyStart.copy( dollyEnd ); - - } else if ( state === STATE.PAN ) { - - if ( scope.noPan === true ) return; - - panEnd.set( event.clientX, event.clientY ); - panDelta.subVectors( panEnd, panStart ); - - scope.pan( panDelta.x, panDelta.y ); - - panStart.copy( panEnd ); - - } - - if ( state !== STATE.NONE ) scope.update(); - - } - - function onMouseUp( /* event */ ) { - - momentumOn = true; - - eventPrevious = undefined; - - if ( scope.enabled === false ) return; - - document.removeEventListener( 'mousemove', onMouseMove, false ); - document.removeEventListener( 'mouseup', onMouseUp, false ); - scope.dispatchEvent( endEvent ); - state = STATE.NONE; - - } - - function onMouseWheel( event ) { - - if ( scope.enabled === false || scope.noZoom === true || state !== STATE.NONE ) return; - - event.preventDefault(); - event.stopPropagation(); - - var delta = 0; - - if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 - - delta = event.wheelDelta; - - } else if ( event.detail !== undefined ) { // Firefox - - delta = - event.detail; - - } - - if ( delta > 0 ) { - - // scope.dollyOut(); - scope.object.fov = ( scope.object.fov < scope.maxFov ) - ? scope.object.fov + 1 - : scope.maxFov; - scope.object.updateProjectionMatrix(); - - } else if ( delta < 0 ) { - - // scope.dollyIn(); - scope.object.fov = ( scope.object.fov > scope.minFov ) - ? scope.object.fov - 1 - : scope.minFov; - scope.object.updateProjectionMatrix(); - - } - - scope.update(); - scope.dispatchEvent( changeEvent ); - scope.dispatchEvent( startEvent ); - scope.dispatchEvent( endEvent ); - - } - - function onKeyUp ( event ) { - - switch ( event.keyCode ) { - - case scope.keys.UP: - keyUp = false; - break; - - case scope.keys.BOTTOM: - keyBottom = false; - break; - - case scope.keys.LEFT: - keyLeft = false; - break; - - case scope.keys.RIGHT: - keyRight = false; - break; - - } - - } - - function onKeyDown( event ) { - - if ( scope.enabled === false || scope.noKeys === true || scope.noRotate === true ) return; - - switch ( event.keyCode ) { - - case scope.keys.UP: - keyUp = true; - break; - - case scope.keys.BOTTOM: - keyBottom = true; - break; - - case scope.keys.LEFT: - keyLeft = true; - break; - - case scope.keys.RIGHT: - keyRight = true; - break; - - } - - if (keyUp || keyBottom || keyLeft || keyRight) { - - momentumOn = true; - - if (keyUp) momentumUp = - scope.rotateSpeed * scope.momentumKeydownFactor; - if (keyBottom) momentumUp = scope.rotateSpeed * scope.momentumKeydownFactor; - if (keyLeft) momentumLeft = - scope.rotateSpeed * scope.momentumKeydownFactor; - if (keyRight) momentumLeft = scope.rotateSpeed * scope.momentumKeydownFactor; - - } - - } - - function touchstart( event ) { - - momentumOn = false; - - momentumLeft = momentumUp = 0; - - if ( scope.enabled === false ) return; - - switch ( event.touches.length ) { - - case 1: // one-fingered touch: rotate - - if ( scope.noRotate === true ) return; - - state = STATE.TOUCH_ROTATE; - - rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); - break; - - case 2: // two-fingered touch: dolly - - if ( scope.noZoom === true ) return; - - state = STATE.TOUCH_DOLLY; - - var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; - var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; - var distance = Math.sqrt( dx * dx + dy * dy ); - - dollyStart.set( 0, distance ); - - break; - - case 3: // three-fingered touch: pan - - if ( scope.noPan === true ) return; - - state = STATE.TOUCH_PAN; - - panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); - break; - - default: - - state = STATE.NONE; - - } - - if ( state !== STATE.NONE ) scope.dispatchEvent( startEvent ); - - } - - function touchmove( event ) { - - if ( scope.enabled === false ) return; - - event.preventDefault(); - event.stopPropagation(); - - var element = scope.domElement === document ? scope.domElement.body : scope.domElement; - - switch ( event.touches.length ) { - - case 1: // one-fingered touch: rotate - - if ( scope.noRotate === true ) return; - if ( state !== STATE.TOUCH_ROTATE ) return; - - rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); - rotateDelta.subVectors( rotateEnd, rotateStart ); - - // rotating across whole screen goes 360 degrees around - scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); - // rotating up and down along whole screen attempts to go 360, but limited to 180 - scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); - - rotateStart.copy( rotateEnd ); - - if( eventPrevious ){ - momentumLeft = event.touches[ 0 ].pageX - eventPrevious.pageX; - momentumUp = event.touches[ 0 ].pageY - eventPrevious.pageY; - } - - eventPrevious = { - pageX: event.touches[ 0 ].pageX, - pageY: event.touches[ 0 ].pageY, - }; - - scope.update(); - break; - - case 2: // two-fingered touch: dolly - - if ( scope.noZoom === true ) return; - if ( state !== STATE.TOUCH_DOLLY ) return; - - var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; - var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; - var distance = Math.sqrt( dx * dx + dy * dy ); - - dollyEnd.set( 0, distance ); - dollyDelta.subVectors( dollyEnd, dollyStart ); - - if ( dollyDelta.y < 0 ) { - - scope.object.fov = ( scope.object.fov < scope.maxFov ) - ? scope.object.fov + 1 - : scope.maxFov; - scope.object.updateProjectionMatrix(); - - } else if ( dollyDelta.y > 0 ) { - - scope.object.fov = ( scope.object.fov > scope.minFov ) - ? scope.object.fov - 1 - : scope.minFov; - scope.object.updateProjectionMatrix(); - - } - - dollyStart.copy( dollyEnd ); - - scope.update(); - scope.dispatchEvent( changeEvent ); - break; - - case 3: // three-fingered touch: pan - - if ( scope.noPan === true ) return; - if ( state !== STATE.TOUCH_PAN ) return; - - panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); - panDelta.subVectors( panEnd, panStart ); - - scope.pan( panDelta.x, panDelta.y ); - - panStart.copy( panEnd ); - - scope.update(); - break; - - default: - - state = STATE.NONE; - - } - - } - - function touchend( /* event */ ) { - - momentumOn = true; - - eventPrevious = undefined; - - if ( scope.enabled === false ) return; - - scope.dispatchEvent( endEvent ); - state = STATE.NONE; - - } - - this.dispose = function() { - - this.domElement.removeEventListener( 'mousedown', onMouseDown ); - this.domElement.removeEventListener( 'mousewheel', onMouseWheel ); - this.domElement.removeEventListener( 'DOMMouseScroll', onMouseWheel ); - - this.domElement.removeEventListener( 'touchstart', touchstart ); - this.domElement.removeEventListener( 'touchend', touchend ); - this.domElement.removeEventListener( 'touchmove', touchmove ); - - window.removeEventListener( 'keyup', onKeyUp ); - window.removeEventListener( 'keydown', onKeyDown ); - - }; - - // this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); - this.domElement.addEventListener( 'mousedown', onMouseDown, { passive: false } ); - this.domElement.addEventListener( 'mousewheel', onMouseWheel, { passive: false } ); - this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, { passive: false } ); // firefox - - this.domElement.addEventListener( 'touchstart', touchstart, { passive: false } ); - this.domElement.addEventListener( 'touchend', touchend, { passive: false } ); - this.domElement.addEventListener( 'touchmove', touchmove, { passive: false } ); - - window.addEventListener( 'keyup', onKeyUp, { passive: false } ); - window.addEventListener( 'keydown', onKeyDown, { passive: false } ); - - // force an update at start - this.update(); - - } - OrbitControls.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype ), { - - constructor: OrbitControls - - } ); - - /** - * @classdesc Device Orientation Control - * @constructor - * @external DeviceOrientationControls - * @param {THREE.Camera} camera - * @param {HTMLElement} domElement - */ - function DeviceOrientationControls ( camera, domElement ) { - - var scope = this; - var changeEvent = { type: 'change' }; - - var rotY = 0; - var rotX = 0; - var tempX = 0; - var tempY = 0; - - this.camera = camera; - this.camera.rotation.reorder( 'YXZ' ); - this.domElement = ( domElement !== undefined ) ? domElement : document; - - this.enabled = true; - - this.deviceOrientation = {}; - this.screenOrientation = 0; - - this.alpha = 0; - this.alphaOffsetAngle = 0; - - - var onDeviceOrientationChangeEvent = function( event ) { - - scope.deviceOrientation = event; - - }; - - var onScreenOrientationChangeEvent = function() { - - scope.screenOrientation = window.orientation || 0; - - }; - - var onTouchStartEvent = function (event) { - - event.preventDefault(); - event.stopPropagation(); - - tempX = event.touches[ 0 ].pageX; - tempY = event.touches[ 0 ].pageY; - - }; - - var onTouchMoveEvent = function (event) { - - event.preventDefault(); - event.stopPropagation(); - - rotY += THREE.Math.degToRad( ( event.touches[ 0 ].pageX - tempX ) / 4 ); - rotX += THREE.Math.degToRad( ( tempY - event.touches[ 0 ].pageY ) / 4 ); - - scope.updateAlphaOffsetAngle( rotY ); - - tempX = event.touches[ 0 ].pageX; - tempY = event.touches[ 0 ].pageY; - - }; - - // The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y'' - - var setCameraQuaternion = function( quaternion, alpha, beta, gamma, orient ) { - - var zee = new THREE.Vector3( 0, 0, 1 ); - - var euler = new THREE.Euler(); - - var q0 = new THREE.Quaternion(); - - var q1 = new THREE.Quaternion( - Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis - - var vectorFingerY; - var fingerQY = new THREE.Quaternion(); - var fingerQX = new THREE.Quaternion(); - - if ( scope.screenOrientation == 0 ) { - - vectorFingerY = new THREE.Vector3( 1, 0, 0 ); - fingerQY.setFromAxisAngle( vectorFingerY, -rotX ); - - } else if ( scope.screenOrientation == 180 ) { - - vectorFingerY = new THREE.Vector3( 1, 0, 0 ); - fingerQY.setFromAxisAngle( vectorFingerY, rotX ); - - } else if ( scope.screenOrientation == 90 ) { - - vectorFingerY = new THREE.Vector3( 0, 1, 0 ); - fingerQY.setFromAxisAngle( vectorFingerY, rotX ); - - } else if ( scope.screenOrientation == - 90) { - - vectorFingerY = new THREE.Vector3( 0, 1, 0 ); - fingerQY.setFromAxisAngle( vectorFingerY, -rotX ); - - } - - q1.multiply( fingerQY ); - q1.multiply( fingerQX ); - - euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us - - quaternion.setFromEuler( euler ); // orient the device - - quaternion.multiply( q1 ); // camera looks out the back of the device, not the top - - quaternion.multiply( q0.setFromAxisAngle( zee, - orient ) ); // adjust for screen orientation - - }; - - this.connect = function() { - - onScreenOrientationChangeEvent(); // run once on load - - window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent, { passive: true } ); - window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, { passive: true } ); - window.addEventListener( 'deviceorientation', this.update.bind( this ), { passive: true } ); - - scope.domElement.addEventListener( 'touchstart', onTouchStartEvent, { passive: false } ); - scope.domElement.addEventListener( 'touchmove', onTouchMoveEvent, { passive: false } ); - - scope.enabled = true; - - }; - - this.disconnect = function() { - - window.removeEventListener( 'orientationchange', onScreenOrientationChangeEvent, false ); - window.removeEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false ); - window.removeEventListener( 'deviceorientation', this.update.bind( this ), false ); - - scope.domElement.removeEventListener( 'touchstart', onTouchStartEvent, false ); - scope.domElement.removeEventListener( 'touchmove', onTouchMoveEvent, false ); - - scope.enabled = false; - - }; - - this.update = function( ignoreUpdate ) { - - if ( scope.enabled === false ) return; - - var alpha = scope.deviceOrientation.alpha ? THREE.Math.degToRad( scope.deviceOrientation.alpha ) + scope.alphaOffsetAngle : 0; // Z - var beta = scope.deviceOrientation.beta ? THREE.Math.degToRad( scope.deviceOrientation.beta ) : 0; // X' - var gamma = scope.deviceOrientation.gamma ? THREE.Math.degToRad( scope.deviceOrientation.gamma ) : 0; // Y'' - var orient = scope.screenOrientation ? THREE.Math.degToRad( scope.screenOrientation ) : 0; // O - - setCameraQuaternion( scope.camera.quaternion, alpha, beta, gamma, orient ); - scope.alpha = alpha; - - if ( ignoreUpdate !== true ) { scope.dispatchEvent( changeEvent ); } - - }; - - this.updateAlphaOffsetAngle = function( angle ) { - - this.alphaOffsetAngle = angle; - this.update(); - - }; - - this.dispose = function() { - - this.disconnect(); - - }; - - this.connect(); - - } - DeviceOrientationControls.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype), { - - constructor: DeviceOrientationControls - - } ); - - /** - * @classdesc Google Cardboard Effect Composer - * @constructor - * @external CardboardEffect - * @param {THREE.WebGLRenderer} renderer - */ - function CardboardEffect ( renderer ) { - - var _camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); - - var _scene = new THREE.Scene(); - - var _stereo = new THREE.StereoCamera(); - _stereo.aspect = 0.5; - - var _params = { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBAFormat }; - - var _renderTarget = new THREE.WebGLRenderTarget( 512, 512, _params ); - _renderTarget.scissorTest = true; - _renderTarget.texture.generateMipmaps = false; - - /* - * Distortion Mesh ported from: - * https://github.com/borismus/webvr-boilerplate/blob/master/src/distortion/barrel-distortion-fragment.js - */ - - var distortion = new THREE.Vector2( 0.441, 0.156 ); - - var geometry = new THREE.PlaneBufferGeometry( 1, 1, 10, 20 ).removeAttribute( 'normal' ).toNonIndexed(); - - var positions = geometry.attributes.position.array; - var uvs = geometry.attributes.uv.array; - - // duplicate - geometry.attributes.position.count *= 2; - geometry.attributes.uv.count *= 2; - - var positions2 = new Float32Array( positions.length * 2 ); - positions2.set( positions ); - positions2.set( positions, positions.length ); - - var uvs2 = new Float32Array( uvs.length * 2 ); - uvs2.set( uvs ); - uvs2.set( uvs, uvs.length ); - - var vector = new THREE.Vector2(); - var length = positions.length / 3; - - for ( var i = 0, l = positions2.length / 3; i < l; i ++ ) { - - vector.x = positions2[ i * 3 + 0 ]; - vector.y = positions2[ i * 3 + 1 ]; - - var dot = vector.dot( vector ); - var scalar = 1.5 + ( distortion.x + distortion.y * dot ) * dot; - - var offset = i < length ? 0 : 1; - - positions2[ i * 3 + 0 ] = ( vector.x / scalar ) * 1.5 - 0.5 + offset; - positions2[ i * 3 + 1 ] = ( vector.y / scalar ) * 3.0; - - uvs2[ i * 2 ] = ( uvs2[ i * 2 ] + offset ) * 0.5; - - } - - geometry.attributes.position.array = positions2; - geometry.attributes.uv.array = uvs2; - - // - - var material = new THREE.MeshBasicMaterial( { map: _renderTarget.texture } ); - var mesh = new THREE.Mesh( geometry, material ); - _scene.add( mesh ); - - // - - this.setSize = function ( width, height ) { - - renderer.setSize( width, height ); - - var pixelRatio = renderer.getPixelRatio(); - - _renderTarget.setSize( width * pixelRatio, height * pixelRatio ); - - }; - - this.render = function ( scene, camera ) { - - scene.updateMatrixWorld(); - - if ( camera.parent === null ) camera.updateMatrixWorld(); - - _stereo.update( camera ); - - var width = _renderTarget.width / 2; - var height = _renderTarget.height; - - if ( renderer.autoClear ) renderer.clear(); - - _renderTarget.scissor.set( 0, 0, width, height ); - _renderTarget.viewport.set( 0, 0, width, height ); - renderer.setRenderTarget( _renderTarget ); - renderer.render( scene, _stereo.cameraL ); - - renderer.clearDepth(); - - _renderTarget.scissor.set( width, 0, width, height ); - _renderTarget.viewport.set( width, 0, width, height ); - renderer.setRenderTarget( _renderTarget ); - renderer.render( scene, _stereo.cameraR ); - - renderer.clearDepth(); - - renderer.setRenderTarget( null ); - renderer.render( _scene, _camera ); - }; - - } - - /** - * @classdesc Stereo Effect Composer - * @constructor - * @external StereoEffect - * @param {THREE.WebGLRenderer} renderer - */ - const StereoEffect = function ( renderer ) { - - var _stereo = new THREE.StereoCamera(); - _stereo.aspect = 0.5; - var size = new THREE.Vector2(); - - this.setEyeSeparation = function ( eyeSep ) { - - _stereo.eyeSep = eyeSep; - - }; - - this.setSize = function ( width, height ) { - - renderer.setSize( width, height ); - - }; - - this.render = function ( scene, camera ) { - - scene.updateMatrixWorld(); - - if ( camera.parent === null ) camera.updateMatrixWorld(); - - _stereo.update( camera ); - - renderer.getSize( size ); - - if ( renderer.autoClear ) renderer.clear(); - renderer.setScissorTest( true ); - - renderer.setScissor( 0, 0, size.width / 2, size.height ); - renderer.setViewport( 0, 0, size.width / 2, size.height ); - renderer.render( scene, _stereo.cameraL ); - - renderer.setScissor( size.width / 2, 0, size.width / 2, size.height ); - renderer.setViewport( size.width / 2, 0, size.width / 2, size.height ); - renderer.render( scene, _stereo.cameraR ); - - renderer.setScissorTest( false ); - - }; - - }; - - /** - * @classdesc Viewer contains pre-defined scene, camera and renderer - * @constructor - * @param {object} [options] - Use custom or default config options - * @param {HTMLElement} [options.container] - A HTMLElement to host the canvas - * @param {THREE.Scene} [options.scene=THREE.Scene] - A THREE.Scene which contains panorama and 3D objects - * @param {THREE.Camera} [options.camera=THREE.PerspectiveCamera] - A THREE.Camera to view the scene - * @param {THREE.WebGLRenderer} [options.renderer=THREE.WebGLRenderer] - A THREE.WebGLRenderer to render canvas - * @param {boolean} [options.controlBar=true] - Show/hide control bar on the bottom of the container - * @param {array} [options.controlButtons=[]] - Button names to mount on controlBar if controlBar exists, Defaults to ['fullscreen', 'setting', 'video'] - * @param {boolean} [options.autoHideControlBar=false] - Auto hide control bar when click on non-active area - * @param {boolean} [options.autoHideInfospot=true] - Auto hide infospots when click on non-active area - * @param {boolean} [options.horizontalView=false] - Allow only horizontal camera control - * @param {number} [options.clickTolerance=10] - Distance tolerance to tigger click / tap event - * @param {number} [options.cameraFov=60] - Camera field of view value - * @param {boolean} [options.reverseDragging=false] - Reverse dragging direction - * @param {boolean} [options.enableReticle=false] - Enable reticle for mouseless interaction other than VR mode - * @param {number} [options.dwellTime=1500] - Dwell time for reticle selection in ms - * @param {boolean} [options.autoReticleSelect=true] - Auto select a clickable target after dwellTime - * @param {boolean} [options.viewIndicator=false] - Adds an angle view indicator in upper left corner - * @param {number} [options.indicatorSize=30] - Size of View Indicator - * @param {string} [options.output='none'] - Whether and where to output raycast position. Could be 'event', 'console' or 'overlay'. - * @param {boolean} [options.autoRotate=false] - Auto rotate - * @param {number} [options.autoRotateSpeed=2.0] - Auto rotate speed as in degree per second. Positive is counter-clockwise and negative is clockwise. - * @param {number} [options.autoRotateActivationDuration=5000] - Duration before auto rotatation when no user interactivity in ms - */ - function Viewer ( options ) { - - let container; - - options = options || {}; - options.controlBar = options.controlBar !== undefined ? options.controlBar : true; - options.controlButtons = options.controlButtons || [ 'fullscreen', 'setting', 'video' ]; - options.autoHideControlBar = options.autoHideControlBar !== undefined ? options.autoHideControlBar : false; - options.autoHideInfospot = options.autoHideInfospot !== undefined ? options.autoHideInfospot : true; - options.horizontalView = options.horizontalView !== undefined ? options.horizontalView : false; - options.clickTolerance = options.clickTolerance || 10; - options.cameraFov = options.cameraFov || 60; - options.reverseDragging = options.reverseDragging || false; - options.enableReticle = options.enableReticle || false; - options.dwellTime = options.dwellTime || 1500; - options.autoReticleSelect = options.autoReticleSelect !== undefined ? options.autoReticleSelect : true; - options.viewIndicator = options.viewIndicator !== undefined ? options.viewIndicator : false; - options.indicatorSize = options.indicatorSize || 30; - options.output = options.output ? options.output : 'none'; - options.autoRotate = options.autoRotate || false; - options.autoRotateSpeed = options.autoRotateSpeed || 2.0; - options.autoRotateActivationDuration = options.autoRotateActivationDuration || 5000; - - this.options = options; - - /* - * CSS Icon - * const styleLoader = new StyleLoader(); - * styleLoader.inject( 'icono' ); - */ - - // Container - if ( options.container ) { - - container = options.container; - container._width = container.clientWidth; - container._height = container.clientHeight; - - } else { - - container = document.createElement( 'div' ); - container.classList.add( 'panolens-container' ); - container.style.width = '100%'; - container.style.height = '100%'; - container._width = window.innerWidth; - container._height = window.innerHeight; - document.body.appendChild( container ); - - } - - this.container = container; - - this.camera = options.camera || new THREE.PerspectiveCamera( this.options.cameraFov, this.container.clientWidth / this.container.clientHeight, 1, 10000 ); - this.scene = options.scene || new THREE.Scene(); - this.renderer = options.renderer || new THREE.WebGLRenderer( { alpha: true, antialias: false } ); - this.sceneReticle = new THREE.Scene(); - - this.viewIndicatorSize = this.options.indicatorSize; - - this.reticle = {}; - this.tempEnableReticle = this.options.enableReticle; - - this.mode = MODES.NORMAL; - - this.panorama = null; - this.widget = null; - - this.hoverObject = null; - this.infospot = null; - this.pressEntityObject = null; - this.pressObject = null; - - this.raycaster = new THREE.Raycaster(); - this.raycasterPoint = new THREE.Vector2(); - this.userMouse = new THREE.Vector2(); - this.updateCallbacks = []; - this.requestAnimationId = null; - - this.cameraFrustum = new THREE.Frustum(); - this.cameraViewProjectionMatrix = new THREE.Matrix4(); - - this.autoRotateRequestId = null; - - this.outputDivElement = null; - - this.touchSupported = 'ontouchstart' in window || window.DocumentTouch && document instanceof DocumentTouch; - - // Handler references - this.HANDLER_MOUSE_DOWN = this.onMouseDown.bind( this ); - this.HANDLER_MOUSE_UP = this.onMouseUp.bind( this ); - this.HANDLER_MOUSE_MOVE = this.onMouseMove.bind( this ); - this.HANDLER_WINDOW_RESIZE = this.onWindowResize.bind( this ); - this.HANDLER_KEY_DOWN = this.onKeyDown.bind( this ); - this.HANDLER_KEY_UP = this.onKeyUp.bind( this ); - this.HANDLER_TAP = this.onTap.bind( this, { - clientX: this.container.clientWidth / 2, - clientY: this.container.clientHeight / 2 - } ); - - // Flag for infospot output - this.OUTPUT_INFOSPOT = false; - - // Animations - this.tweenLeftAnimation = new Tween.Tween(); - this.tweenUpAnimation = new Tween.Tween(); - - // Renderer - this.renderer.setPixelRatio( window.devicePixelRatio ); - this.renderer.setSize( this.container.clientWidth, this.container.clientHeight ); - this.renderer.setClearColor( 0x000000, 0 ); - this.renderer.autoClear = false; - - // Append Renderer Element to container - this.renderer.domElement.classList.add( 'panolens-canvas' ); - this.renderer.domElement.style.display = 'block'; - this.container.style.backgroundColor = '#000'; - this.container.appendChild( this.renderer.domElement ); - - // Camera Controls - this.OrbitControls = new OrbitControls( this.camera, this.container ); - this.OrbitControls.id = 'orbit'; - this.OrbitControls.minDistance = 1; - this.OrbitControls.noPan = true; - this.OrbitControls.autoRotate = this.options.autoRotate; - this.OrbitControls.autoRotateSpeed = this.options.autoRotateSpeed; - - this.DeviceOrientationControls = new DeviceOrientationControls( this.camera, this.container ); - this.DeviceOrientationControls.id = 'device-orientation'; - this.DeviceOrientationControls.enabled = false; - this.camera.position.z = 1; - - // Register change event if passiveRenering - if ( this.options.passiveRendering ) { - - console.warn( 'passiveRendering is now deprecated' ); - - } - - // Controls - this.controls = [ this.OrbitControls, this.DeviceOrientationControls ]; - this.control = this.OrbitControls; - - // Cardboard effect - this.CardboardEffect = new CardboardEffect( this.renderer ); - this.CardboardEffect.setSize( this.container.clientWidth, this.container.clientHeight ); - - // Stereo effect - this.StereoEffect = new StereoEffect( this.renderer ); - this.StereoEffect.setSize( this.container.clientWidth, this.container.clientHeight ); - - this.effect = this.CardboardEffect; - - // Add default hidden reticle - this.addReticle(); - - // Lock horizontal view - if ( this.options.horizontalView ) { - this.OrbitControls.minPolarAngle = Math.PI / 2; - this.OrbitControls.maxPolarAngle = Math.PI / 2; - } - - // Add Control UI - if ( this.options.controlBar !== false ) { - this.addDefaultControlBar( this.options.controlButtons ); - } - - // Add View Indicator - if ( this.options.viewIndicator ) { - this.addViewIndicator(); - } - - // Reverse dragging direction - if ( this.options.reverseDragging ) { - this.reverseDraggingDirection(); - } - - // Register event if reticle is enabled, otherwise defaults to mouse - if ( this.options.enableReticle ) { - this.enableReticleControl(); - } else { - this.registerMouseAndTouchEvents(); - } - - // Output infospot position to an overlay container if specified - if ( this.options.output === 'overlay' ) { - this.addOutputElement(); - } - - // Register dom event listeners - this.registerEventListeners(); - - // Animate - this.animate.call( this ); - - } - Viewer.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype ), { - - constructor: Viewer, - - /** - * Add an object to the scene - * Automatically hookup with panolens-viewer-handler listener - * to communicate with viewer method - * @param {THREE.Object3D} object - The object to be added - * @memberOf Viewer - * @instance - */ - add: function ( object ) { - - if ( arguments.length > 1 ) { - - for ( let i = 0; i < arguments.length; i ++ ) { - - this.add( arguments[ i ] ); - - } - - return this; - - } - - this.scene.add( object ); - - // All object added to scene has 'panolens-viewer-handler' event to handle viewer communication - if ( object.addEventListener ) { - - object.addEventListener( 'panolens-viewer-handler', this.eventHandler.bind( this ) ); - - } - - // All object added to scene being passed with container - if ( object instanceof Panorama && object.dispatchEvent ) { - - object.dispatchEvent( { type: 'panolens-container', container: this.container } ); - - } - - if ( object instanceof CameraPanorama ) { - - object.dispatchEvent( { type: 'panolens-scene', scene: this.scene } ); - - } - - // Hookup default panorama event listeners - if ( object.type === 'panorama' ) { - - this.addPanoramaEventListener( object ); - - if ( !this.panorama ) { - - this.setPanorama( object ); - - } - - } - - }, - - /** - * Remove an object from the scene - * @param {THREE.Object3D} object - Object to be removed - * @memberOf Viewer - * @instance - */ - remove: function ( object ) { - - if ( object.removeEventListener ) { - - object.removeEventListener( 'panolens-viewer-handler', this.eventHandler.bind( this ) ); - - } - - this.scene.remove( object ); - - }, - - /** - * Add default control bar - * @param {array} array - The control buttons array - * @memberOf Viewer - * @instance - */ - addDefaultControlBar: function ( array ) { - - if ( this.widget ) { - - console.warn( 'Default control bar exists' ); - return; - - } - - const widget = new Widget( this.container ); - widget.addEventListener( 'panolens-viewer-handler', this.eventHandler.bind( this ) ); - widget.addControlBar(); - array.forEach( buttonName => { - - widget.addControlButton( buttonName ); - - } ); - - this.widget = widget; - - }, - - /** - * Set a panorama to be the current one - * @param {Panorama} pano - Panorama to be set - * @memberOf Viewer - * @instance - */ - setPanorama: function ( pano ) { - - const leavingPanorama = this.panorama; - - if ( pano.type === 'panorama' && leavingPanorama !== pano ) { - - // Clear exisiting infospot - this.hideInfospot(); - - const afterEnterComplete = function () { - - if ( leavingPanorama ) { leavingPanorama.onLeave(); } - pano.removeEventListener( 'enter-fade-start', afterEnterComplete ); - - }; - - pano.addEventListener( 'enter-fade-start', afterEnterComplete ); - - // Assign and enter panorama - (this.panorama = pano).onEnter(); - - } - - }, - - /** - * Event handler to execute commands from child objects - * @param {object} event - The dispatched event with method as function name and data as an argument - * @memberOf Viewer - * @instance - */ - eventHandler: function ( event ) { - - if ( event.method && this[ event.method ] ) { - - this[ event.method ]( event.data ); - - } - - }, - - /** - * Dispatch event to all descendants - * @param {object} event - Event to be passed along - * @memberOf Viewer - * @instance - */ - dispatchEventToChildren: function ( event ) { - - this.scene.traverse( function ( object ) { - - if ( object.dispatchEvent ) { - - object.dispatchEvent( event ); - - } - - }); - - }, - - /** - * Set widget content - * @method activateWidgetItem - * @param {integer} controlIndex - Control index - * @param {integer} mode - Modes for effects - * @memberOf Viewer - * @instance - */ - activateWidgetItem: function ( controlIndex, mode ) { - - const mainMenu = this.widget.mainMenu; - const ControlMenuItem = mainMenu.children[ 0 ]; - const ModeMenuItem = mainMenu.children[ 1 ]; - - let item; - - if ( controlIndex !== undefined ) { - - switch ( controlIndex ) { - - case 0: - - item = ControlMenuItem.subMenu.children[ 1 ]; - - break; - - case 1: - - item = ControlMenuItem.subMenu.children[ 2 ]; - - break; - - default: - - item = ControlMenuItem.subMenu.children[ 1 ]; - - break; - - } - - ControlMenuItem.subMenu.setActiveItem( item ); - ControlMenuItem.setSelectionTitle( item.textContent ); - - } - - if ( mode !== undefined ) { - - switch( mode ) { - - case MODES.CARDBOARD: - - item = ModeMenuItem.subMenu.children[ 2 ]; - - break; - - case MODES.STEREO: - - item = ModeMenuItem.subMenu.children[ 3 ]; - - break; - - default: - - item = ModeMenuItem.subMenu.children[ 1 ]; - - break; - } - - ModeMenuItem.subMenu.setActiveItem( item ); - ModeMenuItem.setSelectionTitle( item.textContent ); - - } - - }, - - /** - * Enable rendering effect - * @param {MODES} mode - Modes for effects - * @memberOf Viewer - * @instance - */ - enableEffect: function ( mode ) { - - if ( this.mode === mode ) { return; } - if ( mode === MODES.NORMAL ) { this.disableEffect(); return; } - else { this.mode = mode; } - - const fov = this.camera.fov; - - switch( mode ) { - - case MODES.CARDBOARD: - - this.effect = this.CardboardEffect; - this.enableReticleControl(); - - break; - - case MODES.STEREO: - - this.effect = this.StereoEffect; - this.enableReticleControl(); - - break; - - default: - - this.effect = null; - this.disableReticleControl(); - - break; - - } - - this.activateWidgetItem( undefined, this.mode ); - - /** - * Dual eye effect event - * @type {object} - * @event Infospot#panolens-dual-eye-effect - * @property {MODES} mode - Current display mode - */ - this.dispatchEventToChildren( { type: 'panolens-dual-eye-effect', mode: this.mode } ); - - // Force effect stereo camera to update by refreshing fov - this.camera.fov = fov + 10e-3; - this.effect.setSize( this.container.clientWidth, this.container.clientHeight ); - this.render(); - this.camera.fov = fov; - - /** - * Dispatch mode change event - * @type {object} - * @event Viewer#mode-change - * @property {MODES} mode - Current display mode - */ - this.dispatchEvent( { type: 'mode-change', mode: this.mode } ); - - }, - - /** - * Disable additional rendering effect - * @memberOf Viewer - * @instance - */ - disableEffect: function () { - - if ( this.mode === MODES.NORMAL ) { return; } - - this.mode = MODES.NORMAL; - this.disableReticleControl(); - - this.activateWidgetItem( undefined, this.mode ); - - /** - * Dual eye effect event - * @type {object} - * @event Infospot#panolens-dual-eye-effect - * @property {MODES} mode - Current display mode - */ - this.dispatchEventToChildren( { type: 'panolens-dual-eye-effect', mode: this.mode } ); - - this.renderer.setSize( this.container.clientWidth, this.container.clientHeight ); - this.render(); - - /** - * Dispatch mode change event - * @type {object} - * @event Viewer#mode-change - * @property {MODES} mode - Current display mode - */ - this.dispatchEvent( { type: 'mode-change', mode: this.mode } ); - }, - - /** - * Enable reticle control - * @memberOf Viewer - * @instance - */ - enableReticleControl: function () { - - if ( this.reticle.visible ) { return; } - - this.tempEnableReticle = true; - - // Register reticle event and unregister mouse event - this.unregisterMouseAndTouchEvents(); - this.reticle.show(); - this.registerReticleEvent(); - this.updateReticleEvent(); - - }, - - /** - * Disable reticle control - * @memberOf Viewer - * @instance - */ - disableReticleControl: function () { - - this.tempEnableReticle = false; - - // Register mouse event and unregister reticle event - if ( !this.options.enableReticle ) { - - this.reticle.hide(); - this.unregisterReticleEvent(); - this.registerMouseAndTouchEvents(); - - } else { - - this.updateReticleEvent(); - - } - - }, - - /** - * Enable auto rotation - * @memberOf Viewer - * @instance - */ - enableAutoRate: function () { - - this.options.autoRotate = true; - this.OrbitControls.autoRotate = true; - - }, - - /** - * Disable auto rotation - * @memberOf Viewer - * @instance - */ - disableAutoRate: function () { - - clearTimeout( this.autoRotateRequestId ); - this.options.autoRotate = false; - this.OrbitControls.autoRotate = false; - - }, - - /** - * Toggle video play or stop - * @param {boolean} pause - * @memberOf Viewer - * @instance - * @fires Viewer#video-toggle - */ - toggleVideoPlay: function ( pause ) { - - if ( this.panorama instanceof VideoPanorama ) { - - /** - * Toggle video event - * @type {object} - * @event Viewer#video-toggle - */ - this.panorama.dispatchEvent( { type: 'video-toggle', pause: pause } ); - - } - - }, - - /** - * Set currentTime in a video - * @param {number} percentage - Percentage of a video. Range from 0.0 to 1.0 - * @memberOf Viewer - * @instance - * @fires Viewer#video-time - */ - setVideoCurrentTime: function ( percentage ) { - - if ( this.panorama instanceof VideoPanorama ) { - - /** - * Setting video time event - * @type {object} - * @event Viewer#video-time - * @property {number} percentage - Percentage of a video. Range from 0.0 to 1.0 - */ - this.panorama.dispatchEvent( { type: 'video-time', percentage: percentage } ); - - } - - }, - - /** - * This will be called when video updates if an widget is present - * @param {number} percentage - Percentage of a video. Range from 0.0 to 1.0 - * @memberOf Viewer - * @instance - * @fires Viewer#video-update - */ - onVideoUpdate: function ( percentage ) { - - const { widget } = this; - - /** - * Video update event - * @type {object} - * @event Viewer#video-update - * @property {number} percentage - Percentage of a video. Range from 0.0 to 1.0 - */ - if( widget ) { widget.dispatchEvent( { type: 'video-update', percentage: percentage } ); } - - }, - - /** - * Add update callback to be called every animation frame - * @param {function} callback - * @memberOf Viewer - * @instance - */ - addUpdateCallback: function ( fn ) { - - if ( fn ) { - - this.updateCallbacks.push( fn ); - - } - - }, - - /** - * Remove update callback - * @param {function} fn - The function to be removed - * @memberOf Viewer - * @instance - */ - removeUpdateCallback: function ( fn ) { - - const index = this.updateCallbacks.indexOf( fn ); - - if ( fn && index >= 0 ) { - - this.updateCallbacks.splice( index, 1 ); - - } - - }, - - /** - * Show video widget - * @memberOf Viewer - * @instance - */ - showVideoWidget: function () { - - const { widget } = this; - - /** - * Show video widget event - * @type {object} - * @event Viewer#video-control-show - */ - if( widget ) { widget.dispatchEvent( { type: 'video-control-show' } ); } - - }, - - /** - * Hide video widget - * @memberOf Viewer - * @instance - */ - hideVideoWidget: function () { - - const { widget } = this; - - /** - * Hide video widget - * @type {object} - * @event Viewer#video-control-hide - */ - if( widget ) { widget.dispatchEvent( { type: 'video-control-hide' } ); } - - }, - - /** - * Update video play button - * @param {boolean} paused - * @memberOf Viewer - * @instance - */ - updateVideoPlayButton: function ( paused ) { - - const { widget } = this; - - if ( widget && widget.videoElement && widget.videoElement.controlButton ) { - - widget.videoElement.controlButton.update( paused ); - - } - - }, - - /** - * Add default panorama event listeners - * @param {Panorama} pano - The panorama to be added with event listener - * @memberOf Viewer - * @instance - */ - addPanoramaEventListener: function ( pano ) { - - // Set camera control on every panorama - pano.addEventListener( 'enter-fade-start', this.setCameraControl.bind( this ) ); - - // Show and hide widget event only when it's VideoPanorama - if ( pano instanceof VideoPanorama ) { - - pano.addEventListener( 'enter-fade-start', this.showVideoWidget.bind( this ) ); - pano.addEventListener( 'leave', function () { - - if ( !(this.panorama instanceof VideoPanorama) ) { - - this.hideVideoWidget.call( this ); - - } - - }.bind( this ) ); - - } - - }, - - /** - * Set camera control - * @memberOf Viewer - * @instance - */ - setCameraControl: function () { - - this.OrbitControls.target.copy( this.panorama.position ); - - }, - - /** - * Get current camera control - * @return {object} - Current navigation control - * @memberOf Viewer - * @instance - * @returns {THREE.OrbitControls|THREE.DeviceOrientationControls} - */ - getControl: function () { - - return this.control; - - }, - - /** - * Get scene - * @memberOf Viewer - * @instance - * @return {THREE.Scene} - Current scene which the viewer is built on - */ - getScene: function () { - - return this.scene; - - }, - - /** - * Get camera - * @memberOf Viewer - * @instance - * @return {THREE.Camera} - The scene camera - */ - getCamera: function () { - - return this.camera; - - }, - - /** - * Get renderer - * @memberOf Viewer - * @instance - * @return {THREE.WebGLRenderer} - The renderer using webgl - */ - getRenderer: function () { - - return this.renderer; - - }, - - /** - * Get container - * @memberOf Viewer - * @instance - * @return {HTMLElement} - The container holds rendererd canvas - */ - getContainer: function () { - - return this.container; - - }, - - /** - * Get control id - * @memberOf Viewer - * @instance - * @return {string} - Control id. 'orbit' or 'device-orientation' - */ - getControlId: function () { - - return this.control.id; - - }, - - /** - * Get next navigation control id - * @memberOf Viewer - * @instance - * @return {string} - Next control id - */ - getNextControlId: function () { - - return this.controls[ this.getNextControlIndex() ].id; - - }, - - /** - * Get next navigation control index - * @memberOf Viewer - * @instance - * @return {number} - Next control index - */ - getNextControlIndex: function () { - - const controls = this.controls; - const control = this.control; - const nextIndex = controls.indexOf( control ) + 1; - - return ( nextIndex >= controls.length ) ? 0 : nextIndex; - - }, - - /** - * Set field of view of camera - * @param {number} fov - * @memberOf Viewer - * @instance - */ - setCameraFov: function ( fov ) { - - this.camera.fov = fov; - this.camera.updateProjectionMatrix(); - - }, - - /** - * Enable control by index - * @param {CONTROLS} index - Index of camera control - * @memberOf Viewer - * @instance - */ - enableControl: function ( index ) { - - index = ( index >= 0 && index < this.controls.length ) ? index : 0; - - this.control.enabled = false; - - this.control = this.controls[ index ]; - - this.control.enabled = true; - - switch ( index ) { - - case CONTROLS.ORBIT: - - this.camera.position.copy( this.panorama.position ); - this.camera.position.z += 1; - - break; - - case CONTROLS.DEVICEORIENTATION: - - this.camera.position.copy( this.panorama.position ); - - break; - - default: - - break; - } - - this.control.update(); - - this.activateWidgetItem( index, undefined ); - - }, - - /** - * Disable current control - * @memberOf Viewer - * @instance - */ - disableControl: function () { - - this.control.enabled = false; - - }, - - /** - * Toggle next control - * @memberOf Viewer - * @instance - */ - toggleNextControl: function () { - - this.enableControl( this.getNextControlIndex() ); - - }, - - /** - * Screen Space Projection - * @memberOf Viewer - * @instance - */ - getScreenVector: function ( worldVector ) { - - const vector = worldVector.clone(); - const widthHalf = ( this.container.clientWidth ) / 2; - const heightHalf = this.container.clientHeight / 2; - - vector.project( this.camera ); - - vector.x = ( vector.x * widthHalf ) + widthHalf; - vector.y = - ( vector.y * heightHalf ) + heightHalf; - vector.z = 0; - - return vector; - - }, - - /** - * Check Sprite in Viewport - * @memberOf Viewer - * @instance - */ - checkSpriteInViewport: function ( sprite ) { - - this.camera.matrixWorldInverse.getInverse( this.camera.matrixWorld ); - this.cameraViewProjectionMatrix.multiplyMatrices( this.camera.projectionMatrix, this.camera.matrixWorldInverse ); - this.cameraFrustum.setFromMatrix( this.cameraViewProjectionMatrix ); - - return sprite.visible && this.cameraFrustum.intersectsSprite( sprite ); - - }, - - /** - * Reverse dragging direction - * @memberOf Viewer - * @instance - */ - reverseDraggingDirection: function () { - - this.OrbitControls.rotateSpeed *= -1; - this.OrbitControls.momentumScalingFactor *= -1; - - }, - - /** - * Add reticle - * @memberOf Viewer - * @instance - */ - addReticle: function () { - - this.reticle = new Reticle( 0xffffff, true, this.options.dwellTime ); - this.reticle.hide(); - this.camera.add( this.reticle ); - this.sceneReticle.add( this.camera ); - - }, - - /** - * Tween control looking center - * @param {THREE.Vector3} vector - Vector to be looked at the center - * @param {number} [duration=1000] - Duration to tween - * @param {function} [easing=TWEEN.Easing.Exponential.Out] - Easing function - * @memberOf Viewer - * @instance - */ - tweenControlCenter: function ( vector, duration, easing ) { - - if ( this.control !== this.OrbitControls ) { - - return; - - } - - // Pass in arguments as array - if ( vector instanceof Array ) { - - duration = vector[ 1 ]; - easing = vector[ 2 ]; - vector = vector[ 0 ]; - - } - - duration = duration !== undefined ? duration : 1000; - easing = easing || Tween.Easing.Exponential.Out; - - let scope, ha, va, chv, cvv, hv, vv, vptc, ov, nv; - - scope = this; - - chv = this.camera.getWorldDirection( new THREE.Vector3() ); - cvv = chv.clone(); - - vptc = this.panorama.getWorldPosition( new THREE.Vector3() ).sub( this.camera.getWorldPosition( new THREE.Vector3() ) ); - - hv = vector.clone(); - // Scale effect - hv.x *= -1; - hv.add( vptc ).normalize(); - vv = hv.clone(); - - chv.y = 0; - hv.y = 0; - - ha = Math.atan2( hv.z, hv.x ) - Math.atan2( chv.z, chv.x ); - ha = ha > Math.PI ? ha - 2 * Math.PI : ha; - ha = ha < -Math.PI ? ha + 2 * Math.PI : ha; - va = Math.abs( cvv.angleTo( chv ) + ( cvv.y * vv.y <= 0 ? vv.angleTo( hv ) : -vv.angleTo( hv ) ) ); - va *= vv.y < cvv.y ? 1 : -1; - - ov = { left: 0, up: 0 }; - nv = { left: 0, up: 0 }; - - this.tweenLeftAnimation.stop(); - this.tweenUpAnimation.stop(); - - this.tweenLeftAnimation = new Tween.Tween( ov ) - .to( { left: ha }, duration ) - .easing( easing ) - .onUpdate(function(ov){ - scope.control.rotateLeft( ov.left - nv.left ); - nv.left = ov.left; - }) - .start(); - - this.tweenUpAnimation = new Tween.Tween( ov ) - .to( { up: va }, duration ) - .easing( easing ) - .onUpdate(function(ov){ - scope.control.rotateUp( ov.up - nv.up ); - nv.up = ov.up; - }) - .start(); - - }, - - /** - * Tween control looking center by object - * @param {THREE.Object3D} object - Object to be looked at the center - * @param {number} [duration=1000] - Duration to tween - * @param {function} [easing=TWEEN.Easing.Exponential.Out] - Easing function - * @memberOf Viewer - * @instance - */ - tweenControlCenterByObject: function ( object, duration, easing ) { - - let isUnderScalePlaceHolder = false; - - object.traverseAncestors( function ( ancestor ) { - - if ( ancestor.scalePlaceHolder ) { - - isUnderScalePlaceHolder = true; - - } - } ); - - if ( isUnderScalePlaceHolder ) { - - const invertXVector = new THREE.Vector3( -1, 1, 1 ); - - this.tweenControlCenter( object.getWorldPosition( new THREE.Vector3() ).multiply( invertXVector ), duration, easing ); - - } else { - - this.tweenControlCenter( object.getWorldPosition( new THREE.Vector3() ), duration, easing ); - - } - - }, - - /** - * This is called when window size is changed - * @fires Viewer#window-resize - * @param {number} [windowWidth] - Specify if custom element has changed width - * @param {number} [windowHeight] - Specify if custom element has changed height - * @memberOf Viewer - * @instance - */ - onWindowResize: function ( windowWidth, windowHeight ) { - - let width, height; - - const expand = this.container.classList.contains( 'panolens-container' ) || this.container.isFullscreen; - - if ( windowWidth !== undefined && windowHeight !== undefined ) { - - width = windowWidth; - height = windowHeight; - this.container._width = windowWidth; - this.container._height = windowHeight; - - } else { - - const isAndroid = /(android)/i.test(window.navigator.userAgent); - - const adjustWidth = isAndroid - ? Math.min(document.documentElement.clientWidth, window.innerWidth || 0) - : Math.max(document.documentElement.clientWidth, window.innerWidth || 0); - - const adjustHeight = isAndroid - ? Math.min(document.documentElement.clientHeight, window.innerHeight || 0) - : Math.max(document.documentElement.clientHeight, window.innerHeight || 0); - - width = expand ? adjustWidth : this.container.clientWidth; - height = expand ? adjustHeight : this.container.clientHeight; - - this.container._width = width; - this.container._height = height; - - } - - this.camera.aspect = width / height; - this.camera.updateProjectionMatrix(); - - this.renderer.setSize( width, height ); - - // Update reticle - if ( this.options.enableReticle || this.tempEnableReticle ) { - - this.updateReticleEvent(); - - } - - /** - * Window resizing event - * @type {object} - * @event Viewer#window-resize - * @property {number} width - Width of the window - * @property {number} height - Height of the window - */ - this.dispatchEvent( { type: 'window-resize', width: width, height: height }); - this.scene.traverse( function ( object ) { - - if ( object.dispatchEvent ) { - - object.dispatchEvent( { type: 'window-resize', width: width, height: height }); - - } - - } ); - - }, - - /** - * Add output element - * @memberOf Viewer - * @instance - */ - addOutputElement: function () { - - const element = document.createElement( 'div' ); - element.style.position = 'absolute'; - element.style.right = '10px'; - element.style.top = '10px'; - element.style.color = '#fff'; - this.container.appendChild( element ); - this.outputDivElement = element; - - }, - - /** - * Output position in developer console by holding down Ctrl button - * @memberOf Viewer - * @instance - */ - outputPosition: function () { - - const intersects = this.raycaster.intersectObject( this.panorama, true ); - - if ( intersects.length > 0 ) { - - const point = intersects[ 0 ].point.clone(); - const converter = new THREE.Vector3( -1, 1, 1 ); - const world = this.panorama.getWorldPosition( new THREE.Vector3() ); - point.sub( world ).multiply( converter ); - - const position = { - x: point.x.toFixed(2), - y: point.y.toFixed(2), - z: point.z.toFixed(2), - }; - - const message = `${position.x}, ${position.y}, ${position.z}`; - - if ( point.length() === 0 ) { return; } - - switch ( this.options.output ) { - - case 'event': - /** - * Dispatch raycast position as event - * @type {object} - * @event Viewer#position-output - */ - this.dispatchEvent( { type: 'position-output', position: position } ); - break; - - case 'console': - console.info( message ); - break; - - case 'overlay': - this.outputDivElement.textContent = message; - break; - - default: - break; - - } - - } - - }, - - /** - * On mouse down - * @param {MouseEvent} event - * @memberOf Viewer - * @instance - */ - onMouseDown: function ( event ) { - - event.preventDefault(); - - this.userMouse.x = ( event.clientX >= 0 ) ? event.clientX : event.touches[0].clientX; - this.userMouse.y = ( event.clientY >= 0 ) ? event.clientY : event.touches[0].clientY; - this.userMouse.type = 'mousedown'; - this.onTap( event ); - - }, - - /** - * On mouse move - * @param {MouseEvent} event - * @memberOf Viewer - * @instance - */ - onMouseMove: function ( event ) { - - event.preventDefault(); - this.userMouse.type = 'mousemove'; - this.onTap( event ); - - }, - - /** - * On mouse up - * @param {MouseEvent} event - * @memberOf Viewer - * @instance - */ - onMouseUp: function ( event ) { - - let onTarget = false; - - this.userMouse.type = 'mouseup'; - - const type = ( this.userMouse.x >= event.clientX - this.options.clickTolerance - && this.userMouse.x <= event.clientX + this.options.clickTolerance - && this.userMouse.y >= event.clientY - this.options.clickTolerance - && this.userMouse.y <= event.clientY + this.options.clickTolerance ) - || ( event.changedTouches - && this.userMouse.x >= event.changedTouches[0].clientX - this.options.clickTolerance - && this.userMouse.x <= event.changedTouches[0].clientX + this.options.clickTolerance - && this.userMouse.y >= event.changedTouches[0].clientY - this.options.clickTolerance - && this.userMouse.y <= event.changedTouches[0].clientY + this.options.clickTolerance ) - ? 'click' : undefined; - - // Event should happen on canvas - if ( event && event.target && !event.target.classList.contains( 'panolens-canvas' ) ) { return; } - - event.preventDefault(); - - if ( event.changedTouches && event.changedTouches.length === 1 ) { - - onTarget = this.onTap( { clientX: event.changedTouches[0].clientX, clientY: event.changedTouches[0].clientY }, type ); - - } else { - - onTarget = this.onTap( event, type ); - - } - - this.userMouse.type = 'none'; - - if ( onTarget ) { - - return; - - } - - if ( type === 'click' ) { - - const { options: { autoHideInfospot, autoHideControlBar }, panorama, toggleControlBar } = this; - - if ( autoHideInfospot && panorama ) { - - panorama.toggleInfospotVisibility(); - - } - - if ( autoHideControlBar ) { - - toggleControlBar(); - - } - - } - - }, - - /** - * On tap eveny frame - * @param {MouseEvent} event - * @param {string} type - * @memberOf Viewer - * @instance - */ - onTap: function ( event, type ) { - - const { left, top } = this.container.getBoundingClientRect(); - const { clientWidth, clientHeight } = this.container; - - this.raycasterPoint.x = ( ( event.clientX - left ) / clientWidth ) * 2 - 1; - this.raycasterPoint.y = - ( ( event.clientY - top ) / clientHeight ) * 2 + 1; - - this.raycaster.setFromCamera( this.raycasterPoint, this.camera ); - - // Return if no panorama - if ( !this.panorama ) { - - return; - - } - - // output infospot information - if ( event.type !== 'mousedown' && this.touchSupported || this.OUTPUT_INFOSPOT ) { - - this.outputPosition(); - - } - - - const intersects = this.raycaster.intersectObjects( this.panorama.children, true ); - const intersect_entity = this.getConvertedIntersect( intersects ); - const intersect = ( intersects.length > 0 ) ? intersects[0].object : undefined; - - if ( this.userMouse.type === 'mouseup' ) { - - if ( intersect_entity && this.pressEntityObject === intersect_entity && this.pressEntityObject.dispatchEvent ) { - - this.pressEntityObject.dispatchEvent( { type: 'pressstop-entity', mouseEvent: event } ); - - } - - this.pressEntityObject = undefined; - - } - - if ( this.userMouse.type === 'mouseup' ) { - - if ( intersect && this.pressObject === intersect && this.pressObject.dispatchEvent ) { - - this.pressObject.dispatchEvent( { type: 'pressstop', mouseEvent: event } ); - - } - - this.pressObject = undefined; - - } - - if ( type === 'click' ) { - - this.panorama.dispatchEvent( { type: 'click', intersects: intersects, mouseEvent: event } ); - - if ( intersect_entity && intersect_entity.dispatchEvent ) { - - intersect_entity.dispatchEvent( { type: 'click-entity', mouseEvent: event } ); - - } - - if ( intersect && intersect.dispatchEvent ) { - - intersect.dispatchEvent( { type: 'click', mouseEvent: event } ); - - } - - } else { - - this.panorama.dispatchEvent( { type: 'hover', intersects: intersects, mouseEvent: event } ); - - if ( ( this.hoverObject && intersects.length > 0 && this.hoverObject !== intersect_entity ) - || ( this.hoverObject && intersects.length === 0 ) ){ - - if ( this.hoverObject.dispatchEvent ) { - - this.hoverObject.dispatchEvent( { type: 'hoverleave', mouseEvent: event } ); - - this.reticle.end(); - - } - - this.hoverObject = undefined; - - } - - if ( intersect_entity && intersects.length > 0 ) { - - if ( this.hoverObject !== intersect_entity ) { - - this.hoverObject = intersect_entity; - - if ( this.hoverObject.dispatchEvent ) { - - this.hoverObject.dispatchEvent( { type: 'hoverenter', mouseEvent: event } ); - - // Start reticle timer - if ( this.options.autoReticleSelect && this.options.enableReticle || this.tempEnableReticle ) { - this.reticle.start( this.onTap.bind( this, event, 'click' ) ); - } - - } - - } - - if ( this.userMouse.type === 'mousedown' && this.pressEntityObject != intersect_entity ) { - - this.pressEntityObject = intersect_entity; - - if ( this.pressEntityObject.dispatchEvent ) { - - this.pressEntityObject.dispatchEvent( { type: 'pressstart-entity', mouseEvent: event } ); - - } - - } - - if ( this.userMouse.type === 'mousedown' && this.pressObject != intersect ) { - - this.pressObject = intersect; - - if ( this.pressObject.dispatchEvent ) { - - this.pressObject.dispatchEvent( { type: 'pressstart', mouseEvent: event } ); - - } - - } - - if ( this.userMouse.type === 'mousemove' || this.options.enableReticle ) { - - if ( intersect && intersect.dispatchEvent ) { - - intersect.dispatchEvent( { type: 'hover', mouseEvent: event } ); - - } - - if ( this.pressEntityObject && this.pressEntityObject.dispatchEvent ) { - - this.pressEntityObject.dispatchEvent( { type: 'pressmove-entity', mouseEvent: event } ); - - } - - if ( this.pressObject && this.pressObject.dispatchEvent ) { - - this.pressObject.dispatchEvent( { type: 'pressmove', mouseEvent: event } ); - - } - - } - - } - - if ( !intersect_entity && this.pressEntityObject && this.pressEntityObject.dispatchEvent ) { - - this.pressEntityObject.dispatchEvent( { type: 'pressstop-entity', mouseEvent: event } ); - - this.pressEntityObject = undefined; - - } - - if ( !intersect && this.pressObject && this.pressObject.dispatchEvent ) { - - this.pressObject.dispatchEvent( { type: 'pressstop', mouseEvent: event } ); - - this.pressObject = undefined; - - } - - } - - // Infospot handler - if ( intersect && intersect instanceof Infospot ) { - - this.infospot = intersect; - - if ( type === 'click' ) { - - return true; - - } - - - } else if ( this.infospot ) { - - this.hideInfospot(); - - } - - // Auto rotate - if ( this.options.autoRotate && this.userMouse.type !== 'mousemove' ) { - - // Auto-rotate idle timer - clearTimeout( this.autoRotateRequestId ); - - if ( this.control === this.OrbitControls ) { - - this.OrbitControls.autoRotate = false; - this.autoRotateRequestId = window.setTimeout( this.enableAutoRate.bind( this ), this.options.autoRotateActivationDuration ); - - } - - } - - }, - - /** - * Get converted intersect - * @param {array} intersects - * @memberOf Viewer - * @instance - */ - getConvertedIntersect: function ( intersects ) { - - let intersect; - - for ( let i = 0; i < intersects.length; i++ ) { - - if ( intersects[i].distance >= 0 && intersects[i].object && !intersects[i].object.passThrough ) { - - if ( intersects[i].object.entity && intersects[i].object.entity.passThrough ) { - continue; - } else if ( intersects[i].object.entity && !intersects[i].object.entity.passThrough ) { - intersect = intersects[i].object.entity; - break; - } else { - intersect = intersects[i].object; - break; - } - - } - - } - - return intersect; - - }, - - /** - * Hide infospot - * @memberOf Viewer - * @instance - */ - hideInfospot: function () { - - if ( this.infospot ) { - - this.infospot.onHoverEnd(); - - this.infospot = undefined; - - } - - }, - - /** - * Toggle control bar - * @memberOf Viewer - * @instance - * @fires Viewer#control-bar-toggle - */ - toggleControlBar: function () { - - const { widget } = this; - - /** - * Toggle control bar event - * @type {object} - * @event Viewer#control-bar-toggle - */ - if ( widget ) { - - widget.dispatchEvent( { type: 'control-bar-toggle' } ); - - } - - }, - - /** - * On key down - * @param {KeyboardEvent} event - * @memberOf Viewer - * @instance - */ - onKeyDown: function ( event ) { - - if ( this.options.output && this.options.output !== 'none' && event.key === 'Control' ) { - - this.OUTPUT_INFOSPOT = true; - - } - - }, - - /** - * On key up - * @param {KeyboardEvent} event - * @memberOf Viewer - * @instance - */ - onKeyUp: function () { - - this.OUTPUT_INFOSPOT = false; - - }, - - /** - * Update control and callbacks - * @memberOf Viewer - * @instance - */ - update: function () { - - Tween.update(); - - this.updateCallbacks.forEach( function( callback ){ callback(); } ); - - this.control.update(); - - this.scene.traverse( function( child ){ - if ( child instanceof Infospot - && child.element - && ( this.hoverObject === child - || child.element.style.display !== 'none' - || (child.element.left && child.element.left.style.display !== 'none') - || (child.element.right && child.element.right.style.display !== 'none') ) ) { - if ( this.checkSpriteInViewport( child ) ) { - const { x, y } = this.getScreenVector( child.getWorldPosition( new THREE.Vector3() ) ); - child.translateElement( x, y ); - } else { - child.onDismiss(); - } - - } - }.bind( this ) ); - - }, - - /** - * Rendering function to be called on every animation frame - * Render reticle last - * @memberOf Viewer - * @instance - */ - render: function () { - - if ( this.mode === MODES.CARDBOARD || this.mode === MODES.STEREO ) { - - this.renderer.clear(); - this.effect.render( this.scene, this.camera ); - this.effect.render( this.sceneReticle, this.camera ); - - - } else { - - this.renderer.clear(); - this.renderer.render( this.scene, this.camera ); - this.renderer.clearDepth(); - this.renderer.render( this.sceneReticle, this.camera ); - - } - - }, - - /** - * Animate - * @memberOf Viewer - * @instance - */ - animate: function () { - - this.requestAnimationId = window.requestAnimationFrame( this.animate.bind( this ) ); - - this.onChange(); - - }, - - /** - * On change - * @memberOf Viewer - * @instance - */ - onChange: function () { - - this.update(); - this.render(); - - }, - - /** - * Register mouse and touch event on container - * @memberOf Viewer - * @instance - */ - registerMouseAndTouchEvents: function () { - - const options = { passive: false }; - - this.container.addEventListener( 'mousedown' , this.HANDLER_MOUSE_DOWN, options ); - this.container.addEventListener( 'mousemove' , this.HANDLER_MOUSE_MOVE, options ); - this.container.addEventListener( 'mouseup' , this.HANDLER_MOUSE_UP , options ); - this.container.addEventListener( 'touchstart', this.HANDLER_MOUSE_DOWN, options ); - this.container.addEventListener( 'touchend' , this.HANDLER_MOUSE_UP , options ); - - }, - - /** - * Unregister mouse and touch event on container - * @memberOf Viewer - * @instance - */ - unregisterMouseAndTouchEvents: function () { - - this.container.removeEventListener( 'mousedown' , this.HANDLER_MOUSE_DOWN, false ); - this.container.removeEventListener( 'mousemove' , this.HANDLER_MOUSE_MOVE, false ); - this.container.removeEventListener( 'mouseup' , this.HANDLER_MOUSE_UP , false ); - this.container.removeEventListener( 'touchstart', this.HANDLER_MOUSE_DOWN, false ); - this.container.removeEventListener( 'touchend' , this.HANDLER_MOUSE_UP , false ); - - }, - - /** - * Register reticle event - * @memberOf Viewer - * @instance - */ - registerReticleEvent: function () { - - this.addUpdateCallback( this.HANDLER_TAP ); - - }, - - /** - * Unregister reticle event - * @memberOf Viewer - * @instance - */ - unregisterReticleEvent: function () { - - this.removeUpdateCallback( this.HANDLER_TAP ); - - }, - - /** - * Update reticle event - * @memberOf Viewer - * @instance - */ - updateReticleEvent: function () { - - const clientX = this.container.clientWidth / 2 + this.container.offsetLeft; - const clientY = this.container.clientHeight / 2; - - this.removeUpdateCallback( this.HANDLER_TAP ); - this.HANDLER_TAP = this.onTap.bind( this, { clientX, clientY } ); - this.addUpdateCallback( this.HANDLER_TAP ); - - }, - - /** - * Register container and window listeners - * @memberOf Viewer - * @instance - */ - registerEventListeners: function () { - - // Resize Event - window.addEventListener( 'resize' , this.HANDLER_WINDOW_RESIZE, true ); - - // Keyboard Event - window.addEventListener( 'keydown', this.HANDLER_KEY_DOWN, true ); - window.addEventListener( 'keyup' , this.HANDLER_KEY_UP , true ); - - }, - - /** - * Unregister container and window listeners - * @memberOf Viewer - * @instance - */ - unregisterEventListeners: function () { - - // Resize Event - window.removeEventListener( 'resize' , this.HANDLER_WINDOW_RESIZE, true ); - - // Keyboard Event - window.removeEventListener( 'keydown', this.HANDLER_KEY_DOWN, true ); - window.removeEventListener( 'keyup' , this.HANDLER_KEY_UP , true ); - - }, - - /** - * Dispose all scene objects and clear cache - * @memberOf Viewer - * @instance - */ - dispose: function () { - - this.tweenLeftAnimation.stop(); - this.tweenUpAnimation.stop(); - - // Unregister dom event listeners - this.unregisterEventListeners(); - - // recursive disposal on 3d objects - function recursiveDispose ( object ) { - - for ( let i = object.children.length - 1; i >= 0; i-- ) { - - recursiveDispose( object.children[i] ); - object.remove( object.children[i] ); - - } - - if ( object instanceof Panorama || object instanceof Infospot ) { - - object.dispose(); - object = null; - - } else if ( object.dispatchEvent ){ - - object.dispatchEvent( 'dispose' ); - - } - - } - - recursiveDispose( this.scene ); - - // dispose widget - if ( this.widget ) { - - this.widget.dispose(); - this.widget = null; - - } - - // clear cache - if ( THREE.Cache && THREE.Cache.enabled ) { - - THREE.Cache.clear(); - - } - - }, - - /** - * Destroy viewer by disposing and stopping requestAnimationFrame - * @memberOf Viewer - * @instance - */ - destroy: function () { - - this.dispose(); - this.render(); - window.cancelAnimationFrame( this.requestAnimationId ); - - }, - - /** - * On panorama dispose - * @memberOf Viewer - * @instance - */ - onPanoramaDispose: function ( panorama ) { - - if ( panorama instanceof VideoPanorama ) { - - this.hideVideoWidget(); - - } - - if ( panorama === this.panorama ) { - - this.panorama = null; - - } - - }, - - /** - * Load ajax call - * @param {string} url - URL to be requested - * @param {function} [callback] - Callback after request completes - * @memberOf Viewer - * @instance - */ - loadAsyncRequest: function ( url, callback = () => {} ) { - - const request = new window.XMLHttpRequest(); - request.onloadend = function ( event ) { - callback( event ); - }; - request.open( 'GET', url, true ); - request.send( null ); - - }, - - /** - * View indicator in upper left - * @memberOf Viewer - * @instance - */ - addViewIndicator: function () { - - const scope = this; - - function loadViewIndicator ( asyncEvent ) { - - if ( asyncEvent.loaded === 0 ) return; - - const viewIndicatorDiv = asyncEvent.target.responseXML.documentElement; - viewIndicatorDiv.style.width = scope.viewIndicatorSize + 'px'; - viewIndicatorDiv.style.height = scope.viewIndicatorSize + 'px'; - viewIndicatorDiv.style.position = 'absolute'; - viewIndicatorDiv.style.top = '10px'; - viewIndicatorDiv.style.left = '10px'; - viewIndicatorDiv.style.opacity = '0.5'; - viewIndicatorDiv.style.cursor = 'pointer'; - viewIndicatorDiv.id = 'panolens-view-indicator-container'; - - scope.container.appendChild( viewIndicatorDiv ); - - const indicator = viewIndicatorDiv.querySelector( '#indicator' ); - const setIndicatorD = function () { - - scope.radius = scope.viewIndicatorSize * 0.225; - scope.currentPanoAngle = scope.camera.rotation.y - THREE.Math.degToRad( 90 ); - scope.fovAngle = THREE.Math.degToRad( scope.camera.fov ) ; - scope.leftAngle = -scope.currentPanoAngle - scope.fovAngle / 2; - scope.rightAngle = -scope.currentPanoAngle + scope.fovAngle / 2; - scope.leftX = scope.radius * Math.cos( scope.leftAngle ); - scope.leftY = scope.radius * Math.sin( scope.leftAngle ); - scope.rightX = scope.radius * Math.cos( scope.rightAngle ); - scope.rightY = scope.radius * Math.sin( scope.rightAngle ); - scope.indicatorD = 'M ' + scope.leftX + ' ' + scope.leftY + ' A ' + scope.radius + ' ' + scope.radius + ' 0 0 1 ' + scope.rightX + ' ' + scope.rightY; - - if ( scope.leftX && scope.leftY && scope.rightX && scope.rightY && scope.radius ) { - - indicator.setAttribute( 'd', scope.indicatorD ); - - } - - }; - - scope.addUpdateCallback( setIndicatorD ); - - const indicatorOnMouseEnter = function () { - - this.style.opacity = '1'; - - }; - - const indicatorOnMouseLeave = function () { - - this.style.opacity = '0.5'; - - }; - - viewIndicatorDiv.addEventListener( 'mouseenter', indicatorOnMouseEnter ); - viewIndicatorDiv.addEventListener( 'mouseleave', indicatorOnMouseLeave ); - } - - this.loadAsyncRequest( DataImage.ViewIndicator, loadViewIndicator ); - - }, - - /** - * Append custom control item to existing control bar - * @param {object} [option={}] - Style object to overwirte default element style. It takes 'style', 'onTap' and 'group' properties. - * @memberOf Viewer - * @instance - */ - appendControlItem: function ( option ) { - - const item = this.widget.createCustomItem( option ); - - if ( option.group === 'video' ) { - - this.widget.videoElement.appendChild( item ); - - } else { - - this.widget.barElement.appendChild( item ); - - } - - return item; - - }, - - /** - * Clear all cached files - * @memberOf Viewer - * @instance - */ - clearAllCache: function () { - - THREE.Cache.clear(); - - } - - } ); - - if ( THREE.REVISION != THREE_REVISION ) { - - console.warn( `three.js version is not matched. Please consider use the target revision ${THREE_REVISION}` ); - - } - - /** - * Panolens.js - * @author pchen66 - * @namespace PANOLENS - */ - window.TWEEN = Tween; - - exports.BasicPanorama = BasicPanorama; - exports.CONTROLS = CONTROLS; - exports.CameraPanorama = CameraPanorama; - exports.CubePanorama = CubePanorama; - exports.CubeTextureLoader = CubeTextureLoader; - exports.DataImage = DataImage; - exports.EmptyPanorama = EmptyPanorama; - exports.GoogleStreetviewPanorama = GoogleStreetviewPanorama; - exports.ImageLittlePlanet = ImageLittlePlanet; - exports.ImageLoader = ImageLoader; - exports.ImagePanorama = ImagePanorama; - exports.Infospot = Infospot; - exports.LittlePlanet = LittlePlanet; - exports.MODES = MODES; - exports.Media = Media; - exports.Panorama = Panorama; - exports.REVISION = REVISION; - exports.Reticle = Reticle; - exports.THREE_REVISION = THREE_REVISION; - exports.THREE_VERSION = THREE_VERSION; - exports.TextureLoader = TextureLoader; - exports.VERSION = VERSION; - exports.VideoPanorama = VideoPanorama; - exports.Viewer = Viewer; - exports.Widget = Widget; - - Object.defineProperty(exports, '__esModule', { value: true }); - -})); diff --git a/build/panolens.min.js b/build/panolens.min.js deleted file mode 100644 index 078ef538..00000000 --- a/build/panolens.min.js +++ /dev/null @@ -1,202 +0,0 @@ -(function(h,e){"object"===typeof exports&&"undefined"!==typeof module?e(exports,require("three")):"function"===typeof define&&define.amd?define(["exports","three"],e):(h=h||self,e(h.PANOLENS={},h.THREE))})(this,function(h,e){function S(a){this.constraints=Object.assign({video:{width:{ideal:1920},height:{ideal:1080},facingMode:{exact:"environment"}},audio:!1},a);this.element=this.scene=this.container=null;this.devices=[];this.stream=null;this.ratioScalar=1;this.videoDeviceIndex=0}function M(a,b,c){a= -void 0===a?16777215:a;b=void 0===b?!0:b;c=void 0===c?1500:c;this.dpr=window.devicePixelRatio;var d=this.createCanvas(),g=d.canvas;d=d.context;var k=new e.SpriteMaterial({color:a,map:this.createCanvasTexture(g)});e.Sprite.call(this,k);this.canvasWidth=g.width;this.canvasHeight=g.height;this.context=d;this.color=a instanceof e.Color?a:new e.Color(a);this.autoSelect=b;this.dwellTime=c;this.rippleDuration=500;this.position.z=-10;this.center.set(.5,.5);this.scale.set(.5,.5,1);this.callback=this.timerId= -this.startTimestamp=null;this.frustumCulled=!1;this.updateCanvasArcByProgress(0)}function z(a,b,c){a=void 0===a?300:a;b=b||u.Info;e.Sprite.call(this);this.type="infospot";this.animated=void 0!==c?c:!0;this.frustumCulled=this.isHovering=!1;this.cursorStyle=this.toPanorama=this.element=null;this.mode=t.NORMAL;this.scale.set(a,a,1);this.rotation.y=Math.PI;this.container=null;this.originalRaycast=this.raycast;this.HANDLER_FOCUS=null;this.material.side=e.DoubleSide;this.material.depthTest=!1;this.material.transparent= -!0;this.material.opacity=0;this.scaleUpAnimation=new r.Tween;this.scaleDownAnimation=new r.Tween;c=function(d){if(this.material){var b=d.image.width/d.image.height,c=new e.Vector3;d.image.width=d.image.naturalWidth||64;d.image.height=d.image.naturalHeight||64;this.scale.set(b*a,a,1);c.copy(this.scale);this.scaleUpAnimation=(new r.Tween(this.scale)).to({x:1.3*c.x,y:1.3*c.y},500).easing(r.Easing.Elastic.Out);this.scaleDownAnimation=(new r.Tween(this.scale)).to({x:c.x,y:c.y},500).easing(r.Easing.Elastic.Out); -this.material.map=d;this.material.needsUpdate=!0}}.bind(this);this.showAnimation=(new r.Tween(this.material)).to({opacity:1},500).onStart(this.enableRaycast.bind(this,!0)).easing(r.Easing.Quartic.Out);this.hideAnimation=(new r.Tween(this.material)).to({opacity:0},500).onStart(this.enableRaycast.bind(this,!1)).easing(r.Easing.Quartic.Out);this.addEventListener("click",this.onClick);this.addEventListener("hover",this.onHover);this.addEventListener("hoverenter",this.onHoverStart);this.addEventListener("hoverleave", -this.onHoverEnd);this.addEventListener("panolens-dual-eye-effect",this.onDualEyeEffect);this.addEventListener("panolens-container",this.setContainer.bind(this));this.addEventListener("dismiss",this.onDismiss);this.addEventListener("panolens-infospot-focus",this.setFocusMethod);N.load(b,c)}function I(a){a||console.warn("PANOLENS.Widget: No container specified");e.EventDispatcher.call(this);this.DEFAULT_TRANSITION="all 0.27s ease";this.TOUCH_ENABLED=!!("ontouchstart"in window||window.DocumentTouch&& -document instanceof DocumentTouch);this.PREVENT_EVENT_HANDLER=function(a){a.preventDefault();a.stopPropagation()};this.container=a;this.mask=this.activeSubMenu=this.activeMainItem=this.mainMenu=this.settingElement=this.videoElement=this.fullscreenElement=this.barElement=null}function n(a,b){e.Mesh.call(this,a,b);this.type="panorama";this.ImageQualityLow=1;this.ImageQualityFair=2;this.ImageQualityMedium=3;this.ImageQualityHigh=4;this.ImageQualitySuperHigh=5;this.animationDuration=1E3;this.defaultInfospotSize= -350;this.container=void 0;this.loaded=!1;this.linkedSpots=[];this.isInfospotVisible=!1;this.linkingImageScale=this.linkingImageURL=void 0;this.material.side=e.BackSide;this.material.opacity=0;this.scale.x*=-1;this.renderOrder=-1;this.active=!1;this.infospotAnimation=(new r.Tween(this)).to({},this.animationDuration/2);this.addEventListener("load",this.fadeIn.bind(this));this.addEventListener("panolens-container",this.setContainer.bind(this));this.addEventListener("click",this.onClick.bind(this));this.setupTransitions()} -function y(a,b,c){b=b||new e.SphereBufferGeometry(5E3,60,40);c=c||new e.MeshBasicMaterial({opacity:0,transparent:!0});n.call(this,b,c);this.src=a;this.radius=5E3}function W(){var a=new e.BufferGeometry,b=new e.MeshBasicMaterial({color:0,opacity:0,transparent:!0});a.addAttribute("position",new e.BufferAttribute(new Float32Array,1));n.call(this,a,b)}function F(a){a=void 0===a?[]:a;var b=Object.assign({},e.ShaderLib.cube),c=new e.BoxBufferGeometry(1E4,1E4,1E4);b=new e.ShaderMaterial({fragmentShader:b.fragmentShader, -vertexShader:b.vertexShader,uniforms:b.uniforms,side:e.BackSide,transparent:!0});n.call(this,c,b);this.images=a;this.edgeLength=1E4;this.material.uniforms.opacity.value=0}function O(){for(var a=[],b=0;6>b;b++)a.push(u.WhiteTile);F.call(this,a)}function B(a,b){b=void 0===b?{}:b;var c=new e.SphereBufferGeometry(5E3,60,40),d=new e.MeshBasicMaterial({opacity:0,transparent:!0});n.call(this,c,d);this.src=a;this.options={videoElement:document.createElement("video"),loop:!0,muted:!0,autoplay:!1,playsinline:!0, -crossOrigin:"anonymous"};Object.assign(this.options,b);this.videoElement=this.options.videoElement;this.videoProgress=0;this.radius=5E3;this.addEventListener("leave",this.pauseVideo.bind(this));this.addEventListener("enter-fade-start",this.resumeVideoProgress.bind(this));this.addEventListener("video-toggle",this.toggleVideo.bind(this));this.addEventListener("video-time",this.setVideoCurrentTime.bind(this))}function P(a){this._parameters=a=void 0===a?{}:a;this._panoId=this._zoom=null;this._panoClient= -new google.maps.StreetViewService;this._total=this._count=0;this._canvas=[];this._ctx=[];this._hc=this._wc=0;this.result=null;this.rotation=0;this.copyright="";this.onPanoramaLoad=this.onSizeChange=null;this.levelsW=[1,2,4,7,13,26];this.levelsH=[1,1,2,4,7,13];this.widths=[416,832,1664,3328,6656,13312];this.heights=[416,416,832,1664,3328,6656];this.maxH=this.maxW=6656;var b;try{var c=document.createElement("canvas");(b=c.getContext("experimental-webgl"))||(b=c.getContext("webgl"))}catch(d){}this.maxW= -Math.max(b.getParameter(b.MAX_TEXTURE_SIZE),this.maxW);this.maxH=Math.max(b.getParameter(b.MAX_TEXTURE_SIZE),this.maxH)}function Z(a,b){y.call(this);this.panoId=a;this.gsvLoader=null;this.loadRequested=!1;this.setupGoogleMapAPI(b)}function C(a,b,c,d){c=void 0===c?1E4:c;d=void 0===d?.5:d;"image"===(void 0===a?"image":a)&&y.call(this,b,this.createGeometry(c,d),this.createMaterial(c));this.size=c;this.ratio=d;this.EPS=1E-6;this.frameId=null;this.dragging=!1;this.userMouse=new e.Vector2;this.quatA=new e.Quaternion; -this.quatB=new e.Quaternion;this.quatCur=new e.Quaternion;this.quatSlerp=new e.Quaternion;this.vectorX=new e.Vector3(1,0,0);this.vectorY=new e.Vector3(0,1,0);this.addEventListener("window-resize",this.onWindowResize)}function aa(a,b,c){C.call(this,"image",a,b,c)}function J(a){var b=new e.SphereBufferGeometry(5E3,60,40),c=new e.MeshBasicMaterial({visible:!1});n.call(this,b,c);this.media=new S(a);this.radius=5E3;this.addEventListener("enter",this.start.bind(this));this.addEventListener("leave",this.stop.bind(this)); -this.addEventListener("panolens-container",this.onPanolensContainer.bind(this));this.addEventListener("panolens-scene",this.onPanolensScene.bind(this))}function ba(a,b){function c(a){Q=!1;K=L=0;if(!1!==f.enabled){a.preventDefault();if(a.button===f.mouseButtons.ORBIT){if(!0===f.noRotate)return;x=w.ROTATE;D.set(a.clientX,a.clientY)}else if(a.button===f.mouseButtons.ZOOM){if(!0===f.noZoom)return;x=w.DOLLY;y.set(a.clientX,a.clientY)}else if(a.button===f.mouseButtons.PAN){if(!0===f.noPan)return;x=w.PAN; -n.set(a.clientX,a.clientY)}x!==w.NONE&&(document.addEventListener("mousemove",d,!1),document.addEventListener("mouseup",g,!1),f.dispatchEvent(O));f.update()}}function d(a){if(!1!==f.enabled){a.preventDefault();var d=f.domElement===document?f.domElement.body:f.domElement;if(x===w.ROTATE){if(!0===f.noRotate)return;h.set(a.clientX,a.clientY);r.subVectors(h,D);f.rotateLeft(2*Math.PI*r.x/d.clientWidth*f.rotateSpeed);f.rotateUp(2*Math.PI*r.y/d.clientHeight*f.rotateSpeed);D.copy(h);G&&(K=a.clientX-G.clientX, -L=a.clientY-G.clientY);G=a}else if(x===w.DOLLY){if(!0===f.noZoom)return;z.set(a.clientX,a.clientY);T.subVectors(z,y);0T.y&&f.dollyOut();y.copy(z)}else if(x===w.PAN){if(!0===f.noPan)return;U.set(a.clientX,a.clientY);t.subVectors(U,n);f.pan(t.x,t.y);n.copy(U)}x!==w.NONE&&f.update()}}function g(){Q=!0;G=void 0;!1!==f.enabled&&(document.removeEventListener("mousemove",d,!1),document.removeEventListener("mouseup",g,!1),f.dispatchEvent(P),x=w.NONE)}function k(a){if(!1!==f.enabled&&!0!== -f.noZoom&&x===w.NONE){a.preventDefault();a.stopPropagation();var d=0;void 0!==a.wheelDelta?d=a.wheelDelta:void 0!==a.detail&&(d=-a.detail);0d&&(f.object.fov=f.object.fov>f.minFov?f.object.fov-1:f.minFov,f.object.updateProjectionMatrix());f.update();f.dispatchEvent(V);f.dispatchEvent(O);f.dispatchEvent(P)}}function p(a){switch(a.keyCode){case f.keys.UP:I=!1;break;case f.keys.BOTTOM:J=!1;break;case f.keys.LEFT:X= -!1;break;case f.keys.RIGHT:Y=!1}}function m(a){if(!1!==f.enabled&&!0!==f.noKeys&&!0!==f.noRotate){switch(a.keyCode){case f.keys.UP:I=!0;break;case f.keys.BOTTOM:J=!0;break;case f.keys.LEFT:X=!0;break;case f.keys.RIGHT:Y=!0}if(I||J||X||Y)Q=!0,I&&(L=-f.rotateSpeed*f.momentumKeydownFactor),J&&(L=f.rotateSpeed*f.momentumKeydownFactor),X&&(K=-f.rotateSpeed*f.momentumKeydownFactor),Y&&(K=f.rotateSpeed*f.momentumKeydownFactor)}}function l(a){Q=!1;K=L=0;if(!1!==f.enabled){switch(a.touches.length){case 1:if(!0=== -f.noRotate)return;x=w.TOUCH_ROTATE;D.set(a.touches[0].pageX,a.touches[0].pageY);break;case 2:if(!0===f.noZoom)return;x=w.TOUCH_DOLLY;var d=a.touches[0].pageX-a.touches[1].pageX;a=a.touches[0].pageY-a.touches[1].pageY;y.set(0,Math.sqrt(d*d+a*a));break;case 3:if(!0===f.noPan)return;x=w.TOUCH_PAN;n.set(a.touches[0].pageX,a.touches[0].pageY);break;default:x=w.NONE}x!==w.NONE&&f.dispatchEvent(O)}}function v(a){if(!1!==f.enabled){a.preventDefault();a.stopPropagation();var d=f.domElement===document?f.domElement.body: -f.domElement;switch(a.touches.length){case 1:if(!0===f.noRotate)break;if(x!==w.TOUCH_ROTATE)break;h.set(a.touches[0].pageX,a.touches[0].pageY);r.subVectors(h,D);f.rotateLeft(2*Math.PI*r.x/d.clientWidth*f.rotateSpeed);f.rotateUp(2*Math.PI*r.y/d.clientHeight*f.rotateSpeed);D.copy(h);G&&(K=a.touches[0].pageX-G.pageX,L=a.touches[0].pageY-G.pageY);G={pageX:a.touches[0].pageX,pageY:a.touches[0].pageY};f.update();break;case 2:if(!0===f.noZoom)break;if(x!==w.TOUCH_DOLLY)break;d=a.touches[0].pageX-a.touches[1].pageX; -a=a.touches[0].pageY-a.touches[1].pageY;z.set(0,Math.sqrt(d*d+a*a));T.subVectors(z,y);0>T.y?(f.object.fov=f.object.fovf.minFov?f.object.fov-1:f.minFov,f.object.updateProjectionMatrix());y.copy(z);f.update();f.dispatchEvent(V);break;case 3:if(!0===f.noPan)break;if(x!==w.TOUCH_PAN)break;U.set(a.touches[0].pageX,a.touches[0].pageY);t.subVectors(U,n);f.pan(t.x,t.y);n.copy(U);f.update();break;default:x= -w.NONE}}}function q(){Q=!0;G=void 0;!1!==f.enabled&&(f.dispatchEvent(P),x=w.NONE)}this.object=a;this.domElement=void 0!==b?b:document;this.frameId=null;this.enabled=!0;this.center=this.target=new e.Vector3;this.noZoom=!1;this.zoomSpeed=1;this.minDistance=0;this.maxDistance=Infinity;this.minZoom=0;this.maxZoom=Infinity;this.noRotate=!1;this.rotateSpeed=-.15;this.noPan=!0;this.keyPanSpeed=7;this.autoRotate=!1;this.autoRotateSpeed=2;this.minPolarAngle=0;this.maxPolarAngle=Math.PI;this.momentumDampingFactor= -.9;this.momentumScalingFactor=-.005;this.momentumKeydownFactor=20;this.minFov=30;this.maxFov=120;this.minAzimuthAngle=-Infinity;this.maxAzimuthAngle=Infinity;this.noKeys=!1;this.keys={LEFT:37,UP:38,RIGHT:39,BOTTOM:40};this.mouseButtons={ORBIT:e.MOUSE.LEFT,ZOOM:e.MOUSE.MIDDLE,PAN:e.MOUSE.RIGHT};var f=this,D=new e.Vector2,h=new e.Vector2,r=new e.Vector2,n=new e.Vector2,U=new e.Vector2,t=new e.Vector2,u=new e.Vector3,A=new e.Vector3,y=new e.Vector2,z=new e.Vector2,T=new e.Vector2,R=0,H=0,B=0,C=0,E=1, -F=new e.Vector3,M=new e.Vector3,N=new e.Quaternion,K=0,L=0,G,Q=!1,I,J,X,Y,w={NONE:-1,ROTATE:0,DOLLY:1,PAN:2,TOUCH_ROTATE:3,TOUCH_DOLLY:4,TOUCH_PAN:5},x=w.NONE;this.target0=this.target.clone();this.position0=this.object.position.clone();this.zoom0=this.object.zoom;var S=(new e.Quaternion).setFromUnitVectors(a.up,new e.Vector3(0,1,0)),W=S.clone().inverse(),V={type:"change"},O={type:"start"},P={type:"end"};this.setLastQuaternion=function(a){N.copy(a);f.object.quaternion.copy(a)};this.getLastPosition= -function(){return M};this.rotateLeft=function(a){void 0===a&&(a=2*Math.PI/60/60*f.autoRotateSpeed);C-=a};this.rotateUp=function(a){void 0===a&&(a=2*Math.PI/60/60*f.autoRotateSpeed);B-=a};this.panLeft=function(a){var d=this.object.matrix.elements;u.set(d[0],d[1],d[2]);u.multiplyScalar(-a);F.add(u)};this.panUp=function(a){var d=this.object.matrix.elements;u.set(d[4],d[5],d[6]);u.multiplyScalar(a);F.add(u)};this.pan=function(a,d){var b=f.domElement===document?f.domElement.body:f.domElement;if(f.object instanceof -e.PerspectiveCamera){var c=f.object.position.clone().sub(f.target).length();c*=Math.tan(f.object.fov/2*Math.PI/180);f.panLeft(2*a*c/b.clientHeight);f.panUp(2*d*c/b.clientHeight)}else f.object instanceof e.OrthographicCamera?(f.panLeft(a*(f.object.right-f.object.left)/b.clientWidth),f.panUp(d*(f.object.top-f.object.bottom)/b.clientHeight)):console.warn("WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.")};this.momentum=function(){Q&&(1E-4>Math.abs(K)&&1E-4>Math.abs(L)?Q= -!1:(L*=this.momentumDampingFactor,K*=this.momentumDampingFactor,C-=this.momentumScalingFactor*K,B-=this.momentumScalingFactor*L))};this.dollyIn=function(a){void 0===a&&(a=Math.pow(.95,f.zoomSpeed));f.object instanceof e.PerspectiveCamera?E/=a:f.object instanceof e.OrthographicCamera?(f.object.zoom=Math.max(this.minZoom,Math.min(this.maxZoom,this.object.zoom*a)),f.object.updateProjectionMatrix(),f.dispatchEvent(V)):console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.")}; -this.dollyOut=function(a){void 0===a&&(a=Math.pow(.95,f.zoomSpeed));f.object instanceof e.PerspectiveCamera?E*=a:f.object instanceof e.OrthographicCamera?(f.object.zoom=Math.max(this.minZoom,Math.min(this.maxZoom,this.object.zoom/a)),f.object.updateProjectionMatrix(),f.dispatchEvent(V)):console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.")};this.update=function(a){var d=this.object.position;A.copy(d).sub(this.target);A.applyQuaternion(S);R=Math.atan2(A.x, -A.z);H=Math.atan2(Math.sqrt(A.x*A.x+A.z*A.z),A.y);this.autoRotate&&x===w.NONE&&this.rotateLeft(2*Math.PI/60/60*f.autoRotateSpeed);this.momentum();R+=C;H+=B;R=Math.max(this.minAzimuthAngle,Math.min(this.maxAzimuthAngle,R));H=Math.max(this.minPolarAngle,Math.min(this.maxPolarAngle,H));H=Math.max(1E-7,Math.min(Math.PI-1E-7,H));var b=A.length()*E;b=Math.max(this.minDistance,Math.min(this.maxDistance,b));this.target.add(F);A.x=b*Math.sin(H)*Math.sin(R);A.y=b*Math.cos(H);A.z=b*Math.sin(H)*Math.cos(R);A.applyQuaternion(W); -d.copy(this.target).add(A);this.object.lookAt(this.target);B=C=0;E=1;F.set(0,0,0);if(1E-7g&&(l.total=l.total/g*6);c(l)},d)});return k}};S.prototype=Object.assign(Object.create(e.EventDispatcher.prototype),{setContainer:function(a){this.container=a},setScene:function(a){this.scene=a},enumerateDevices:function(){var a=this.devices,b=new Promise(function(b){b(a)});return 0=g.length?(c(0),d--):c(d);b(g[d])})},getDevices:function(a){a=void 0===a?"video":a;var b=this.devices;return this.enumerateDevices().then(function(a){return a.map(function(a){b.includes(a)||b.push(a);return a})}).then(function(b){var d=new RegExp(a,"i");return b.filter(function(a){return d.test(a.kind)})})},getUserMedia:function(a){var b=this.setMediaStream.bind(this),c=this.playVideo.bind(this); -return window.navigator.mediaDevices.getUserMedia(a).then(b).then(c).catch(function(a){console.warn("PANOLENS.Media: "+a)})},setVideDeviceIndex:function(a){this.videoDeviceIndex=a},start:function(a){var b=this.constraints,c=this.getUserMedia.bind(this);this.element=this.createVideoElement();return this.getDevices().then(function(d){if(!d||0===d.length)throw Error("no video device found");b.video.deviceId=(a||d[0]).deviceId;return c(b)})},stop:function(){var a=this.stream;a&&a.active&&(a.getTracks()[0].stop(), -window.removeEventListener("resize",this.onWindowResize.bind(this)),this.stream=this.element=null)},setMediaStream:function(a){this.stream=a;this.element.srcObject=a;this.scene&&(this.scene.background=this.createVideoTexture());window.addEventListener("resize",this.onWindowResize.bind(this))},playVideo:function(){var a=this.element;a&&(a.play(),this.dispatchEvent({type:"play"}))},pauseVideo:function(){var a=this.element;a&&(a.pause(),this.dispatchEvent({type:"pause"}))},createVideoTexture:function(){var a= -this.element,b=new e.VideoTexture(a);b.generateMipmaps=!1;b.minFilter=e.LinearFilter;b.magFilter=e.LinearFilter;b.format=e.RGBFormat;b.center.set(.5,.5);a.addEventListener("canplay",this.onWindowResize.bind(this));return b},createVideoElement:function(){var a=this.dispatchEvent.bind(this),b=document.createElement("video");b.setAttribute("autoplay","");b.setAttribute("muted","");b.setAttribute("playsinline","");b.style.position="absolute";b.style.top="0";b.style.left="0";b.style.width="100%";b.style.height= -"100%";b.style.objectPosition="center";b.style.objectFit="cover";b.style.display=this.scene?"none":"";b.addEventListener("canplay",function(){return a({type:"canplay"})});return b},onWindowResize:function(){if(this.element&&this.element.videoWidth&&this.element.videoHeight&&this.scene){var a=this.container,b=a.clientWidth;a=a.clientHeight;var c=this.scene.background,d=this.element;d=d.videoHeight/d.videoWidth*(this.container?b/a:1)*this.ratioScalar;b>a?c.repeat.set(d,1):c.repeat.set(1,1/d)}}});M.prototype= -Object.assign(Object.create(e.Sprite.prototype),{constructor:M,setColor:function(a){this.material.color.copy(a instanceof e.Color?a:new e.Color(a))},createCanvasTexture:function(a){a=new e.CanvasTexture(a);a.minFilter=e.LinearFilter;a.magFilter=e.LinearFilter;a.generateMipmaps=!1;return a},createCanvas:function(){var a=document.createElement("canvas"),b=a.getContext("2d"),c=this.dpr;a.width=32*c;a.height=32*c;b.scale(c,c);b.shadowBlur=5;b.shadowColor="rgba(200,200,200,0.9)";return{canvas:a,context:b}}, -updateCanvasArcByProgress:function(a){var b=this.context,c=this.canvasWidth,d=this.canvasHeight,g=this.material,k=this.dpr,e=a*Math.PI*2,m=this.color.getStyle(),l=.5*c/k;k=.5*d/k;b.clearRect(0,0,c,d);b.beginPath();0===a?(b.arc(l,k,c/16,0,2*Math.PI),b.fillStyle=m,b.fill()):(b.arc(l,k,c/4-3,-Math.PI/2,-Math.PI/2+e),b.strokeStyle=m,b.lineWidth=3,b.stroke());b.closePath();g.map.needsUpdate=!0},ripple:function(){var a=this,b=this.context,c=this.canvasWidth,d=this.canvasHeight,g=this.material,k=this.rippleDuration, -e=performance.now(),m=this.color,l=this.dpr,h=.5*c/l,q=.5*d/l,f=function(){var p=window.requestAnimationFrame(f),v=(performance.now()-e)/k,n=0<1-v?1-v:0,r=v*c*.5/l;b.clearRect(0,0,c,d);b.beginPath();b.arc(h,q,r,0,2*Math.PI);b.fillStyle="rgba("+255*m.r+", "+255*m.g+", "+255*m.b+", "+n+")";b.fill();b.closePath();1<=v&&(window.cancelAnimationFrame(p),a.updateCanvasArcByProgress(0),a.dispatchEvent({type:"reticle-ripple-end"}));g.map.needsUpdate=!0};this.dispatchEvent({type:"reticle-ripple-start"});f()}, -show:function(){this.visible=!0},hide:function(){this.visible=!1},start:function(a){this.autoSelect&&(this.dispatchEvent({type:"reticle-start"}),this.startTimestamp=performance.now(),this.callback=a,this.update())},end:function(){this.startTimestamp&&(window.cancelAnimationFrame(this.timerId),this.updateCanvasArcByProgress(0),this.startTimestamp=this.timerId=this.callback=null,this.dispatchEvent({type:"reticle-end"}))},update:function(){this.timerId=window.requestAnimationFrame(this.update.bind(this)); -var a=(performance.now()-this.startTimestamp)/this.dwellTime;this.updateCanvasArcByProgress(a);this.dispatchEvent({type:"reticle-update",progress:a});1<=a&&(window.cancelAnimationFrame(this.timerId),this.callback&&this.callback(),this.end(),this.ripple())}});var r=function(a,b){return b={exports:{}},a(b,b.exports),b.exports}(function(a,b){b=function(){this._tweens={};this._tweensAddedDuringUpdate={}};b.prototype={getAll:function(){return Object.keys(this._tweens).map(function(a){return this._tweens[a]}.bind(this))}, -removeAll:function(){this._tweens={}},add:function(a){this._tweens[a.getId()]=a;this._tweensAddedDuringUpdate[a.getId()]=a},remove:function(a){delete this._tweens[a.getId()];delete this._tweensAddedDuringUpdate[a.getId()]},update:function(a,b){var d=Object.keys(this._tweens);if(0===d.length)return!1;for(a=void 0!==a?a:c.now();0(a*=2)?.5*a*a:-.5*(--a*(a-2)-1)}},Cubic:{In:function(a){return a*a*a},Out:function(a){return--a*a*a+1},InOut:function(a){return 1> -(a*=2)?.5*a*a*a:.5*((a-=2)*a*a+2)}},Quartic:{In:function(a){return a*a*a*a},Out:function(a){return 1- --a*a*a*a},InOut:function(a){return 1>(a*=2)?.5*a*a*a*a:-.5*((a-=2)*a*a*a-2)}},Quintic:{In:function(a){return a*a*a*a*a},Out:function(a){return--a*a*a*a*a+1},InOut:function(a){return 1>(a*=2)?.5*a*a*a*a*a:.5*((a-=2)*a*a*a*a+2)}},Sinusoidal:{In:function(a){return 1-Math.cos(a*Math.PI/2)},Out:function(a){return Math.sin(a*Math.PI/2)},InOut:function(a){return.5*(1-Math.cos(Math.PI*a))}},Exponential:{In:function(a){return 0=== -a?0:Math.pow(1024,a-1)},Out:function(a){return 1===a?1:1-Math.pow(2,-10*a)},InOut:function(a){return 0===a?0:1===a?1:1>(a*=2)?.5*Math.pow(1024,a-1):.5*(-Math.pow(2,-10*(a-1))+2)}},Circular:{In:function(a){return 1-Math.sqrt(1-a*a)},Out:function(a){return Math.sqrt(1- --a*a)},InOut:function(a){return 1>(a*=2)?-.5*(Math.sqrt(1-a*a)-1):.5*(Math.sqrt(1-(a-=2)*a)+1)}},Elastic:{In:function(a){return 0===a?0:1===a?1:-Math.pow(2,10*(a-1))*Math.sin(5*(a-1.1)*Math.PI)},Out:function(a){return 0===a?0:1===a? -1:Math.pow(2,-10*a)*Math.sin(5*(a-.1)*Math.PI)+1},InOut:function(a){if(0===a)return 0;if(1===a)return 1;a*=2;return 1>a?-.5*Math.pow(2,10*(a-1))*Math.sin(5*(a-1.1)*Math.PI):.5*Math.pow(2,-10*(a-1))*Math.sin(5*(a-1.1)*Math.PI)+1}},Back:{In:function(a){return a*a*(2.70158*a-1.70158)},Out:function(a){return--a*a*(2.70158*a+1.70158)+1},InOut:function(a){return 1>(a*=2)?.5*a*a*(3.5949095*a-2.5949095):.5*((a-=2)*a*(3.5949095*a+2.5949095)+2)}},Bounce:{In:function(a){return 1-c.Easing.Bounce.Out(1-a)},Out:function(a){return a< -1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+.9375:7.5625*(a-=2.625/2.75)*a+.984375},InOut:function(a){return.5>a?.5*c.Easing.Bounce.In(2*a):.5*c.Easing.Bounce.Out(2*a-1)+.5}}};c.Interpolation={Linear:function(a,b){var d=a.length-1,g=d*b,e=Math.floor(g),l=c.Interpolation.Utils.Linear;return 0>b?l(a[0],a[1],g):1d?d:e+1],g-e)},Bezier:function(a,b){for(var d=0,g=a.length-1,e=Math.pow,l=c.Interpolation.Utils.Bernstein,h=0;h<= -g;h++)d+=e(1-b,g-h)*e(b,h)*a[h]*l(g,h);return d},CatmullRom:function(a,b){var d=a.length-1,g=d*b,e=Math.floor(g),l=c.Interpolation.Utils.CatmullRom;return a[0]===a[d]?(0>b&&(e=Math.floor(g=d*(1+b))),l(a[(e-1+d)%d],a[e],a[(e+1)%d],a[(e+2)%d],g-e)):0>b?a[0]-(l(a[0],a[0],a[1],a[1],-g)-a[0]):1l?0:l,f.setProgress(l),g.dispatchEvent({type:"panolens-viewer-handler",method:"setVideoCurrentTime",data:l}))}function c(a){a.stopPropagation();e=!1;d()}function d(){g.container.removeEventListener("mousemove",b,!1);g.container.removeEventListener("mouseup",c,!1);g.container.removeEventListener("touchmove",b,!1);g.container.removeEventListener("touchend",c,!1)}var g=this,e=!1,p,m,l;var h=document.createElement("div");h.style.width="0%";h.style.height="100%";h.style.backgroundColor="#fff";var q= -document.createElement("div");q.style.float="right";q.style.width="14px";q.style.height="14px";q.style.transform="translate(7px, -5px)";q.style.borderRadius="50%";q.style.backgroundColor="#ddd";q.addEventListener("mousedown",a,{passive:!0});q.addEventListener("touchstart",a,{passive:!0});h.appendChild(q);var f=this.createCustomItem({style:{float:"left",width:"30%",height:"4px",marginTop:"20px",backgroundColor:"rgba(188,188,188,0.8)"},onTap:function(a){a.preventDefault();a.stopPropagation();if(a.target!== -q){var b=a.changedTouches&&0=window.innerWidth?this.ImageQualityFair:800=window.innerWidth?this.ImageQualityMedium:1280=window.innerWidth?this.ImageQualityHigh:1920=d&&(b.zoom.value=d)},onUpdateCallback:function(){this.frameId=window.requestAnimationFrame(this.onUpdateCallback.bind(this));this.quatSlerp.slerp(this.quatCur,.1);this.material&& -this.material.uniforms.transform.value.makeRotationFromQuaternion(this.quatSlerp);!this.dragging&&1-this.quatSlerp.clone().dot(this.quatCur)=a.length?0:b},setCameraFov:function(a){this.camera.fov=a;this.camera.updateProjectionMatrix()}, -enableControl:function(a){a=0<=a&&aMath.PI?m-2*Math.PI:m;m=m<-Math.PI?m+2*Math.PI:m;k=Math.abs(h.angleTo(k)+(0>=h.y*l.y?l.angleTo(a):-l.angleTo(a)));k*=l.y=a.clientX-this.options.clickTolerance&&this.userMouse.x<=a.clientX+ -this.options.clickTolerance&&this.userMouse.y>=a.clientY-this.options.clickTolerance&&this.userMouse.y<=a.clientY+this.options.clickTolerance||a.changedTouches&&this.userMouse.x>=a.changedTouches[0].clientX-this.options.clickTolerance&&this.userMouse.x<=a.changedTouches[0].clientX+this.options.clickTolerance&&this.userMouse.y>=a.changedTouches[0].clientY-this.options.clickTolerance&&this.userMouse.y<=a.changedTouches[0].clientY+this.options.clickTolerance?"click":void 0;if(!a||!a.target||a.target.classList.contains("panolens-canvas"))if(a.preventDefault(), -a=a.changedTouches&&1===a.changedTouches.length?this.onTap({clientX:a.changedTouches[0].clientX,clientY:a.changedTouches[0].clientY},b):this.onTap(a,b),this.userMouse.type="none",!a&&"click"===b){b=this.options;a=b.autoHideControlBar;var c=this.panorama,d=this.toggleControlBar;b.autoHideInfospot&&c&&c.toggleInfospotVisibility();a&&d()}},onTap:function(a,b){var c=this.container.getBoundingClientRect(),d=c.top,e=this.container,h=e.clientHeight;this.raycasterPoint.x=(a.clientX-c.left)/e.clientWidth* -2-1;this.raycasterPoint.y=2*-((a.clientY-d)/h)+1;this.raycaster.setFromCamera(this.raycasterPoint,this.camera);if(this.panorama){("mousedown"!==a.type&&this.touchSupported||this.OUTPUT_INFOSPOT)&&this.outputPosition();c=this.raycaster.intersectObjects(this.panorama.children,!0);d=this.getConvertedIntersect(c);e=0 {}, onProgress = () => {}, onError = () => {} ) { - - // Enable cache - Cache.enabled = true; - - let cached, request, arrayBufferView, blob, urlCreator, image, reference; - - // Reference key - for (let iconName in DataImage) { - - if (DataImage.hasOwnProperty(iconName) && url === DataImage[iconName]) { - - reference = iconName; - - } - - } - - // Cached - cached = Cache.get(reference ? reference : url); - - if (cached !== undefined) { - - if (onLoad) { - - setTimeout(function () { - - onProgress({loaded: 1, total: 1}); - onLoad(cached); - - }, 0); - - } - - return cached; - - } - - // Construct a new XMLHttpRequest - urlCreator = window.URL || window.webkitURL; - image = document.createElementNS('http://www.w3.org/1999/xhtml', 'img'); - - // Add to cache - Cache.add(reference ? reference : url, image); - - const onImageLoaded = () => { - - urlCreator.revokeObjectURL(image.src); - onLoad(image); - - }; - - if (url.indexOf('data:') === 0) { - - image.addEventListener('load', onImageLoaded, false); - image.src = url; - return image; - } - - image.crossOrigin = this.crossOrigin !== undefined ? this.crossOrigin : ''; - - request = new window.XMLHttpRequest(); - request.open('GET', url, true); - if (process.env.npm_lifecycle_event !== 'test') { - request.onreadystatechange = function () { - if (this.readyState === 4 && this.status >= 400) { - onError(); - } - }; - } - request.responseType = 'arraybuffer'; - request.addEventListener( 'error', onError ); - request.addEventListener( 'progress', event => { - - if ( !event ) return; - - const { loaded, total, lengthComputable } = event; - - if ( lengthComputable ) { - - onProgress( { loaded, total } ); - - } - - } ); - - request.addEventListener( 'loadend', event => { - - if ( !event ) return; - const { currentTarget: { response } } = event; - - arrayBufferView = new Uint8Array( response ); - blob = new window.Blob( [ arrayBufferView ] ); - - image.addEventListener( 'load', onImageLoaded, false ); - image.src = urlCreator.createObjectURL( blob ); - - } ); - - request.send(null); - - } - -}; - -/** - * @module TextureLoader - * @description Texture loader based on {@link https://github.com/mrdoob/three.js/blob/master/src/loaders/TextureLoader.js} - */ -const TextureLoader = { - - /** - * Load image texture - * @example PANOLENS.TextureLoader.load( IMAGE_URL ) - * @method load - * @param {string} url - An image url - * @param {function} onLoad - On load callback - * @param {function} onProgress - In progress callback - * @param {function} onError - On error callback - * @return {THREE.Texture} - Image texture - */ - load: function ( url, onLoad = () => {}, onProgress, onError ) { - - var texture = new Texture(); - - ImageLoader.load( url, function ( image ) { - - texture.image = image; - - // JPEGs can't have an alpha channel, so memory can be saved by storing them as RGB. - const isJPEG = url.search( /\.(jpg|jpeg)$/ ) > 0 || url.search( /^data\:image\/jpeg/ ) === 0; - - texture.format = isJPEG ? RGBFormat : RGBAFormat; - texture.needsUpdate = true; - - onLoad( texture ); - - }, onProgress, onError ); - - return texture; - - } - -}; - -/** - * @module CubeTextureLoader - * @description Cube Texture Loader based on {@link https://github.com/mrdoob/three.js/blob/master/src/loaders/CubeTextureLoader.js} - */ -const CubeTextureLoader = { - - /** - * Load 6 images as a cube texture - * @example PANOLENS.CubeTextureLoader.load( [ 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' ] ) - * @method load - * @param {array} urls - array of 6 urls to images, one for each side of the CubeTexture. The urls should be specified in the following order: pos-x, neg-x, pos-y, neg-y, pos-z, neg-z - * @param {function} onLoad - On load callback - * @param {function} onProgress - In progress callback - * @param {function} onError - On error callback - * @return {THREE.CubeTexture} - Cube texture - */ - load: function ( urls, onLoad = () => {}, onProgress = () => {}, onError ) { - - var texture, loaded, progress, all, loadings; - - texture = new CubeTexture( [] ); - - loaded = 0; - progress = {}; - all = {}; - - urls.map( function ( url, index ) { - - ImageLoader.load( url, function ( image ) { - - texture.images[ index ] = image; - - loaded++; - - if ( loaded === 6 ) { - - texture.needsUpdate = true; - - onLoad( texture ); - - } - - }, function ( event ) { - - progress[ index ] = { loaded: event.loaded, total: event.total }; - - all.loaded = 0; - all.total = 0; - loadings = 0; - - for ( var i in progress ) { - - loadings++; - all.loaded += progress[ i ].loaded; - all.total += progress[ i ].total; - - } - - if ( loadings < 6 ) { - - all.total = all.total / loadings * 6; - - } - - onProgress( all ); - - }, onError ); - - } ); - - return texture; - - } - -}; - -/** - * @classdesc User Media - * @constructor - * @param {object} [constraints={ video: { width: { ideal: 1920 }, height: { ideal: 1080 }, facingMode: { exact: 'environment' } }, audio: false }] - */ -function Media ( constraints ) { - - const defaultConstraints = { video: { width: { ideal: 1920 }, height: { ideal: 1080 }, facingMode: { exact: 'environment' } }, audio: false }; - - this.constraints = Object.assign( defaultConstraints, constraints ); - - this.container = null; - this.scene = null; - this.element = null; - this.devices = []; - this.stream = null; - this.ratioScalar = 1; - this.videoDeviceIndex = 0; - -} -Media.prototype = Object.assign( Object.create( EventDispatcher.prototype ), { - - setContainer: function ( container ) { - - this.container = container; - - }, - - setScene: function ( scene ) { - - this.scene = scene; - - }, - - /** - * Enumerate devices - * @memberOf Media - * @instance - * @returns {Promise} - */ - enumerateDevices: function () { - - const devices = this.devices; - const resolvedPromise = new Promise( resolve => { resolve( devices ); } ); - - return devices.length > 0 ? resolvedPromise : window.navigator.mediaDevices.enumerateDevices(); - - }, - - /** - * Switch to next available video device - * @memberOf Media - * @instance - */ - switchNextVideoDevice: function () { - - const stop = this.stop.bind( this ); - const start = this.start.bind( this ); - const setVideDeviceIndex = this.setVideDeviceIndex.bind( this ); - - let index = this.videoDeviceIndex; - - this.getDevices( 'video' ) - .then( devices => { - stop(); - index++; - if ( index >= devices.length ) { - setVideDeviceIndex( 0 ); - index--; - } else { - setVideDeviceIndex( index ); - } - - start( devices[ index ] ); - - - } ); - - }, - - /** - * Get devices - * @param {string} type - type keyword to match device.kind - * @memberOf Media - * @instance - */ - getDevices: function ( type = 'video' ) { - - const devices = this.devices; - const validate = _devices => { - - return _devices.map( device => { - - if ( !devices.includes( device ) ) { devices.push( device ); } - return device; - - } ); - - }; - const filter = _devices => { - - const reg = new RegExp( type, 'i' ); - return _devices.filter( device => reg.test( device.kind ) ); - - }; - - return this.enumerateDevices() - .then( validate ) - .then( filter ); - - }, - - /** - * Get user media - * @param {MediaStreamConstraints} constraints - * @memberOf Media - * @instance - */ - getUserMedia: function ( constraints ) { - - const setMediaStream = this.setMediaStream.bind( this ); - const playVideo = this.playVideo.bind( this ); - const onCatchError = error => { console.warn( `PANOLENS.Media: ${error}` ); }; - - return window.navigator.mediaDevices.getUserMedia( constraints ) - .then( setMediaStream ) - .then( playVideo ) - .catch( onCatchError ); - - }, - - /** - * Set video device index - * @param {number} index - * @memberOf Media - * @instance - */ - setVideDeviceIndex: function ( index ) { - - this.videoDeviceIndex = index; - - }, - - /** - * Start streaming - * @param {MediaDeviceInfo} [targetDevice] - * @memberOf Media - * @instance - */ - start: function( targetDevice ) { - - const constraints = this.constraints; - const getUserMedia = this.getUserMedia.bind( this ); - const onVideoDevices = devices => { - - if ( !devices || devices.length === 0 ) { - - throw Error( 'no video device found' ); - - } - - const device = targetDevice || devices[ 0 ]; - constraints.video.deviceId = device.deviceId; - - return getUserMedia( constraints ); - - }; - - this.element = this.createVideoElement(); - - return this.getDevices().then( onVideoDevices ); - - }, - - /** - * Stop streaming - * @memberOf Media - * @instance - */ - stop: function () { - - const stream = this.stream; - - if ( stream && stream.active ) { - - const track = stream.getTracks()[ 0 ]; - - track.stop(); - - window.removeEventListener( 'resize', this.onWindowResize.bind( this ) ); - - this.element = null; - this.stream = null; - - } - - }, - - /** - * Set media stream - * @param {MediaStream} stream - * @memberOf Media - * @instance - */ - setMediaStream: function ( stream ) { - - this.stream = stream; - this.element.srcObject = stream; - - if ( this.scene ) { - - this.scene.background = this.createVideoTexture(); - - } - - window.addEventListener( 'resize', this.onWindowResize.bind( this ) ); - - }, - - /** - * Play video element - * @memberOf Media - * @instance - */ - playVideo: function () { - - const { element } = this; - - if ( element ) { - - element.play(); - this.dispatchEvent( { type: 'play' } ); - - } - - }, - - /** - * Pause video element - * @memberOf Media - * @instance - */ - pauseVideo: function () { - - const { element } = this; - - if ( element ) { - - element.pause(); - this.dispatchEvent( { type: 'pause' } ); - - } - - }, - - /** - * Create video texture - * @memberOf Media - * @instance - * @returns {THREE.VideoTexture} - */ - createVideoTexture: function () { - - const video = this.element; - const texture = new VideoTexture( video ); - - texture.generateMipmaps = false; - texture.minFilter = LinearFilter; - texture.magFilter = LinearFilter; - texture.format = RGBFormat; - texture.center.set( 0.5, 0.5 ); - - video.addEventListener( 'canplay', this.onWindowResize.bind( this ) ); - - return texture; - - }, - - /** - * Create video element - * @memberOf Media - * @instance - * @returns {HTMLVideoElement} - * @fires Media#canplay - */ - createVideoElement: function() { - - const dispatchEvent = this.dispatchEvent.bind( this ); - const video = document.createElement( 'video' ); - - /** - * Video can play event - * @type {object} - * @event Media#canplay - */ - const canPlay = () => dispatchEvent( { type: 'canplay' } ); - - video.setAttribute( 'autoplay', '' ); - video.setAttribute( 'muted', '' ); - video.setAttribute( 'playsinline', '' ); - - video.style.position = 'absolute'; - video.style.top = '0'; - video.style.left = '0'; - video.style.width = '100%'; - video.style.height = '100%'; - video.style.objectPosition = 'center'; - video.style.objectFit = 'cover'; - video.style.display = this.scene ? 'none' : ''; - - video.addEventListener( 'canplay', canPlay ); - - return video; - - }, - - /** - * On window resize event - * @param {Event} event - * @memberOf Media - * @instance - */ - onWindowResize: function () { - - if ( this.element && this.element.videoWidth && this.element.videoHeight && this.scene ) { - - const { clientWidth: width, clientHeight: height } = this.container; - const texture = this.scene.background; - const { videoWidth, videoHeight } = this.element; - const cameraRatio = videoHeight / videoWidth; - const viewportRatio = this.container ? width / height : 1.0; - const ratio = cameraRatio * viewportRatio * this.ratioScalar; - - if ( width > height ) { - texture.repeat.set( ratio, 1 ); - } else { - texture.repeat.set( 1, 1 / ratio ); - } - - } - - } - -} ); - -/** - * @classdesc Reticle 3D Sprite - * @constructor - * @param {THREE.Color} [color=0xffffff] - Color of the reticle sprite - * @param {boolean} [autoSelect=true] - Auto selection - * @param {number} [dwellTime=1500] - Duration for dwelling sequence to complete - */ - -function Reticle ( color = 0xffffff, autoSelect = true, dwellTime = 1500 ) { - - this.dpr = window.devicePixelRatio; - - const { canvas, context } = this.createCanvas(); - const material = new SpriteMaterial( { color, map: this.createCanvasTexture( canvas ) } ); - - Sprite.call( this, material ); - - this.canvasWidth = canvas.width; - this.canvasHeight = canvas.height; - this.context = context; - this.color = color instanceof Color ? color : new Color( color ); - - this.autoSelect = autoSelect; - this.dwellTime = dwellTime; - this.rippleDuration = 500; - this.position.z = -10; - this.center.set( 0.5, 0.5 ); - this.scale.set( 0.5, 0.5, 1 ); - - this.startTimestamp = null; - this.timerId = null; - this.callback = null; - - this.frustumCulled = false; - - this.updateCanvasArcByProgress( 0 ); - -} -Reticle.prototype = Object.assign( Object.create( Sprite.prototype ), { - - constructor: Reticle, - - /** - * Set material color - * @param {THREE.Color} color - * @memberOf Reticle - * @instance - */ - setColor: function ( color ) { - - this.material.color.copy( color instanceof Color ? color : new Color( color ) ); - - }, - - /** - * Create canvas texture - * @param {HTMLCanvasElement} canvas - * @memberOf Reticle - * @instance - * @returns {THREE.CanvasTexture} - */ - createCanvasTexture: function ( canvas ) { - - const texture = new CanvasTexture( canvas ); - texture.minFilter = LinearFilter; - texture.magFilter = LinearFilter; - texture.generateMipmaps = false; - - return texture; - - }, - - /** - * Create canvas element - * @memberOf Reticle - * @instance - * @returns {object} object - * @returns {HTMLCanvasElement} object.canvas - * @returns {CanvasRenderingContext2D} object.context - */ - createCanvas: function () { - - const width = 32; - const height = 32; - const canvas = document.createElement( 'canvas' ); - const context = canvas.getContext( '2d' ); - const dpr = this.dpr; - - canvas.width = width * dpr; - canvas.height = height * dpr; - context.scale( dpr, dpr ); - - context.shadowBlur = 5; - context.shadowColor = 'rgba(200,200,200,0.9)'; - - return { canvas, context }; - - }, - - /** - * Update canvas arc by progress - * @param {number} progress - * @memberOf Reticle - * @instance - */ - updateCanvasArcByProgress: function ( progress ) { - - const context = this.context; - const { canvasWidth, canvasHeight, material } = this; - const dpr = this.dpr; - const degree = progress * Math.PI * 2; - const color = this.color.getStyle(); - const x = canvasWidth * 0.5 / dpr; - const y = canvasHeight * 0.5 / dpr; - const lineWidth = 3; - - context.clearRect( 0, 0, canvasWidth, canvasHeight ); - context.beginPath(); - - if ( progress === 0 ) { - context.arc( x, y, canvasWidth / 16, 0, 2 * Math.PI ); - context.fillStyle = color; - context.fill(); - } else { - context.arc( x, y, canvasWidth / 4 - lineWidth, -Math.PI / 2, -Math.PI / 2 + degree ); - context.strokeStyle = color; - context.lineWidth = lineWidth; - context.stroke(); - } - - context.closePath(); - - material.map.needsUpdate = true; - - }, - - /** - * Ripple effect - * @memberOf Reticle - * @instance - * @fires Reticle#reticle-ripple-start - * @fires Reticle#reticle-ripple-end - */ - ripple: function () { - - const context = this.context; - const { canvasWidth, canvasHeight, material } = this; - const duration = this.rippleDuration; - const timestamp = performance.now(); - const color = this.color; - const dpr = this.dpr; - const x = canvasWidth * 0.5 / dpr; - const y = canvasHeight * 0.5 / dpr; - - const update = () => { - - const timerId = window.requestAnimationFrame( update ); - const elapsed = performance.now() - timestamp; - const progress = elapsed / duration; - const opacity = 1.0 - progress > 0 ? 1.0 - progress : 0; - const radius = progress * canvasWidth * 0.5 / dpr; - - context.clearRect( 0, 0, canvasWidth, canvasHeight ); - context.beginPath(); - context.arc( x, y, radius, 0, Math.PI * 2 ); - context.fillStyle = `rgba(${color.r * 255}, ${color.g * 255}, ${color.b * 255}, ${opacity})`; - context.fill(); - context.closePath(); - - if ( progress >= 1.0 ) { - - window.cancelAnimationFrame( timerId ); - this.updateCanvasArcByProgress( 0 ); - - /** - * Reticle ripple end event - * @type {object} - * @event Reticle#reticle-ripple-end - */ - this.dispatchEvent( { type: 'reticle-ripple-end' } ); - - } - - material.map.needsUpdate = true; - - }; - - /** - * Reticle ripple start event - * @type {object} - * @event Reticle#reticle-ripple-start - */ - this.dispatchEvent( { type: 'reticle-ripple-start' } ); - - update(); - - }, - - /** - * Make reticle visible - * @memberOf Reticle - * @instance - */ - show: function () { - - this.visible = true; - - }, - - /** - * Make reticle invisible - * @memberOf Reticle - * @instance - */ - hide: function () { - - this.visible = false; - - }, - - /** - * Start dwelling - * @param {function} callback - * @memberOf Reticle - * @instance - * @fires Reticle#reticle-start - */ - start: function ( callback ) { - - if ( !this.autoSelect ) { - - return; - - } - - /** - * Reticle start event - * @type {object} - * @event Reticle#reticle-start - */ - this.dispatchEvent( { type: 'reticle-start' } ); - - this.startTimestamp = performance.now(); - this.callback = callback; - this.update(); - - }, - - /** - * End dwelling - * @memberOf Reticle - * @instance - * @fires Reticle#reticle-end - */ - end: function(){ - - if ( !this.startTimestamp ) { return; } - - window.cancelAnimationFrame( this.timerId ); - - this.updateCanvasArcByProgress( 0 ); - this.callback = null; - this.timerId = null; - this.startTimestamp = null; - - /** - * Reticle end event - * @type {object} - * @event Reticle#reticle-end - */ - this.dispatchEvent( { type: 'reticle-end' } ); - - }, - - /** - * Update dwelling - * @memberOf Reticle - * @instance - * @fires Reticle#reticle-update - */ - update: function () { - - this.timerId = window.requestAnimationFrame( this.update.bind( this ) ); - - const elapsed = performance.now() - this.startTimestamp; - const progress = elapsed / this.dwellTime; - - this.updateCanvasArcByProgress( progress ); - - /** - * Reticle update event - * @type {object} - * @event Reticle#reticle-update - */ - this.dispatchEvent( { type: 'reticle-update', progress } ); - - if ( progress >= 1.0 ) { - - window.cancelAnimationFrame( this.timerId ); - if ( this.callback ) { this.callback(); } - this.end(); - this.ripple(); - - } - - } - -} ); - -function createCommonjsModule(fn, module) { - return module = { exports: {} }, fn(module, module.exports), module.exports; -} - -var Tween = createCommonjsModule(function (module, exports) { -/** - * Tween.js - Licensed under the MIT license - * https://github.com/tweenjs/tween.js - * ---------------------------------------------- - * - * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors. - * Thank you all, you're awesome! - */ - - -var _Group = function () { - this._tweens = {}; - this._tweensAddedDuringUpdate = {}; -}; - -_Group.prototype = { - getAll: function () { - - return Object.keys(this._tweens).map(function (tweenId) { - return this._tweens[tweenId]; - }.bind(this)); - - }, - - removeAll: function () { - - this._tweens = {}; - - }, - - add: function (tween) { - - this._tweens[tween.getId()] = tween; - this._tweensAddedDuringUpdate[tween.getId()] = tween; - - }, - - remove: function (tween) { - - delete this._tweens[tween.getId()]; - delete this._tweensAddedDuringUpdate[tween.getId()]; - - }, - - update: function (time, preserve) { - - var tweenIds = Object.keys(this._tweens); - - if (tweenIds.length === 0) { - return false; - } - - time = time !== undefined ? time : TWEEN.now(); - - // Tweens are updated in "batches". If you add a new tween during an update, then the - // new tween will be updated in the next batch. - // If you remove a tween during an update, it may or may not be updated. However, - // if the removed tween was added during the current batch, then it will not be updated. - while (tweenIds.length > 0) { - this._tweensAddedDuringUpdate = {}; - - for (var i = 0; i < tweenIds.length; i++) { - - var tween = this._tweens[tweenIds[i]]; - - if (tween && tween.update(time) === false) { - tween._isPlaying = false; - - if (!preserve) { - delete this._tweens[tweenIds[i]]; - } - } - } - - tweenIds = Object.keys(this._tweensAddedDuringUpdate); - } - - return true; - - } -}; - -var TWEEN = new _Group(); - -TWEEN.Group = _Group; -TWEEN._nextId = 0; -TWEEN.nextId = function () { - return TWEEN._nextId++; -}; - - -// Include a performance.now polyfill. -// In node.js, use process.hrtime. -if (typeof (self) === 'undefined' && typeof (process) !== 'undefined' && process.hrtime) { - TWEEN.now = function () { - var time = process.hrtime(); - - // Convert [seconds, nanoseconds] to milliseconds. - return time[0] * 1000 + time[1] / 1000000; - }; -} -// In a browser, use self.performance.now if it is available. -else if (typeof (self) !== 'undefined' && - self.performance !== undefined && - self.performance.now !== undefined) { - // This must be bound, because directly assigning this function - // leads to an invocation exception in Chrome. - TWEEN.now = self.performance.now.bind(self.performance); -} -// Use Date.now if it is available. -else if (Date.now !== undefined) { - TWEEN.now = Date.now; -} -// Otherwise, use 'new Date().getTime()'. -else { - TWEEN.now = function () { - return new Date().getTime(); - }; -} - - -TWEEN.Tween = function (object, group) { - this._object = object; - this._valuesStart = {}; - this._valuesEnd = {}; - this._valuesStartRepeat = {}; - this._duration = 1000; - this._repeat = 0; - this._repeatDelayTime = undefined; - this._yoyo = false; - this._isPlaying = false; - this._reversed = false; - this._delayTime = 0; - this._startTime = null; - this._easingFunction = TWEEN.Easing.Linear.None; - this._interpolationFunction = TWEEN.Interpolation.Linear; - this._chainedTweens = []; - this._onStartCallback = null; - this._onStartCallbackFired = false; - this._onUpdateCallback = null; - this._onRepeatCallback = null; - this._onCompleteCallback = null; - this._onStopCallback = null; - this._group = group || TWEEN; - this._id = TWEEN.nextId(); - -}; - -TWEEN.Tween.prototype = { - getId: function () { - return this._id; - }, - - isPlaying: function () { - return this._isPlaying; - }, - - to: function (properties, duration) { - - this._valuesEnd = Object.create(properties); - - if (duration !== undefined) { - this._duration = duration; - } - - return this; - - }, - - duration: function duration(d) { - this._duration = d; - return this; - }, - - start: function (time) { - - this._group.add(this); - - this._isPlaying = true; - - this._onStartCallbackFired = false; - - this._startTime = time !== undefined ? typeof time === 'string' ? TWEEN.now() + parseFloat(time) : time : TWEEN.now(); - this._startTime += this._delayTime; - - for (var property in this._valuesEnd) { - - // Check if an Array was provided as property value - if (this._valuesEnd[property] instanceof Array) { - - if (this._valuesEnd[property].length === 0) { - continue; - } - - // Create a local copy of the Array with the start value at the front - this._valuesEnd[property] = [this._object[property]].concat(this._valuesEnd[property]); - - } - - // If `to()` specifies a property that doesn't exist in the source object, - // we should not set that property in the object - if (this._object[property] === undefined) { - continue; - } - - // Save the starting value. - this._valuesStart[property] = this._object[property]; - - if ((this._valuesStart[property] instanceof Array) === false) { - this._valuesStart[property] *= 1.0; // Ensures we're using numbers, not strings - } - - this._valuesStartRepeat[property] = this._valuesStart[property] || 0; - - } - - return this; - - }, - - stop: function () { - - if (!this._isPlaying) { - return this; - } - - this._group.remove(this); - this._isPlaying = false; - - if (this._onStopCallback !== null) { - this._onStopCallback(this._object); - } - - this.stopChainedTweens(); - return this; - - }, - - end: function () { - - this.update(Infinity); - return this; - - }, - - stopChainedTweens: function () { - - for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { - this._chainedTweens[i].stop(); - } - - }, - - group: function (group) { - this._group = group; - return this; - }, - - delay: function (amount) { - - this._delayTime = amount; - return this; - - }, - - repeat: function (times) { - - this._repeat = times; - return this; - - }, - - repeatDelay: function (amount) { - - this._repeatDelayTime = amount; - return this; - - }, - - yoyo: function (yoyo) { - - this._yoyo = yoyo; - return this; - - }, - - easing: function (easingFunction) { - - this._easingFunction = easingFunction; - return this; - - }, - - interpolation: function (interpolationFunction) { - - this._interpolationFunction = interpolationFunction; - return this; - - }, - - chain: function () { - - this._chainedTweens = arguments; - return this; - - }, - - onStart: function (callback) { - - this._onStartCallback = callback; - return this; - - }, - - onUpdate: function (callback) { - - this._onUpdateCallback = callback; - return this; - - }, - - onRepeat: function onRepeat(callback) { - - this._onRepeatCallback = callback; - return this; - - }, - - onComplete: function (callback) { - - this._onCompleteCallback = callback; - return this; - - }, - - onStop: function (callback) { - - this._onStopCallback = callback; - return this; - - }, - - update: function (time) { - - var property; - var elapsed; - var value; - - if (time < this._startTime) { - return true; - } - - if (this._onStartCallbackFired === false) { - - if (this._onStartCallback !== null) { - this._onStartCallback(this._object); - } - - this._onStartCallbackFired = true; - } - - elapsed = (time - this._startTime) / this._duration; - elapsed = (this._duration === 0 || elapsed > 1) ? 1 : elapsed; - - value = this._easingFunction(elapsed); - - for (property in this._valuesEnd) { - - // Don't update properties that do not exist in the source object - if (this._valuesStart[property] === undefined) { - continue; - } - - var start = this._valuesStart[property] || 0; - var end = this._valuesEnd[property]; - - if (end instanceof Array) { - - this._object[property] = this._interpolationFunction(end, value); - - } else { - - // Parses relative end values with start as base (e.g.: +10, -3) - if (typeof (end) === 'string') { - - if (end.charAt(0) === '+' || end.charAt(0) === '-') { - end = start + parseFloat(end); - } else { - end = parseFloat(end); - } - } - - // Protect against non numeric properties. - if (typeof (end) === 'number') { - this._object[property] = start + (end - start) * value; - } - - } - - } - - if (this._onUpdateCallback !== null) { - this._onUpdateCallback(this._object, elapsed); - } - - if (elapsed === 1) { - - if (this._repeat > 0) { - - if (isFinite(this._repeat)) { - this._repeat--; - } - - // Reassign starting values, restart by making startTime = now - for (property in this._valuesStartRepeat) { - - if (typeof (this._valuesEnd[property]) === 'string') { - this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]); - } - - if (this._yoyo) { - var tmp = this._valuesStartRepeat[property]; - - this._valuesStartRepeat[property] = this._valuesEnd[property]; - this._valuesEnd[property] = tmp; - } - - this._valuesStart[property] = this._valuesStartRepeat[property]; - - } - - if (this._yoyo) { - this._reversed = !this._reversed; - } - - if (this._repeatDelayTime !== undefined) { - this._startTime = time + this._repeatDelayTime; - } else { - this._startTime = time + this._delayTime; - } - - if (this._onRepeatCallback !== null) { - this._onRepeatCallback(this._object); - } - - return true; - - } else { - - if (this._onCompleteCallback !== null) { - - this._onCompleteCallback(this._object); - } - - for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { - // Make the chained tweens start exactly at the time they should, - // even if the `update()` method was called way past the duration of the tween - this._chainedTweens[i].start(this._startTime + this._duration); - } - - return false; - - } - - } - - return true; - - } -}; - - -TWEEN.Easing = { - - Linear: { - - None: function (k) { - - return k; - - } - - }, - - Quadratic: { - - In: function (k) { - - return k * k; - - }, - - Out: function (k) { - - return k * (2 - k); - - }, - - InOut: function (k) { - - if ((k *= 2) < 1) { - return 0.5 * k * k; - } - - return - 0.5 * (--k * (k - 2) - 1); - - } - - }, - - Cubic: { - - In: function (k) { - - return k * k * k; - - }, - - Out: function (k) { - - return --k * k * k + 1; - - }, - - InOut: function (k) { - - if ((k *= 2) < 1) { - return 0.5 * k * k * k; - } - - return 0.5 * ((k -= 2) * k * k + 2); - - } - - }, - - Quartic: { - - In: function (k) { - - return k * k * k * k; - - }, - - Out: function (k) { - - return 1 - (--k * k * k * k); - - }, - - InOut: function (k) { - - if ((k *= 2) < 1) { - return 0.5 * k * k * k * k; - } - - return - 0.5 * ((k -= 2) * k * k * k - 2); - - } - - }, - - Quintic: { - - In: function (k) { - - return k * k * k * k * k; - - }, - - Out: function (k) { - - return --k * k * k * k * k + 1; - - }, - - InOut: function (k) { - - if ((k *= 2) < 1) { - return 0.5 * k * k * k * k * k; - } - - return 0.5 * ((k -= 2) * k * k * k * k + 2); - - } - - }, - - Sinusoidal: { - - In: function (k) { - - return 1 - Math.cos(k * Math.PI / 2); - - }, - - Out: function (k) { - - return Math.sin(k * Math.PI / 2); - - }, - - InOut: function (k) { - - return 0.5 * (1 - Math.cos(Math.PI * k)); - - } - - }, - - Exponential: { - - In: function (k) { - - return k === 0 ? 0 : Math.pow(1024, k - 1); - - }, - - Out: function (k) { - - return k === 1 ? 1 : 1 - Math.pow(2, - 10 * k); - - }, - - InOut: function (k) { - - if (k === 0) { - return 0; - } - - if (k === 1) { - return 1; - } - - if ((k *= 2) < 1) { - return 0.5 * Math.pow(1024, k - 1); - } - - return 0.5 * (- Math.pow(2, - 10 * (k - 1)) + 2); - - } - - }, - - Circular: { - - In: function (k) { - - return 1 - Math.sqrt(1 - k * k); - - }, - - Out: function (k) { - - return Math.sqrt(1 - (--k * k)); - - }, - - InOut: function (k) { - - if ((k *= 2) < 1) { - return - 0.5 * (Math.sqrt(1 - k * k) - 1); - } - - return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1); - - } - - }, - - Elastic: { - - In: function (k) { - - if (k === 0) { - return 0; - } - - if (k === 1) { - return 1; - } - - return -Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI); - - }, - - Out: function (k) { - - if (k === 0) { - return 0; - } - - if (k === 1) { - return 1; - } - - return Math.pow(2, -10 * k) * Math.sin((k - 0.1) * 5 * Math.PI) + 1; - - }, - - InOut: function (k) { - - if (k === 0) { - return 0; - } - - if (k === 1) { - return 1; - } - - k *= 2; - - if (k < 1) { - return -0.5 * Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI); - } - - return 0.5 * Math.pow(2, -10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI) + 1; - - } - - }, - - Back: { - - In: function (k) { - - var s = 1.70158; - - return k * k * ((s + 1) * k - s); - - }, - - Out: function (k) { - - var s = 1.70158; - - return --k * k * ((s + 1) * k + s) + 1; - - }, - - InOut: function (k) { - - var s = 1.70158 * 1.525; - - if ((k *= 2) < 1) { - return 0.5 * (k * k * ((s + 1) * k - s)); - } - - return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2); - - } - - }, - - Bounce: { - - In: function (k) { - - return 1 - TWEEN.Easing.Bounce.Out(1 - k); - - }, - - Out: function (k) { - - if (k < (1 / 2.75)) { - return 7.5625 * k * k; - } else if (k < (2 / 2.75)) { - return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75; - } else if (k < (2.5 / 2.75)) { - return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375; - } else { - return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375; - } - - }, - - InOut: function (k) { - - if (k < 0.5) { - return TWEEN.Easing.Bounce.In(k * 2) * 0.5; - } - - return TWEEN.Easing.Bounce.Out(k * 2 - 1) * 0.5 + 0.5; - - } - - } - -}; - -TWEEN.Interpolation = { - - Linear: function (v, k) { - - var m = v.length - 1; - var f = m * k; - var i = Math.floor(f); - var fn = TWEEN.Interpolation.Utils.Linear; - - if (k < 0) { - return fn(v[0], v[1], f); - } - - if (k > 1) { - return fn(v[m], v[m - 1], m - f); - } - - return fn(v[i], v[i + 1 > m ? m : i + 1], f - i); - - }, - - Bezier: function (v, k) { - - var b = 0; - var n = v.length - 1; - var pw = Math.pow; - var bn = TWEEN.Interpolation.Utils.Bernstein; - - for (var i = 0; i <= n; i++) { - b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i); - } - - return b; - - }, - - CatmullRom: function (v, k) { - - var m = v.length - 1; - var f = m * k; - var i = Math.floor(f); - var fn = TWEEN.Interpolation.Utils.CatmullRom; - - if (v[0] === v[m]) { - - if (k < 0) { - i = Math.floor(f = m * (1 + k)); - } - - return fn(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i); - - } else { - - if (k < 0) { - return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]); - } - - if (k > 1) { - return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]); - } - - return fn(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i); - - } - - }, - - Utils: { - - Linear: function (p0, p1, t) { - - return (p1 - p0) * t + p0; - - }, - - Bernstein: function (n, i) { - - var fc = TWEEN.Interpolation.Utils.Factorial; - - return fc(n) / fc(i) / fc(n - i); - - }, - - Factorial: (function () { - - var a = [1]; - - return function (n) { - - var s = 1; - - if (a[n]) { - return a[n]; - } - - for (var i = n; i > 1; i--) { - s *= i; - } - - a[n] = s; - return s; - - }; - - })(), - - CatmullRom: function (p0, p1, p2, p3, t) { - - var v0 = (p2 - p0) * 0.5; - var v1 = (p3 - p1) * 0.5; - var t2 = t * t; - var t3 = t * t2; - - return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (- 3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1; - - } - - } - -}; - -// UMD (Universal Module Definition) -(function (root) { - - { - - // Node.js - module.exports = TWEEN; - - } - -})(); -}); - -/** - * @classdesc Information spot attached to panorama - * @constructor - * @param {number} [scale=300] - Default scale - * @param {string} [imageSrc=PANOLENS.DataImage.Info] - Image overlay info - * @param {boolean} [animated=true] - Enable default hover animation - */ -function Infospot ( scale = 300, imageSrc, animated ) { - - const duration = 500, scaleFactor = 1.3; - - imageSrc = imageSrc || DataImage.Info; - - Sprite.call( this ); - - this.type = 'infospot'; - - this.animated = animated !== undefined ? animated : true; - this.isHovering = false; - - /* - * TODO: Three.js bug hotfix for sprite raycasting r104 - * https://github.com/mrdoob/three.js/issues/14624 - */ - this.frustumCulled = false; - - this.element = null; - this.toPanorama = null; - this.cursorStyle = null; - - this.mode = MODES.NORMAL; - - this.scale.set( scale, scale, 1 ); - this.rotation.y = Math.PI; - - this.container = null; - - this.originalRaycast = this.raycast; - - // Event Handler - this.HANDLER_FOCUS = null; - - this.material.side = DoubleSide; - this.material.depthTest = false; - this.material.transparent = true; - this.material.opacity = 0; - - this.scaleUpAnimation = new Tween.Tween(); - this.scaleDownAnimation = new Tween.Tween(); - - - const postLoad = function ( texture ) { - - if ( !this.material ) { return; } - - const ratio = texture.image.width / texture.image.height; - const textureScale = new Vector3(); - - texture.image.width = texture.image.naturalWidth || 64; - texture.image.height = texture.image.naturalHeight || 64; - - this.scale.set( ratio * scale, scale, 1 ); - - textureScale.copy( this.scale ); - - this.scaleUpAnimation = new Tween.Tween( this.scale ) - .to( { x: textureScale.x * scaleFactor, y: textureScale.y * scaleFactor }, duration ) - .easing( Tween.Easing.Elastic.Out ); - - this.scaleDownAnimation = new Tween.Tween( this.scale ) - .to( { x: textureScale.x, y: textureScale.y }, duration ) - .easing( Tween.Easing.Elastic.Out ); - - this.material.map = texture; - this.material.needsUpdate = true; - - }.bind( this ); - - // Add show and hide animations - this.showAnimation = new Tween.Tween( this.material ) - .to( { opacity: 1 }, duration ) - .onStart( this.enableRaycast.bind( this, true ) ) - .easing( Tween.Easing.Quartic.Out ); - - this.hideAnimation = new Tween.Tween( this.material ) - .to( { opacity: 0 }, duration ) - .onStart( this.enableRaycast.bind( this, false ) ) - .easing( Tween.Easing.Quartic.Out ); - - // Attach event listeners - this.addEventListener( 'click', this.onClick ); - this.addEventListener( 'hover', this.onHover ); - this.addEventListener( 'hoverenter', this.onHoverStart ); - this.addEventListener( 'hoverleave', this.onHoverEnd ); - this.addEventListener( 'panolens-dual-eye-effect', this.onDualEyeEffect ); - this.addEventListener( 'panolens-container', this.setContainer.bind( this ) ); - this.addEventListener( 'dismiss', this.onDismiss ); - this.addEventListener( 'panolens-infospot-focus', this.setFocusMethod ); - - TextureLoader.load( imageSrc, postLoad ); - -} -Infospot.prototype = Object.assign( Object.create( Sprite.prototype ), { - - constructor: Infospot, - - /** - * Set infospot container - * @param {HTMLElement|object} data - Data with container information - * @memberOf Infospot - * @instance - */ - setContainer: function ( data ) { - - let container; - - if ( data instanceof HTMLElement ) { - - container = data; - - } else if ( data && data.container ) { - - container = data.container; - - } - - // Append element if exists - if ( container && this.element ) { - - container.appendChild( this.element ); - - } - - this.container = container; - - }, - - /** - * Get container - * @memberOf Infospot - * @instance - * @return {HTMLElement} - The container of this infospot - */ - getContainer: function () { - - return this.container; - - }, - - /** - * This will be called by a click event - * Translate and lock the hovering element if any - * @param {object} event - Event containing mouseEvent with clientX and clientY - * @memberOf Infospot - * @instance - */ - onClick: function ( event ) { - - if ( this.element && this.getContainer() ) { - - this.onHoverStart( event ); - - // Lock element - this.lockHoverElement(); - - } - - }, - - /** - * Dismiss current element if any - * @param {object} event - Dismiss event - * @memberOf Infospot - * @instance - */ - onDismiss: function () { - - if ( this.element ) { - - this.unlockHoverElement(); - this.onHoverEnd(); - - } - - }, - - /** - * This will be called by a mouse hover event - * Translate the hovering element if any - * @param {object} event - Event containing mouseEvent with clientX and clientY - * @memberOf Infospot - * @instance - */ - onHover: function () {}, - - /** - * This will be called on a mouse hover start - * Sets cursor style to 'pointer', display the element and scale up the infospot - * @param {object} event - * @memberOf Infospot - * @instance - */ - onHoverStart: function ( event ) { - - if ( !this.getContainer() ) { return; } - - const cursorStyle = this.cursorStyle || ( this.mode === MODES.NORMAL ? 'pointer' : 'default' ); - const { scaleDownAnimation, scaleUpAnimation, element } = this; - - this.isHovering = true; - this.container.style.cursor = cursorStyle; - - if ( this.animated ) { - - scaleDownAnimation.stop(); - scaleUpAnimation.start(); - - } - - if ( element && event.mouseEvent.clientX >= 0 && event.mouseEvent.clientY >= 0 ) { - - const { left, right, style } = element; - - if ( this.mode === MODES.CARDBOARD || this.mode === MODES.STEREO ) { - - style.display = 'none'; - left.style.display = 'block'; - right.style.display = 'block'; - - // Store element width for reference - element._width = left.clientWidth; - element._height = left.clientHeight; - - } else { - - style.display = 'block'; - if ( left ) { left.style.display = 'none'; } - if ( right ) { right.style.display = 'none'; } - - // Store element width for reference - element._width = element.clientWidth; - element._height = element.clientHeight; - - } - - } - - }, - - /** - * This will be called on a mouse hover end - * Sets cursor style to 'default', hide the element and scale down the infospot - * @memberOf Infospot - * @instance - */ - onHoverEnd: function () { - - if ( !this.getContainer() ) { return; } - - const { scaleDownAnimation, scaleUpAnimation, element } = this; - - this.isHovering = false; - this.container.style.cursor = 'default'; - - if ( this.animated ) { - - scaleUpAnimation.stop(); - scaleDownAnimation.start(); - - } - - if ( element && !this.element.locked ) { - - const { left, right, style } = element; - - style.display = 'none'; - if ( left ) { left.style.display = 'none'; } - if ( right ) { right.style.display = 'none'; } - - this.unlockHoverElement(); - - } - - }, - - /** - * On dual eye effect handler - * Creates duplicate left and right element - * @param {object} event - panolens-dual-eye-effect event - * @memberOf Infospot - * @instance - */ - onDualEyeEffect: function ( event ) { - - if ( !this.getContainer() ) { return; } - - let element, halfWidth, halfHeight; - - this.mode = event.mode; - - element = this.element; - - halfWidth = this.container.clientWidth / 2; - halfHeight = this.container.clientHeight / 2; - - if ( !element ) { - - return; - - } - - if ( !element.left && !element.right ) { - - element.left = element.cloneNode( true ); - element.right = element.cloneNode( true ); - - } - - if ( this.mode === MODES.CARDBOARD || this.mode === MODES.STEREO ) { - - element.left.style.display = element.style.display; - element.right.style.display = element.style.display; - element.style.display = 'none'; - - } else { - - element.style.display = element.left.style.display; - element.left.style.display = 'none'; - element.right.style.display = 'none'; - - } - - // Update elements translation - this.translateElement( halfWidth, halfHeight ); - - this.container.appendChild( element.left ); - this.container.appendChild( element.right ); - - }, - - /** - * Translate the hovering element by css transform - * @param {number} x - X position on the window screen - * @param {number} y - Y position on the window screen - * @memberOf Infospot - * @instance - */ - translateElement: function ( x, y ) { - - if ( !this.element._width || !this.element._height || !this.getContainer() ) { - - return; - - } - - let left, top, element, width, height, delta, container; - - container = this.container; - element = this.element; - width = element._width / 2; - height = element._height / 2; - delta = element.verticalDelta !== undefined ? element.verticalDelta : 40; - - left = x - width; - top = y - height - delta; - - if ( ( this.mode === MODES.CARDBOARD || this.mode === MODES.STEREO ) - && element.left && element.right - && !( x === container.clientWidth / 2 && y === container.clientHeight / 2 ) ) { - - left = container.clientWidth / 4 - width + ( x - container.clientWidth / 2 ); - top = container.clientHeight / 2 - height - delta + ( y - container.clientHeight / 2 ); - - this.setElementStyle( 'transform', element.left, 'translate(' + left + 'px, ' + top + 'px)' ); - - left += container.clientWidth / 2; - - this.setElementStyle( 'transform', element.right, 'translate(' + left + 'px, ' + top + 'px)' ); - - } else { - - this.setElementStyle( 'transform', element, 'translate(' + left + 'px, ' + top + 'px)' ); - - } - - }, - - /** - * Set vendor specific css - * @param {string} type - CSS style name - * @param {HTMLElement} element - The element to be modified - * @param {string} value - Style value - * @memberOf Infospot - * @instance - */ - setElementStyle: function ( type, element, value ) { - - const style = element.style; - - if ( type === 'transform' ) { - - style.webkitTransform = style.msTransform = style.transform = value; - - } - - }, - - /** - * Set hovering text content - * @param {string} text - Text to be displayed - * @memberOf Infospot - * @instance - */ - setText: function ( text ) { - - if ( this.element ) { - - this.element.textContent = text; - - } - - }, - - /** - * Set cursor css style on hover - * @memberOf Infospot - * @instance - */ - setCursorHoverStyle: function ( style ) { - - this.cursorStyle = style; - - }, - - /** - * Add hovering text element - * @param {string} text - Text to be displayed - * @param {number} [delta=40] - Vertical delta to the infospot - * @memberOf Infospot - * @instance - */ - addHoverText: function ( text, delta = 40 ) { - - if ( !this.element ) { - - this.element = document.createElement( 'div' ); - this.element.style.display = 'none'; - this.element.style.color = '#fff'; - this.element.style.top = 0; - this.element.style.maxWidth = '50%'; - this.element.style.maxHeight = '50%'; - this.element.style.textShadow = '0 0 3px #000000'; - this.element.style.fontFamily = '"Trebuchet MS", Helvetica, sans-serif'; - this.element.style.position = 'absolute'; - this.element.classList.add( 'panolens-infospot' ); - this.element.verticalDelta = delta; - - } - - this.setText( text ); - - }, - - /** - * Add hovering element by cloning an element - * @param {HTMLDOMElement} el - Element to be cloned and displayed - * @param {number} [delta=40] - Vertical delta to the infospot - * @memberOf Infospot - * @instance - */ - addHoverElement: function ( el, delta = 40 ) { - - if ( !this.element ) { - - this.element = el.cloneNode( true ); - this.element.style.display = 'none'; - this.element.style.top = 0; - this.element.style.position = 'absolute'; - this.element.classList.add( 'panolens-infospot' ); - this.element.verticalDelta = delta; - - } - - }, - - /** - * Remove hovering element - * @memberOf Infospot - * @instance - */ - removeHoverElement: function () { - - if ( this.element ) { - - if ( this.element.left ) { - - this.container.removeChild( this.element.left ); - this.element.left = null; - - } - - if ( this.element.right ) { - - this.container.removeChild( this.element.right ); - this.element.right = null; - - } - - this.container.removeChild( this.element ); - this.element = null; - - } - - }, - - /** - * Lock hovering element - * @memberOf Infospot - * @instance - */ - lockHoverElement: function () { - - if ( this.element ) { - - this.element.locked = true; - - } - - }, - - /** - * Unlock hovering element - * @memberOf Infospot - * @instance - */ - unlockHoverElement: function () { - - if ( this.element ) { - - this.element.locked = false; - - } - - }, - - /** - * Enable raycasting - * @param {boolean} [enabled=true] - * @memberOf Infospot - * @instance - */ - enableRaycast: function ( enabled = true ) { - - if ( enabled ) { - - this.raycast = this.originalRaycast; - - } else { - - this.raycast = () => {}; - - } - - }, - - /** - * Show infospot - * @param {number} [delay=0] - Delay time to show - * @memberOf Infospot - * @instance - */ - show: function ( delay = 0 ) { - - const { animated, hideAnimation, showAnimation, material } = this; - - if ( animated ) { - - hideAnimation.stop(); - showAnimation.delay( delay ).start(); - - } else { - - this.enableRaycast( true ); - material.opacity = 1; - - } - - }, - - /** - * Hide infospot - * @param {number} [delay=0] - Delay time to hide - * @memberOf Infospot - * @instance - */ - hide: function ( delay = 0 ) { - - const { animated, hideAnimation, showAnimation, material, element } = this; - - if ( element ) { - const { style } = element; - style.display = 'none'; - } - - if ( animated ) { - - showAnimation.stop(); - hideAnimation.delay( delay ).start(); - - } else { - - this.enableRaycast( false ); - material.opacity = 0; - - } - - }, - - /** - * Set focus event handler - * @memberOf Infospot - * @instance - */ - setFocusMethod: function ( event ) { - - if ( event ) { - - this.HANDLER_FOCUS = event.method; - - } - - }, - - /** - * Focus camera center to this infospot - * @param {number} [duration=1000] - Duration to tween - * @param {function} [easing=TWEEN.Easing.Exponential.Out] - Easing function - * @memberOf Infospot - * @instance - */ - focus: function ( duration, easing ) { - - if ( this.HANDLER_FOCUS ) { - - this.HANDLER_FOCUS( this.position, duration, easing ); - this.onDismiss(); - - } - - }, - - /** - * Dispose - * @memberOf Infospot - * @instance - */ - dispose: function () { - - const { geometry, material } = this; - const { map } = material; - - this.removeHoverElement(); - - if ( this.parent ) { - - this.parent.remove( this ); - - } - - if ( map ) { map.dispose(); material.map = null; } - if ( geometry ) { geometry.dispose(); this.geometry = null; } - if ( material ) { material.dispose(); this.material = null; } - - } - -} ); - -/** - * @classdesc Widget for controls - * @constructor - * @param {HTMLElement} container - A domElement where default control widget will be attached to - */ -function Widget ( container ) { - - if ( !container ) { - - console.warn( 'PANOLENS.Widget: No container specified' ); - - } - - EventDispatcher.call( this ); - - this.DEFAULT_TRANSITION = 'all 0.27s ease'; - this.TOUCH_ENABLED = !!(( 'ontouchstart' in window ) || window.DocumentTouch && document instanceof DocumentTouch); - this.PREVENT_EVENT_HANDLER = function ( event ) { - event.preventDefault(); - event.stopPropagation(); - }; - - this.container = container; - - this.barElement = null; - this.fullscreenElement = null; - this.videoElement = null; - this.settingElement = null; - - this.mainMenu = null; - - this.activeMainItem = null; - this.activeSubMenu = null; - this.mask = null; - -} - -Widget.prototype = Object.assign( Object.create( EventDispatcher.prototype ), { - - constructor: Widget, - - /** - * Add control bar - * @memberOf Widget - * @instance - */ - addControlBar: function () { - - if ( !this.container ) { - - console.warn( 'Widget container not set' ); - return; - } - - var scope = this, bar, styleTranslate, styleOpacity, gradientStyle; - - gradientStyle = 'linear-gradient(bottom, rgba(0,0,0,0.2), rgba(0,0,0,0))'; - - bar = document.createElement( 'div' ); - bar.style.width = '100%'; - bar.style.height = '44px'; - bar.style.float = 'left'; - bar.style.transform = bar.style.webkitTransform = bar.style.msTransform = 'translateY(-100%)'; - bar.style.background = '-webkit-' + gradientStyle; - bar.style.background = '-moz-' + gradientStyle; - bar.style.background = '-o-' + gradientStyle; - bar.style.background = '-ms-' + gradientStyle; - bar.style.background = gradientStyle; - bar.style.transition = this.DEFAULT_TRANSITION; - bar.style.pointerEvents = 'none'; - bar.isHidden = false; - bar.toggle = function () { - bar.isHidden = !bar.isHidden; - styleTranslate = bar.isHidden ? 'translateY(0)' : 'translateY(-100%)'; - styleOpacity = bar.isHidden ? 0 : 1; - bar.style.transform = bar.style.webkitTransform = bar.style.msTransform = styleTranslate; - bar.style.opacity = styleOpacity; - }; - - // Menu - var menu = this.createDefaultMenu(); - this.mainMenu = this.createMainMenu( menu ); - bar.appendChild( this.mainMenu ); - - // Mask - var mask = this.createMask(); - this.mask = mask; - this.container.appendChild( mask ); - - // Dispose - bar.dispose = function () { - - if ( scope.fullscreenElement ) { - - bar.removeChild( scope.fullscreenElement ); - scope.fullscreenElement.dispose(); - scope.fullscreenElement = null; - - } - - if ( scope.settingElement ) { - - bar.removeChild( scope.settingElement ); - scope.settingElement.dispose(); - scope.settingElement = null; - - } - - if ( scope.videoElement ) { - - bar.removeChild( scope.videoElement ); - scope.videoElement.dispose(); - scope.videoElement = null; - - } - - }; - - this.container.appendChild( bar ); - - // Mask events - this.mask.addEventListener( 'mousemove', this.PREVENT_EVENT_HANDLER, true ); - this.mask.addEventListener( 'mouseup', this.PREVENT_EVENT_HANDLER, true ); - this.mask.addEventListener( 'mousedown', this.PREVENT_EVENT_HANDLER, true ); - this.mask.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', function ( event ) { - - event.preventDefault(); - event.stopPropagation(); - - scope.mask.hide(); - scope.settingElement.deactivate(); - - }, false ); - - // Event listener - this.addEventListener( 'control-bar-toggle', bar.toggle ); - - this.barElement = bar; - - }, - - /** - * Create default menu - * @memberOf Widget - * @instance - */ - createDefaultMenu: function () { - - var scope = this, handler; - - handler = function ( method, data ) { - - return function () { - - scope.dispatchEvent( { - - type: 'panolens-viewer-handler', - method: method, - data: data - - } ); - - }; - - }; - - return [ - - { - title: 'Control', - subMenu: [ - { - title: this.TOUCH_ENABLED ? 'Touch' : 'Mouse', - handler: handler( 'enableControl', CONTROLS.ORBIT ) - }, - { - title: 'Sensor', - handler: handler( 'enableControl', CONTROLS.DEVICEORIENTATION ) - } - ] - }, - - { - title: 'Mode', - subMenu: [ - { - title: 'Normal', - handler: handler( 'disableEffect' ) - }, - { - title: 'Cardboard', - handler: handler( 'enableEffect', MODES.CARDBOARD ) - }, - { - title: 'Stereoscopic', - handler: handler( 'enableEffect', MODES.STEREO ) - } - ] - } - - ]; - - }, - - /** - * Add buttons on top of control bar - * @param {string} name - The control button name to be created - * @memberOf Widget - * @instance - */ - addControlButton: function ( name ) { - - let element; - - switch( name ) { - - case 'fullscreen': - - element = this.createFullscreenButton(); - this.fullscreenElement = element; - - break; - - case 'setting': - - element = this.createSettingButton(); - this.settingElement = element; - - break; - - case 'video': - - element = this.createVideoControl(); - this.videoElement = element; - - break; - - default: - - return; - - } - - if ( !element ) { - - return; - - } - - this.barElement.appendChild( element ); - - }, - - /** - * Create modal mask - * @memberOf Widget - * @instance - */ - createMask: function () { - - const element = document.createElement( 'div' ); - element.style.position = 'absolute'; - element.style.top = 0; - element.style.left = 0; - element.style.width = '100%'; - element.style.height = '100%'; - element.style.background = 'transparent'; - element.style.display = 'none'; - - element.show = function () { - - this.style.display = 'block'; - - }; - - element.hide = function () { - - this.style.display = 'none'; - - }; - - return element; - - }, - - /** - * Create Setting button to toggle menu - * @memberOf Widget - * @instance - */ - createSettingButton: function () { - - let scope = this, item; - - function onTap ( event ) { - - event.preventDefault(); - event.stopPropagation(); - - scope.mainMenu.toggle(); - - if ( this.activated ) { - - this.deactivate(); - - } else { - - this.activate(); - - } - - } - - item = this.createCustomItem( { - - style: { - - backgroundImage: 'url("' + DataImage.Setting + '")', - webkitTransition: this.DEFAULT_TRANSITION, - transition: this.DEFAULT_TRANSITION - - }, - - onTap: onTap - - } ); - - item.activate = function () { - - this.style.transform = 'rotate3d(0,0,1,90deg)'; - this.activated = true; - scope.mask.show(); - - }; - - item.deactivate = function () { - - this.style.transform = 'rotate3d(0,0,0,0)'; - this.activated = false; - scope.mask.hide(); - - if ( scope.mainMenu && scope.mainMenu.visible ) { - - scope.mainMenu.hide(); - - } - - if ( scope.activeSubMenu && scope.activeSubMenu.visible ) { - - scope.activeSubMenu.hide(); - - } - - if ( scope.mainMenu && scope.mainMenu._width ) { - - scope.mainMenu.changeSize( scope.mainMenu._width ); - scope.mainMenu.unslideAll(); - - } - - }; - - item.activated = false; - - return item; - - }, - - /** - * Create Fullscreen button - * @return {HTMLSpanElement} - The dom element icon for fullscreen - * @memberOf Widget - * @instance - * @fires Widget#panolens-viewer-handler - */ - createFullscreenButton: function () { - - let scope = this, item, isFullscreen = false, tapSkipped = true, stylesheetId; - - const { container } = this; - - stylesheetId = 'panolens-style-addon'; - - // Don't create button if no support - if ( !document.fullscreenEnabled && - !document.webkitFullscreenEnabled && - !document.mozFullScreenEnabled && - !document.msFullscreenEnabled ) { - return; - } - - function onTap ( event ) { - - event.preventDefault(); - event.stopPropagation(); - - tapSkipped = false; - - if ( !isFullscreen ) { - - if ( container.requestFullscreen ) { container.requestFullscreen(); } - if ( container.msRequestFullscreen ) { container.msRequestFullscreen(); } - if ( container.mozRequestFullScreen ) { container.mozRequestFullScreen(); } - if ( container.webkitRequestFullscreen ) { container.webkitRequestFullscreen( Element.ALLOW_KEYBOARD_INPUT ); } - - isFullscreen = true; - - } else { - - if ( document.exitFullscreen ) { document.exitFullscreen(); } - if ( document.msExitFullscreen ) { document.msExitFullscreen(); } - if ( document.mozCancelFullScreen ) { document.mozCancelFullScreen(); } - if ( document.webkitExitFullscreen ) { document.webkitExitFullscreen( ); } - - isFullscreen = false; - - } - - this.style.backgroundImage = ( isFullscreen ) - ? 'url("' + DataImage.FullscreenLeave + '")' - : 'url("' + DataImage.FullscreenEnter + '")'; - - } - - function onFullScreenChange () { - - if ( tapSkipped ) { - - isFullscreen = !isFullscreen; - - item.style.backgroundImage = ( isFullscreen ) - ? 'url("' + DataImage.FullscreenLeave + '")' - : 'url("' + DataImage.FullscreenEnter + '")'; - - } - - /** - * Viewer handler event - * @type {object} - * @event Widget#panolens-viewer-handler - * @property {string} method - 'onWindowResize' function call on Viewer - */ - scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onWindowResize' } ); - - tapSkipped = true; - - } - - document.addEventListener( 'fullscreenchange', onFullScreenChange, false ); - document.addEventListener( 'webkitfullscreenchange', onFullScreenChange, false ); - document.addEventListener( 'mozfullscreenchange', onFullScreenChange, false ); - document.addEventListener( 'MSFullscreenChange', onFullScreenChange, false ); - - item = this.createCustomItem( { - - style: { - - backgroundImage: 'url("' + DataImage.FullscreenEnter + '")' - - }, - - onTap: onTap - - } ); - - // Add fullscreen stlye if not exists - if ( !document.querySelector( stylesheetId ) ) { - const sheet = document.createElement( 'style' ); - sheet.id = stylesheetId; - sheet.innerHTML = ':-webkit-full-screen { width: 100% !important; height: 100% !important }'; - document.body.appendChild( sheet ); - } - - return item; - - }, - - /** - * Create video control container - * @memberOf Widget - * @instance - * @return {HTMLSpanElement} - The dom element icon for video control - */ - createVideoControl: function () { - - const item = document.createElement( 'span' ); - item.style.display = 'none'; - item.show = function () { - - item.style.display = ''; - - }; - - item.hide = function () { - - item.style.display = 'none'; - item.controlButton.paused = true; - item.controlButton.update(); - - }; - - item.controlButton = this.createVideoControlButton(); - item.seekBar = this.createVideoControlSeekbar(); - - item.appendChild( item.controlButton ); - item.appendChild( item.seekBar ); - - item.dispose = function () { - - item.removeChild( item.controlButton ); - item.removeChild( item.seekBar ); - - item.controlButton.dispose(); - item.controlButton = null; - - item.seekBar.dispose(); - item.seekBar = null; - - }; - - this.addEventListener( 'video-control-show', item.show ); - this.addEventListener( 'video-control-hide', item.hide ); - - return item; - - }, - - /** - * Create video control button - * @memberOf Widget - * @instance - * @return {HTMLSpanElement} - The dom element icon for video control - * @fires Widget#panolens-viewer-handler - */ - createVideoControlButton: function () { - - const scope = this; - - function onTap ( event ) { - - event.preventDefault(); - event.stopPropagation(); - - /** - * Viewer handler event - * @type {object} - * @event Widget#panolens-viewer-handler - * @property {string} method - 'toggleVideoPlay' function call on Viewer - */ - scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'toggleVideoPlay', data: !this.paused } ); - - this.paused = !this.paused; - - item.update(); - - } - const item = this.createCustomItem( { - - style: { - - float: 'left', - backgroundImage: 'url("' + DataImage.VideoPlay + '")' - - }, - - onTap: onTap - - } ); - - item.paused = true; - - item.update = function ( paused ) { - - this.paused = paused !== undefined ? paused : this.paused; - - this.style.backgroundImage = 'url("' + ( this.paused - ? DataImage.VideoPlay - : DataImage.VideoPause ) + '")'; - - }; - - return item; - - }, - - /** - * Create video seekbar - * @memberOf Widget - * @instance - * @return {HTMLSpanElement} - The dom element icon for video seekbar - * @fires Widget#panolens-viewer-handler - */ - createVideoControlSeekbar: function () { - - let scope = this, item, progressElement, progressElementControl, - isDragging = false, mouseX, percentageNow, percentageNext; - - progressElement = document.createElement( 'div' ); - progressElement.style.width = '0%'; - progressElement.style.height = '100%'; - progressElement.style.backgroundColor = '#fff'; - - progressElementControl = document.createElement( 'div' ); - progressElementControl.style.float = 'right'; - progressElementControl.style.width = '14px'; - progressElementControl.style.height = '14px'; - progressElementControl.style.transform = 'translate(7px, -5px)'; - progressElementControl.style.borderRadius = '50%'; - progressElementControl.style.backgroundColor = '#ddd'; - - progressElementControl.addEventListener( 'mousedown', onMouseDown, { passive: true } ); - progressElementControl.addEventListener( 'touchstart', onMouseDown, { passive: true } ); - - function onMouseDown ( event ) { - - event.stopPropagation(); - - isDragging = true; - - mouseX = event.clientX || ( event.changedTouches && event.changedTouches[0].clientX ); - - percentageNow = parseInt( progressElement.style.width ) / 100; - - addControlListeners(); - } - - function onVideoControlDrag ( event ) { - - if( isDragging ){ - - const clientX = event.clientX || ( event.changedTouches && event.changedTouches[0].clientX ); - - percentageNext = ( clientX - mouseX ) / item.clientWidth; - - percentageNext = percentageNow + percentageNext; - - percentageNext = percentageNext > 1 ? 1 : ( ( percentageNext < 0 ) ? 0 : percentageNext ); - - item.setProgress ( percentageNext ); - - /** - * Viewer handler event - * @type {object} - * @event Widget#panolens-viewer-handler - * @property {string} method - 'setVideoCurrentTime' function call on Viewer - * @property {number} data - Percentage of current video. Range from 0.0 to 1.0 - */ - scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'setVideoCurrentTime', data: percentageNext } ); - - } - - } - - function onVideoControlStop ( event ) { - - event.stopPropagation(); - - isDragging = false; - - removeControlListeners(); - - } - - function addControlListeners () { - - scope.container.addEventListener( 'mousemove', onVideoControlDrag, { passive: true } ); - scope.container.addEventListener( 'mouseup', onVideoControlStop, { passive: true } ); - scope.container.addEventListener( 'touchmove', onVideoControlDrag, { passive: true } ); - scope.container.addEventListener( 'touchend', onVideoControlStop, { passive: true } ); - - - } - - function removeControlListeners () { - - scope.container.removeEventListener( 'mousemove', onVideoControlDrag, false ); - scope.container.removeEventListener( 'mouseup', onVideoControlStop, false ); - scope.container.removeEventListener( 'touchmove', onVideoControlDrag, false ); - scope.container.removeEventListener( 'touchend', onVideoControlStop, false ); - - } - - function onTap ( event ) { - - event.preventDefault(); - event.stopPropagation(); - - if ( event.target === progressElementControl ) { return; } - - const percentage = ( event.changedTouches && event.changedTouches.length > 0 ) - ? ( event.changedTouches[0].pageX - event.target.getBoundingClientRect().left ) / this.clientWidth - : event.offsetX / this.clientWidth; - - /** - * Viewer handler event - * @type {object} - * @property {string} method - 'setVideoCurrentTime' function call on Viewer - * @property {number} data - Percentage of current video. Range from 0.0 to 1.0 - */ - scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'setVideoCurrentTime', data: percentage } ); - - item.setProgress( event.offsetX / this.clientWidth ); - - } - function onDispose () { - - removeControlListeners(); - progressElement = null; - progressElementControl = null; - - } - - progressElement.appendChild( progressElementControl ); - - item = this.createCustomItem( { - - style: { - - float: 'left', - width: '30%', - height: '4px', - marginTop: '20px', - backgroundColor: 'rgba(188,188,188,0.8)' - - }, - - onTap: onTap, - onDispose: onDispose - - } ); - - item.appendChild( progressElement ); - - item.setProgress = function( percentage ) { - - progressElement.style.width = percentage * 100 + '%'; - - }; - - this.addEventListener( 'video-update', function ( event ) { - - item.setProgress( event.percentage ); - - } ); - - item.progressElement = progressElement; - item.progressElementControl = progressElementControl; - - return item; - - }, - - /** - * Create menu item - * @param {string} title - Title to display - * @memberOf Widget - * @instance - * @return {HTMLElement} - An anchor tag element - */ - createMenuItem: function ( title ) { - - const scope = this; - const item = document.createElement( 'a' ); - item.textContent = title; - item.style.display = 'block'; - item.style.padding = '10px'; - item.style.textDecoration = 'none'; - item.style.cursor = 'pointer'; - item.style.pointerEvents = 'auto'; - item.style.transition = this.DEFAULT_TRANSITION; - - item.slide = function ( right ) { - - this.style.transform = 'translateX(' + ( right ? '' : '-' ) + '100%)'; - - }; - - item.unslide = function () { - - this.style.transform = 'translateX(0)'; - - }; - - item.setIcon = function ( url ) { - - if ( this.icon ) { - - this.icon.style.backgroundImage = 'url(' + url + ')'; - - } - - }; - - item.setSelectionTitle = function ( title ) { - - if ( this.selection ) { - - this.selection.textContent = title; - - } - - }; - - item.addSelection = function ( name ) { - - const selection = document.createElement( 'span' ); - selection.style.fontSize = '13px'; - selection.style.fontWeight = '300'; - selection.style.float = 'right'; - - this.selection = selection; - this.setSelectionTitle( name ); - this.appendChild( selection ); - - return this; - - }; - - item.addIcon = function ( url = DataImage.ChevronRight, left = false, flip = false ) { - - const element = document.createElement( 'span' ); - element.style.float = left ? 'left' : 'right'; - element.style.width = '17px'; - element.style.height = '17px'; - element.style[ 'margin' + ( left ? 'Right' : 'Left' ) ] = '12px'; - element.style.backgroundSize = 'cover'; - - if ( flip ) { - - element.style.transform = 'rotateZ(180deg)'; - - } - - this.icon = element; - this.setIcon( url ); - this.appendChild( element ); - - return this; - - }; - - item.addSubMenu = function ( title, items ) { - - this.subMenu = scope.createSubMenu( title, items ); - - return this; - - }; - - item.addEventListener( 'mouseenter', function () { - - this.style.backgroundColor = '#e0e0e0'; - - }, false ); - - item.addEventListener( 'mouseleave', function () { - - this.style.backgroundColor = '#fafafa'; - - }, false ); - - return item; - - }, - - /** - * Create menu item header - * @param {string} title - Title to display - * @memberOf Widget - * @instance - * @return {HTMLElement} - An anchor tag element - */ - createMenuItemHeader: function ( title ) { - - const header = this.createMenuItem( title ); - - header.style.borderBottom = '1px solid #333'; - header.style.paddingBottom = '15px'; - - return header; - - }, - - /** - * Create main menu - * @param {array} menus - Menu array list - * @memberOf Widget - * @instance - * @return {HTMLElement} - A span element - */ - createMainMenu: function ( menus ) { - - let scope = this, menu = this.createMenu(); - - menu._width = 200; - menu.changeSize( menu._width ); - - function onTap ( event ) { - - event.preventDefault(); - event.stopPropagation(); - - let mainMenu = scope.mainMenu, subMenu = this.subMenu; - - function onNextTick () { - - mainMenu.changeSize( subMenu.clientWidth ); - subMenu.show(); - subMenu.unslideAll(); - - } - - mainMenu.hide(); - mainMenu.slideAll(); - mainMenu.parentElement.appendChild( subMenu ); - - scope.activeMainItem = this; - scope.activeSubMenu = subMenu; - - window.requestAnimationFrame( onNextTick ); - - } - for ( var i = 0; i < menus.length; i++ ) { - - var item = menu.addItem( menus[ i ].title ); - - item.style.paddingLeft = '20px'; - - item.addIcon() - .addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', onTap, false ); - - if ( menus[ i ].subMenu && menus[ i ].subMenu.length > 0 ) { - - var title = menus[ i ].subMenu[ 0 ].title; - - item.addSelection( title ) - .addSubMenu( menus[ i ].title, menus[ i ].subMenu ); - - } - - } - - return menu; - - }, - - /** - * Create sub menu - * @param {string} title - Sub menu title - * @param {array} items - Item array list - * @memberOf Widget - * @instance - * @return {HTMLElement} - A span element - */ - createSubMenu: function ( title, items ) { - - let scope = this, menu, subMenu = this.createMenu(); - - subMenu.items = items; - subMenu.activeItem = null; - - function onTap ( event ) { - - event.preventDefault(); - event.stopPropagation(); - - menu = scope.mainMenu; - menu.changeSize( menu._width ); - menu.unslideAll(); - menu.show(); - subMenu.slideAll( true ); - subMenu.hide(); - - if ( this.type !== 'header' ) { - - subMenu.setActiveItem( this ); - scope.activeMainItem.setSelectionTitle( this.textContent ); - - if ( this.handler ) { this.handler(); } - - } - - } - - subMenu.addHeader( title ).addIcon( undefined, true, true ).addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', onTap, false ); - - for ( let i = 0; i < items.length; i++ ) { - - const item = subMenu.addItem( items[ i ].title ); - - item.style.fontWeight = 300; - item.handler = items[ i ].handler; - item.addIcon( ' ', true ); - item.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', onTap, false ); - - if ( !subMenu.activeItem ) { - - subMenu.setActiveItem( item ); - - } - - } - - subMenu.slideAll( true ); - - return subMenu; - - }, - - /** - * Create general menu - * @memberOf Widget - * @instance - * @return {HTMLElement} - A span element - */ - createMenu: function () { - - const scope = this; - const menu = document.createElement( 'span' ); - const style = menu.style; - - style.padding = '5px 0'; - style.position = 'fixed'; - style.bottom = '100%'; - style.right = '14px'; - style.backgroundColor = '#fafafa'; - style.fontFamily = 'Helvetica Neue'; - style.fontSize = '14px'; - style.visibility = 'hidden'; - style.opacity = 0; - style.boxShadow = '0 0 12pt rgba(0,0,0,0.25)'; - style.borderRadius = '2px'; - style.overflow = 'hidden'; - style.willChange = 'width, height, opacity'; - style.pointerEvents = 'auto'; - style.transition = this.DEFAULT_TRANSITION; - - menu.visible = false; - - menu.changeSize = function ( width, height ) { - - if ( width ) { - - this.style.width = width + 'px'; - - } - - if ( height ) { - - this.style.height = height + 'px'; - - } - - }; - - menu.show = function () { - - this.style.opacity = 1; - this.style.visibility = 'visible'; - this.visible = true; - - }; - - menu.hide = function () { - - this.style.opacity = 0; - this.style.visibility = 'hidden'; - this.visible = false; - - }; - - menu.toggle = function () { - - if ( this.visible ) { - - this.hide(); - - } else { - - this.show(); - - } - - }; - - menu.slideAll = function ( right ) { - - for ( let i = 0; i < menu.children.length; i++ ){ - - if ( menu.children[ i ].slide ) { - - menu.children[ i ].slide( right ); - - } - - } - - }; - - menu.unslideAll = function () { - - for ( let i = 0; i < menu.children.length; i++ ){ - - if ( menu.children[ i ].unslide ) { - - menu.children[ i ].unslide(); - - } - - } - - }; - - menu.addHeader = function ( title ) { - - const header = scope.createMenuItemHeader( title ); - header.type = 'header'; - - this.appendChild( header ); - - return header; - - }; - - menu.addItem = function ( title ) { - - const item = scope.createMenuItem( title ); - item.type = 'item'; - - this.appendChild( item ); - - return item; - - }; - - menu.setActiveItem = function ( item ) { - - if ( this.activeItem ) { - - this.activeItem.setIcon( ' ' ); - - } - - item.setIcon( DataImage.Check ); - - this.activeItem = item; - - }; - - menu.addEventListener( 'mousemove', this.PREVENT_EVENT_HANDLER, true ); - menu.addEventListener( 'mouseup', this.PREVENT_EVENT_HANDLER, true ); - menu.addEventListener( 'mousedown', this.PREVENT_EVENT_HANDLER, true ); - - return menu; - - }, - - /** - * Create custom item element - * @memberOf Widget - * @instance - * @return {HTMLSpanElement} - The dom element icon - */ - createCustomItem: function ( options = {} ) { - - const scope = this; - const item = options.element || document.createElement( 'span' ); - const { onDispose } = options; - - item.style.cursor = 'pointer'; - item.style.float = 'right'; - item.style.width = '44px'; - item.style.height = '100%'; - item.style.backgroundSize = '60%'; - item.style.backgroundRepeat = 'no-repeat'; - item.style.backgroundPosition = 'center'; - item.style.webkitUserSelect = - item.style.MozUserSelect = - item.style.userSelect = 'none'; - item.style.position = 'relative'; - item.style.pointerEvents = 'auto'; - - // White glow on icon - item.addEventListener( scope.TOUCH_ENABLED ? 'touchstart' : 'mouseenter', function() { - item.style.filter = - item.style.webkitFilter = 'drop-shadow(0 0 5px rgba(255,255,255,1))'; - }, { passive: true }); - item.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'mouseleave', function() { - item.style.filter = - item.style.webkitFilter = ''; - }, { passive: true }); - - this.mergeStyleOptions( item, options.style ); - - if ( options.onTap ) { - - item.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', options.onTap, false ); - - } - - item.dispose = function () { - - item.removeEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', options.onTap, false ); - - if ( onDispose ) { options.onDispose(); } - - }; - - return item; - - }, - - /** - * Merge item css style - * @param {HTMLElement} element - The element to be merged with style - * @param {object} options - The style options - * @memberOf Widget - * @instance - * @return {HTMLElement} - The same element with merged styles - */ - mergeStyleOptions: function ( element, options = {} ) { - - for ( let property in options ){ - - if ( options.hasOwnProperty( property ) ) { - - element.style[ property ] = options[ property ]; - - } - - } - - return element; - - }, - - /** - * Dispose widgets by detaching dom elements from container - * @memberOf Widget - * @instance - */ - dispose: function () { - - if ( this.barElement ) { - this.container.removeChild( this.barElement ); - this.barElement.dispose(); - this.barElement = null; - - } - - } - -} ); - -/** - * @classdesc Base Panorama - * @constructor - * @param {THREE.Geometry} geometry - The geometry for this panorama - * @param {THREE.Material} material - The material for this panorama - */ -function Panorama ( geometry, material ) { - - Mesh.call( this, geometry, material ); - - this.type = 'panorama'; - - this.ImageQualityLow = 1; - this.ImageQualityFair = 2; - this.ImageQualityMedium = 3; - this.ImageQualityHigh = 4; - this.ImageQualitySuperHigh = 5; - - this.animationDuration = 1000; - - this.defaultInfospotSize = 350; - - this.container = undefined; - - this.loaded = false; - - this.linkedSpots = []; - - this.isInfospotVisible = false; - - this.linkingImageURL = undefined; - this.linkingImageScale = undefined; - - this.material.side = BackSide; - this.material.opacity = 0; - - this.scale.x *= -1; - this.renderOrder = -1; - - this.active = false; - - this.infospotAnimation = new Tween.Tween( this ).to( {}, this.animationDuration / 2 ); - - this.addEventListener( 'load', this.fadeIn.bind( this ) ); - this.addEventListener( 'panolens-container', this.setContainer.bind( this ) ); - this.addEventListener( 'click', this.onClick.bind( this ) ); - - this.setupTransitions(); - -} - -Panorama.prototype = Object.assign( Object.create( Mesh.prototype ), { - - constructor: Panorama, - - /** - * Adding an object - * To counter the scale.x = -1, it will automatically add an - * empty object with inverted scale on x - * @memberOf Panorama - * @instance - * @param {THREE.Object3D} object - The object to be added - */ - add: function ( object ) { - - let invertedObject; - - if ( arguments.length > 1 ) { - - for ( var i = 0; i < arguments.length; i ++ ) { - - this.add( arguments[ i ] ); - - } - - return this; - - } - - // In case of infospots - if ( object instanceof Infospot ) { - - invertedObject = object; - - if ( object.dispatchEvent ) { - - const { container } = this; - - if ( container ) { object.dispatchEvent( { type: 'panolens-container', container } ); } - - object.dispatchEvent( { type: 'panolens-infospot-focus', method: function ( vector, duration, easing ) { - - /** - * Infospot focus handler event - * @type {object} - * @event Panorama#panolens-viewer-handler - * @property {string} method - Viewer function name - * @property {*} data - The argument to be passed into the method - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'tweenControlCenter', data: [ vector, duration, easing ] } ); - - - }.bind( this ) } ); - } - - } else { - - // Counter scale.x = -1 effect - invertedObject = new Object3D(); - invertedObject.scale.x = -1; - invertedObject.scalePlaceHolder = true; - invertedObject.add( object ); - - } - - Object3D.prototype.add.call( this, invertedObject ); - - }, - - load: function () { - - this.onLoad(); - - }, - - /** - * Click event handler - * @param {object} event - Click event - * @memberOf Panorama - * @instance - * @fires Infospot#dismiss - */ - onClick: function ( event ) { - - if ( event.intersects && event.intersects.length === 0 ) { - - this.traverse( function ( object ) { - - /** - * Dimiss event - * @type {object} - * @event Infospot#dismiss - */ - object.dispatchEvent( { type: 'dismiss' } ); - - } ); - - } - - }, - - /** - * Set container of this panorama - * @param {HTMLElement|object} data - Data with container information - * @memberOf Panorama - * @instance - * @fires Infospot#panolens-container - */ - setContainer: function ( data ) { - - let container; - - if ( data instanceof HTMLElement ) { - - container = data; - - } else if ( data && data.container ) { - - container = data.container; - - } - - if ( container ) { - - this.children.forEach( function ( child ) { - - if ( child instanceof Infospot && child.dispatchEvent ) { - - /** - * Set container event - * @type {object} - * @event Infospot#panolens-container - * @property {HTMLElement} container - The container of this panorama - */ - child.dispatchEvent( { type: 'panolens-container', container: container } ); - - } - - } ); - - this.container = container; - - } - - }, - - /** - * This will be called when panorama is loaded - * @memberOf Panorama - * @instance - * @fires Panorama#load - */ - onLoad: function () { - - this.loaded = true; - - /** - * Load panorama event - * @type {object} - * @event Panorama#load - */ - this.dispatchEvent( { type: 'load' } ); - - }, - - /** - * This will be called when panorama is in progress - * @memberOf Panorama - * @instance - * @fires Panorama#progress - */ - onProgress: function ( progress ) { - - /** - * Loading panorama progress event - * @type {object} - * @event Panorama#progress - * @property {object} progress - The progress object containing loaded and total amount - */ - this.dispatchEvent( { type: 'progress', progress: progress } ); - - }, - - /** - * This will be called when panorama loading has error - * @memberOf Panorama - * @instance - * @fires Panorama#error - */ - onError: function () { - - /** - * Loading panorama error event - * @type {object} - * @event Panorama#error - */ - this.dispatchEvent( { type: 'error' } ); - - }, - - /** - * Get zoom level based on window width - * @memberOf Panorama - * @instance - * @return {number} zoom level indicating image quality - */ - getZoomLevel: function () { - - let zoomLevel; - - if ( window.innerWidth <= 800 ) { - - zoomLevel = this.ImageQualityFair; - - } else if ( window.innerWidth > 800 && window.innerWidth <= 1280 ) { - - zoomLevel = this.ImageQualityMedium; - - } else if ( window.innerWidth > 1280 && window.innerWidth <= 1920 ) { - - zoomLevel = this.ImageQualityHigh; - - } else if ( window.innerWidth > 1920 ) { - - zoomLevel = this.ImageQualitySuperHigh; - - } else { - - zoomLevel = this.ImageQualityLow; - - } - - return zoomLevel; - - }, - - /** - * Update texture of a panorama - * @memberOf Panorama - * @instance - * @param {THREE.Texture} texture - Texture to be updated - */ - updateTexture: function ( texture ) { - - this.material.map = texture; - this.material.needsUpdate = true; - - }, - - /** - * Toggle visibility of infospots in this panorama - * @param {boolean} isVisible - Visibility of infospots - * @param {number} delay - Delay in milliseconds to change visibility - * @memberOf Panorama - * @instance - * @fires Panorama#infospot-animation-complete - */ - toggleInfospotVisibility: function ( isVisible, delay ) { - - delay = ( delay !== undefined ) ? delay : 0; - - const visible = ( isVisible !== undefined ) ? isVisible : ( this.isInfospotVisible ? false : true ); - - this.traverse( function ( object ) { - - if ( object instanceof Infospot ) { - - if ( visible ) { - - object.show( delay ); - - } else { - - object.hide( delay ); - - } - - } - - } ); - - this.isInfospotVisible = visible; - - // Animation complete event - this.infospotAnimation.onComplete( function () { - - /** - * Complete toggling infospot visibility - * @event Panorama#infospot-animation-complete - * @type {object} - */ - this.dispatchEvent( { type: 'infospot-animation-complete', visible: visible } ); - - }.bind( this ) ).delay( delay ).start(); - - }, - - /** - * Set image of this panorama's linking infospot - * @memberOf Panorama - * @instance - * @param {string} url - Url to the image asset - * @param {number} scale - Scale factor of the infospot - */ - setLinkingImage: function ( url, scale ) { - - this.linkingImageURL = url; - this.linkingImageScale = scale; - - }, - - /** - * Link one-way panorama - * @param {Panorama} pano - The panorama to be linked to - * @param {THREE.Vector3} position - The position of infospot which navigates to the pano - * @param {number} [imageScale=300] - Image scale of linked infospot - * @param {string} [imageSrc=DataImage.Arrow] - The image source of linked infospot - * @memberOf Panorama - * @instance - */ - link: function ( pano, position, imageScale, imageSrc ) { - - let scale, img; - - this.visible = true; - - if ( !position ) { - - console.warn( 'Please specify infospot position for linking' ); - - return; - - } - - // Infospot scale - if ( imageScale !== undefined ) { - - scale = imageScale; - - } else if ( pano.linkingImageScale !== undefined ) { - - scale = pano.linkingImageScale; - - } else { - - scale = 300; - - } - - - // Infospot image - if ( imageSrc ) { - - img = imageSrc; - - } else if ( pano.linkingImageURL ) { - - img = pano.linkingImageURL; - - } else { - - img = DataImage.Arrow; - - } - - // Creates a new infospot - const spot = new Infospot( scale, img ); - spot.position.copy( position ); - spot.toPanorama = pano; - spot.addEventListener( 'click', function () { - - /** - * Viewer handler event - * @type {object} - * @event Panorama#panolens-viewer-handler - * @property {string} method - Viewer function name - * @property {*} data - The argument to be passed into the method - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'setPanorama', data: pano } ); - - }.bind( this ) ); - - this.linkedSpots.push( spot ); - - this.add( spot ); - - this.visible = false; - - }, - - reset: function () { - - this.children.length = 0; - - }, - - setupTransitions: function () { - - this.fadeInAnimation = new Tween.Tween( this.material ) - .easing( Tween.Easing.Quartic.Out ) - .onStart( function () { - - this.visible = true; - // this.material.visible = true; - - /** - * Enter panorama fade in start event - * @event Panorama#enter-fade-start - * @type {object} - */ - this.dispatchEvent( { type: 'enter-fade-start' } ); - - }.bind( this ) ); - - this.fadeOutAnimation = new Tween.Tween( this.material ) - .easing( Tween.Easing.Quartic.Out ) - .onComplete( function () { - - this.visible = false; - // this.material.visible = true; - - /** - * Leave panorama complete event - * @event Panorama#leave-complete - * @type {object} - */ - this.dispatchEvent( { type: 'leave-complete' } ); - - }.bind( this ) ); - - this.enterTransition = new Tween.Tween( this ) - .easing( Tween.Easing.Quartic.Out ) - .onComplete( function () { - - /** - * Enter panorama and animation complete event - * @event Panorama#enter-complete - * @type {object} - */ - this.dispatchEvent( { type: 'enter-complete' } ); - - }.bind ( this ) ) - .start(); - - this.leaveTransition = new Tween.Tween( this ) - .easing( Tween.Easing.Quartic.Out ); - - }, - - onFadeAnimationUpdate: function () { - - const alpha = this.material.opacity; - const { uniforms } = this.material; - - if ( uniforms && uniforms.opacity ) { - uniforms.opacity.value = alpha; - } - - }, - - /** - * Start fading in animation - * @memberOf Panorama - * @instance - * @fires Panorama#enter-fade-complete - */ - fadeIn: function ( duration ) { - - duration = duration >= 0 ? duration : this.animationDuration; - - this.fadeOutAnimation.stop(); - this.fadeInAnimation - .to( { opacity: 1 }, duration ) - .onUpdate( this.onFadeAnimationUpdate.bind( this ) ) - .onComplete( function () { - - this.toggleInfospotVisibility( true, duration / 2 ); - - /** - * Enter panorama fade complete event - * @event Panorama#enter-fade-complete - * @type {object} - */ - this.dispatchEvent( { type: 'enter-fade-complete' } ); - - }.bind( this ) ) - .start(); - - }, - - /** - * Start fading out animation - * @memberOf Panorama - * @instance - */ - fadeOut: function ( duration ) { - - duration = duration >= 0 ? duration : this.animationDuration; - - this.fadeInAnimation.stop(); - this.fadeOutAnimation - .to( { opacity: 0 }, duration ) - .onUpdate( this.onFadeAnimationUpdate.bind( this ) ) - .start(); - - }, - - /** - * This will be called when entering a panorama - * @memberOf Panorama - * @instance - * @fires Panorama#enter - * @fires Panorama#enter-start - */ - onEnter: function () { - - const duration = this.animationDuration; - - this.leaveTransition.stop(); - this.enterTransition - .to( {}, duration ) - .onStart( function () { - - /** - * Enter panorama and animation starting event - * @event Panorama#enter-start - * @type {object} - */ - this.dispatchEvent( { type: 'enter-start' } ); - - if ( this.loaded ) { - - this.fadeIn( duration ); - - } else { - - this.load(); - - } - - }.bind( this ) ) - .start(); - - /** - * Enter panorama event - * @event Panorama#enter - * @type {object} - */ - this.dispatchEvent( { type: 'enter' } ); - - this.children.forEach( child => { - - child.dispatchEvent( { type: 'panorama-enter' } ); - - } ); - - this.active = true; - - }, - - /** - * This will be called when leaving a panorama - * @memberOf Panorama - * @instance - * @fires Panorama#leave - */ - onLeave: function () { - - const duration = this.animationDuration; - - this.enterTransition.stop(); - this.leaveTransition - .to( {}, duration ) - .onStart( function () { - - /** - * Leave panorama and animation starting event - * @event Panorama#leave-start - * @type {object} - */ - this.dispatchEvent( { type: 'leave-start' } ); - - this.fadeOut( duration ); - this.toggleInfospotVisibility( false ); - - }.bind( this ) ) - .start(); - - /** - * Leave panorama event - * @event Panorama#leave - * @type {object} - */ - this.dispatchEvent( { type: 'leave' } ); - - this.children.forEach( child => { - - child.dispatchEvent( { type: 'panorama-leave' } ); - - } ); - - this.active = false; - - }, - - /** - * Dispose panorama - * @memberOf Panorama - * @instance - */ - dispose: function () { - - this.infospotAnimation.stop(); - this.fadeInAnimation.stop(); - this.fadeOutAnimation.stop(); - this.enterTransition.stop(); - this.leaveTransition.stop(); - - /** - * On panorama dispose handler - * @type {object} - * @event Panorama#panolens-viewer-handler - * @property {string} method - Viewer function name - * @property {*} data - The argument to be passed into the method - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onPanoramaDispose', data: this } ); - - // recursive disposal on 3d objects - function recursiveDispose ( object ) { - - const { geometry, material } = object; - - for ( var i = object.children.length - 1; i >= 0; i-- ) { - - recursiveDispose( object.children[i] ); - object.remove( object.children[i] ); - - } - - if ( object instanceof Infospot ) { - - object.dispose(); - - } - - if ( geometry ) { geometry.dispose(); object.geometry = null; } - if ( material ) { material.dispose(); object.material = null; } - - } - - recursiveDispose( this ); - - if ( this.parent ) { - - this.parent.remove( this ); - - } - - } - -} ); - -/** - * @classdesc Equirectangular based image panorama - * @constructor - * @param {string} image - Image url or HTMLImageElement - */ -function ImagePanorama ( image, _geometry, _material ) { - - const radius = 5000; - const geometry = _geometry || new SphereBufferGeometry( radius, 60, 40 ); - const material = _material || new MeshBasicMaterial( { opacity: 0, transparent: true } ); - - Panorama.call( this, geometry, material ); - - this.src = image; - this.radius = radius; - -} - -ImagePanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { - - constructor: ImagePanorama, - - /** - * Load image asset - * @param {*} src - Url or image element - * @memberOf ImagePanorama - * @instance - */ - load: function ( src ) { - - src = src || this.src; - - if ( !src ) { - - console.warn( 'Image source undefined' ); - - return; - - } else if ( typeof src === 'string' ) { - - TextureLoader.load( src, this.onLoad.bind( this ), this.onProgress.bind( this ), this.onError.bind( this ) ); - - } else if ( src instanceof HTMLImageElement ) { - - this.onLoad( new Texture( src ) ); - - } - - }, - - /** - * This will be called when image is loaded - * @param {THREE.Texture} texture - Texture to be updated - * @memberOf ImagePanorama - * @instance - */ - onLoad: function ( texture ) { - - texture.minFilter = texture.magFilter = LinearFilter; - texture.needsUpdate = true; - - this.updateTexture( texture ); - - window.requestAnimationFrame( Panorama.prototype.onLoad.bind( this ) ); - - }, - - /** - * Reset - * @memberOf ImagePanorama - * @instance - */ - reset: function () { - - Panorama.prototype.reset.call( this ); - - }, - - /** - * Dispose - * @memberOf ImagePanorama - * @instance - */ - dispose: function () { - - const { material: { map } } = this; - - // Release cached image - Cache.remove( this.src ); - - if ( map ) { map.dispose(); } - - Panorama.prototype.dispose.call( this ); - - } - -} ); - -/** - * @classdesc Empty panorama - * @constructor - */ -function EmptyPanorama () { - - const geometry = new BufferGeometry(); - const material = new MeshBasicMaterial( { color: 0x000000, opacity: 0, transparent: true } ); - - geometry.addAttribute( 'position', new BufferAttribute( new Float32Array(), 1 ) ); - - Panorama.call( this, geometry, material ); - -} - -EmptyPanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { - - constructor: EmptyPanorama - -} ); - -/** - * @classdesc Cubemap-based panorama - * @constructor - * @param {array} images - Array of 6 urls to images, one for each side of the CubeTexture. The urls should be specified in the following order: pos-x, neg-x, pos-y, neg-y, pos-z, neg-z - */ -function CubePanorama ( images = [] ){ - - const edgeLength = 10000; - const shader = Object.assign( {}, ShaderLib[ 'cube' ] ); - const geometry = new BoxBufferGeometry( edgeLength, edgeLength, edgeLength ); - const material = new ShaderMaterial( { - - fragmentShader: shader.fragmentShader, - vertexShader: shader.vertexShader, - uniforms: shader.uniforms, - side: BackSide, - transparent: true - - } ); - - Panorama.call( this, geometry, material ); - - this.images = images; - this.edgeLength = edgeLength; - this.material.uniforms.opacity.value = 0; - -} - -CubePanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { - - constructor: CubePanorama, - - /** - * Load 6 images and bind listeners - * @memberOf CubePanorama - * @instance - */ - load: function () { - - CubeTextureLoader.load( - - this.images, - - this.onLoad.bind( this ), - this.onProgress.bind( this ), - this.onError.bind( this ) - - ); - - }, - - /** - * This will be called when 6 textures are ready - * @param {THREE.CubeTexture} texture - Cube texture - * @memberOf CubePanorama - * @instance - */ - onLoad: function ( texture ) { - - this.material.uniforms[ 'tCube' ].value = texture; - - Panorama.prototype.onLoad.call( this ); - - }, - - /** - * Dispose - * @memberOf CubePanorama - * @instance - */ - dispose: function () { - - const { value } = this.material.uniforms.tCube; - - this.images.forEach( ( image ) => { Cache.remove( image ); } ); - - if ( value instanceof CubeTexture ) { - - value.dispose(); - - } - - Panorama.prototype.dispose.call( this ); - - } - -} ); - -/** - * @classdesc Basic panorama with 6 pre-defined grid images - * @constructor - */ -function BasicPanorama () { - - const images = []; - - for ( let i = 0; i < 6; i++ ) { - - images.push( DataImage.WhiteTile ); - - } - - CubePanorama.call( this, images ); - -} - -BasicPanorama.prototype = Object.assign( Object.create( CubePanorama.prototype ), { - - constructor: BasicPanorama - -} ); - -/** - * @classdesc Video Panorama - * @constructor - * @param {string} src - Equirectangular video url - * @param {object} [options] - Option for video settings - * @param {HTMLElement} [options.videoElement] - HTML5 video element contains the video - * @param {boolean} [options.loop=true] - Specify if the video should loop in the end - * @param {boolean} [options.muted=true] - Mute the video or not. Need to be true in order to autoplay on some browsers - * @param {boolean} [options.autoplay=false] - Specify if the video should auto play - * @param {boolean} [options.playsinline=true] - Specify if video should play inline for iOS. If you want it to auto play inline, set both autoplay and muted options to true - * @param {string} [options.crossOrigin="anonymous"] - Sets the cross-origin attribute for the video, which allows for cross-origin videos in some browsers (Firefox, Chrome). Set to either "anonymous" or "use-credentials". - * @param {number} [radius=5000] - The minimum radius for this panoram - */ -function VideoPanorama ( src, options = {} ) { - - const radius = 5000; - const geometry = new SphereBufferGeometry( radius, 60, 40 ); - const material = new MeshBasicMaterial( { opacity: 0, transparent: true } ); - - Panorama.call( this, geometry, material ); - - this.src = src; - - this.options = { - - videoElement: document.createElement( 'video' ), - loop: true, - muted: true, - autoplay: false, - playsinline: true, - crossOrigin: 'anonymous' - - }; - - Object.assign( this.options, options ); - - this.videoElement = this.options.videoElement; - this.videoProgress = 0; - this.radius = radius; - - this.addEventListener( 'leave', this.pauseVideo.bind( this ) ); - this.addEventListener( 'enter-fade-start', this.resumeVideoProgress.bind( this ) ); - this.addEventListener( 'video-toggle', this.toggleVideo.bind( this ) ); - this.addEventListener( 'video-time', this.setVideoCurrentTime.bind( this ) ); - -} -VideoPanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { - - constructor: VideoPanorama, - - isMobile: function () { - - let check = false; - (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})( window.navigator.userAgent || window.navigator.vendor || window.opera ); - return check; - - }, - - /** - * Load video panorama - * @memberOf VideoPanorama - * @instance - * @fires Panorama#panolens-viewer-handler - */ - load: function () { - - const { muted, loop, autoplay, playsinline, crossOrigin } = this.options; - const video = this.videoElement; - const material = this.material; - const onProgress = this.onProgress.bind( this ); - const onLoad = this.onLoad.bind( this ); - - video.loop = loop; - video.autoplay = autoplay; - video.playsinline = playsinline; - video.crossOrigin = crossOrigin; - video.muted = muted; - - if ( playsinline ) { - - video.setAttribute( 'playsinline', '' ); - video.setAttribute( 'webkit-playsinline', '' ); - - } - - const onloadeddata = function() { - - this.setVideoTexture( video ); - - if ( autoplay ) { - - /** - * Viewer handler event - * @type {object} - * @property {string} method - 'updateVideoPlayButton' - * @property {boolean} data - Pause video or not - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: false } ); - - } - - // For mobile silent autoplay - if ( this.isMobile() ) { - - video.pause(); - - if ( autoplay && muted ) { - - /** - * Viewer handler event - * @type {object} - * @property {string} method - 'updateVideoPlayButton' - * @property {boolean} data - Pause video or not - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: false } ); - - } else { - - /** - * Viewer handler event - * @type {object} - * @property {string} method - 'updateVideoPlayButton' - * @property {boolean} data - Pause video or not - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: true } ); - - } - - } - - const loaded = () => { - - // Fix for threejs r89 delayed update - material.map.needsUpdate = true; - - onProgress( { loaded: 1, total: 1 } ); - onLoad(); - - }; - - window.requestAnimationFrame( loaded ); - - }; - - /** - * Ready state of the audio/video element - * 0 = HAVE_NOTHING - no information whether or not the audio/video is ready - * 1 = HAVE_METADATA - metadata for the audio/video is ready - * 2 = HAVE_CURRENT_DATA - data for the current playback position is available, but not enough data to play next frame/millisecond - * 3 = HAVE_FUTURE_DATA - data for the current and at least the next frame is available - * 4 = HAVE_ENOUGH_DATA - enough data available to start playing - */ - if ( video.readyState > 2 ) { - - onloadeddata.call( this ); - - } else { - - if ( video.querySelectorAll( 'source' ).length === 0 ) { - - const source = document.createElement( 'source' ); - source.src = this.src; - video.appendChild( source ); - - } - - video.load(); - } - - video.addEventListener( 'loadeddata', onloadeddata.bind( this ) ); - - video.addEventListener( 'timeupdate', function () { - - this.videoProgress = video.duration >= 0 ? video.currentTime / video.duration : 0; - - /** - * Viewer handler event - * @type {object} - * @property {string} method - 'onVideoUpdate' - * @property {number} data - The percentage of video progress. Range from 0.0 to 1.0 - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onVideoUpdate', data: this.videoProgress } ); - - }.bind( this ) ); - - video.addEventListener( 'ended', function () { - - if ( !loop ) { - - this.resetVideo(); - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: true } ); - - } - - }.bind( this ), false ); - - }, - - /** - * Set video texture - * @memberOf VideoPanorama - * @instance - * @param {HTMLVideoElement} video - The html5 video element - * @fires Panorama#panolens-viewer-handler - */ - setVideoTexture: function ( video ) { - - if ( !video ) return; - - const videoTexture = new VideoTexture( video ); - videoTexture.minFilter = LinearFilter; - videoTexture.magFilter = LinearFilter; - videoTexture.format = RGBFormat; - - this.updateTexture( videoTexture ); - - }, - - /** - * Reset - * @memberOf VideoPanorama - * @instance - */ - reset: function () { - - this.videoElement = undefined; - - Panorama.prototype.reset.call( this ); - - }, - - /** - * Check if video is paused - * @memberOf VideoPanorama - * @instance - * @return {boolean} - is video paused or not - */ - isVideoPaused: function () { - - return this.videoElement.paused; - - }, - - /** - * Toggle video to play or pause - * @memberOf VideoPanorama - * @instance - */ - toggleVideo: function () { - - const video = this.videoElement; - - if ( !video ) { return; } - - video[ video.paused ? 'play' : 'pause' ](); - - }, - - /** - * Set video currentTime - * @memberOf VideoPanorama - * @instance - * @param {object} event - Event contains percentage. Range from 0.0 to 1.0 - */ - setVideoCurrentTime: function ( { percentage } ) { - - const video = this.videoElement; - - if ( video && !Number.isNaN( percentage ) && percentage !== 1 ) { - - video.currentTime = video.duration * percentage; - - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onVideoUpdate', data: percentage } ); - - } - - }, - - /** - * Play video - * @memberOf VideoPanorama - * @instance - * @fires VideoPanorama#play - * @fires VideoPanorama#play-error - */ - playVideo: function () { - - const video = this.videoElement; - const playVideo = this.playVideo.bind( this ); - const dispatchEvent = this.dispatchEvent.bind( this ); - const onSuccess = () => { - - /** - * Play event - * @type {object} - * @event VideoPanorama#play - * - */ - dispatchEvent( { type: 'play' } ); - - }; - const onError = ( error ) => { - - // Error playing video. Retry next frame. Possibly Waiting for user interaction - window.requestAnimationFrame( playVideo ); - - /** - * Play event - * @type {object} - * @event VideoPanorama#play-error - * - */ - dispatchEvent( { type: 'play-error', error } ); - - }; - - if ( video && video.paused ) { - - video.play().then( onSuccess ).catch( onError ); - - } - - }, - - /** - * Pause video - * @memberOf VideoPanorama - * @instance - * @fires VideoPanorama#pause - */ - pauseVideo: function () { - - const video = this.videoElement; - - if ( video && !video.paused ) { - - video.pause(); - - } - - /** - * Pause event - * @type {object} - * @event VideoPanorama#pause - * - */ - this.dispatchEvent( { type: 'pause' } ); - - }, - - /** - * Resume video - * @memberOf VideoPanorama - * @instance - */ - resumeVideoProgress: function () { - - const video = this.videoElement; - - if ( video.readyState >= 4 && video.autoplay && !this.isMobile() ) { - - this.playVideo(); - - /** - * Viewer handler event - * @type {object} - * @property {string} method - 'updateVideoPlayButton' - * @property {boolean} data - Pause video or not - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: false } ); - - } else { - - this.pauseVideo(); - - /** - * Viewer handler event - * @type {object} - * @property {string} method - 'updateVideoPlayButton' - * @property {boolean} data - Pause video or not - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: true } ); - - } - - this.setVideoCurrentTime( { percentage: this.videoProgress } ); - - }, - - /** - * Reset video at stating point - * @memberOf VideoPanorama - * @instance - */ - resetVideo: function () { - - const video = this.videoElement; - - if ( video ) { - - this.setVideoCurrentTime( { percentage: 0 } ); - - } - - }, - - /** - * Check if video is muted - * @memberOf VideoPanorama - * @instance - * @return {boolean} - is video muted or not - */ - isVideoMuted: function () { - - return this.videoElement.muted; - - }, - - /** - * Mute video - * @memberOf VideoPanorama - * @instance - */ - muteVideo: function () { - - const video = this.videoElement; - - if ( video && !video.muted ) { - - video.muted = true; - - } - - this.dispatchEvent( { type: 'volumechange' } ); - - }, - - /** - * Unmute video - * @memberOf VideoPanorama - * @instance - */ - unmuteVideo: function () { - - const video = this.videoElement; - - if ( video && this.isVideoMuted() ) { - - video.muted = false; - - } - - this.dispatchEvent( { type: 'volumechange' } ); - - }, - - /** - * Returns the video element - * @memberOf VideoPanorama - * @instance - * @returns {HTMLElement} - */ - getVideoElement: function () { - - return this.videoElement; - - }, - - /** - * Dispose video panorama - * @memberOf VideoPanorama - * @instance - */ - dispose: function () { - - const { material: { map } } = this; - - this.pauseVideo(); - - this.removeEventListener( 'leave', this.pauseVideo.bind( this ) ); - this.removeEventListener( 'enter-fade-start', this.resumeVideoProgress.bind( this ) ); - this.removeEventListener( 'video-toggle', this.toggleVideo.bind( this ) ); - this.removeEventListener( 'video-time', this.setVideoCurrentTime.bind( this ) ); - - if ( map ) { map.dispose(); } - - Panorama.prototype.dispose.call( this ); - - } - -} ); - -/** - * @classdesc Google Street View Loader - * @constructor - * @param {object} parameters - */ -function GoogleStreetviewLoader ( parameters = {} ) { - - this._parameters = parameters; - this._zoom = null; - this._panoId = null; - this._panoClient = new google.maps.StreetViewService(); - this._count = 0; - this._total = 0; - this._canvas = []; - this._ctx = []; - this._wc = 0; - this._hc = 0; - this.result = null; - this.rotation = 0; - this.copyright = ''; - this.onSizeChange = null; - this.onPanoramaLoad = null; - - this.levelsW = [ 1, 2, 4, 7, 13, 26 ]; - this.levelsH = [ 1, 1, 2, 4, 7, 13 ]; - - this.widths = [ 416, 832, 1664, 3328, 6656, 13312 ]; - this.heights = [ 416, 416, 832, 1664, 3328, 6656 ]; - - this.maxW = 6656; - this.maxH = 6656; - - let gl; - - try { - - const canvas = document.createElement( 'canvas' ); - - gl = canvas.getContext( 'experimental-webgl' ); - - if( !gl ) { - - gl = canvas.getContext( 'webgl' ); - - } - - } - catch ( error ) { - - } - - this.maxW = Math.max( gl.getParameter( gl.MAX_TEXTURE_SIZE ), this.maxW ); - this.maxH = Math.max( gl.getParameter( gl.MAX_TEXTURE_SIZE ), this.maxH ); - -} - -Object.assign( GoogleStreetviewLoader.prototype, { - - constructor: GoogleStreetviewLoader, - - /** - * Set progress - * @param {number} loaded - * @param {number} total - * @memberOf GoogleStreetviewLoader - * @instance - */ - setProgress: function ( loaded, total ) { - - if ( this.onProgress ) { - - this.onProgress( { loaded: loaded, total: total } ); - - } - - }, - - /** - * Adapt texture to zoom - * @memberOf GoogleStreetviewLoader - * @instance - */ - adaptTextureToZoom: function () { - - const w = this.widths [ this._zoom ]; - const h = this.heights[ this._zoom ]; - - const maxW = this.maxW; - const maxH = this.maxH; - - this._wc = Math.ceil( w / maxW ); - this._hc = Math.ceil( h / maxH ); - - for( let y = 0; y < this._hc; y++ ) { - for( let x = 0; x < this._wc; x++ ) { - const c = document.createElement( 'canvas' ); - if( x < ( this._wc - 1 ) ) c.width = maxW; else c.width = w - ( maxW * x ); - if( y < ( this._hc - 1 ) ) c.height = maxH; else c.height = h - ( maxH * y ); - this._canvas.push( c ); - this._ctx.push( c.getContext( '2d' ) ); - } - } - - }, - - /** - * Compose from tile - * @param {number} x - * @param {number} y - * @param {*} texture - * @memberOf GoogleStreetviewLoader - * @instance - */ - composeFromTile: function ( x, y, texture ) { - - const maxW = this.maxW; - const maxH = this.maxH; - - x *= 512; - y *= 512; - - const px = Math.floor( x / maxW ); - const py = Math.floor( y / maxH ); - - x -= px * maxW; - y -= py * maxH; - - this._ctx[ py * this._wc + px ].drawImage( texture, 0, 0, texture.width, texture.height, x, y, 512, 512 ); - - this.progress(); - - }, - - /** - * Progress - * @memberOf GoogleStreetviewLoader - * @instance - */ - progress: function() { - - this._count++; - - this.setProgress( this._count, this._total ); - - if ( this._count === this._total) { - - this.canvas = this._canvas; - this.panoId = this._panoId; - this.zoom = this._zoom; - - if ( this.onPanoramaLoad ) { - - this.onPanoramaLoad( this._canvas[ 0 ] ); - - } - - } - }, - - /** - * Compose panorama - * @memberOf GoogleStreetviewLoader - * @instance - */ - composePanorama: function () { - - this.setProgress( 0, 1 ); - - const w = this.levelsW[ this._zoom ]; - const h = this.levelsH[ this._zoom ]; - const self = this; - - this._count = 0; - this._total = w * h; - - const { useWebGL } = this._parameters; - - for( let y = 0; y < h; y++ ) { - for( let x = 0; x < w; x++ ) { - const url = 'https://geo0.ggpht.com/cbk?cb_client=maps_sv.tactile&authuser=0&hl=en&output=tile&zoom=' + this._zoom + '&x=' + x + '&y=' + y + '&panoid=' + this._panoId + '&nbt&fover=2'; - ( function( x, y ) { - if( useWebGL ) { - const texture = TextureLoader.load( url, null, function() { - self.composeFromTile( x, y, texture ); - } ); - } else { - const img = new Image(); - img.addEventListener( 'load', function() { - self.composeFromTile( x, y, this ); - } ); - img.crossOrigin = ''; - img.src = url; - } - } )( x, y ); - } - } - - }, - - /** - * Load - * @param {string} panoid - * @memberOf GoogleStreetviewLoader - * @instance - */ - load: function ( panoid ) { - - this.loadPano( panoid ); - - }, - - /** - * Load panorama - * @param {string} id - * @memberOf GoogleStreetviewLoader - * @instance - */ - loadPano: function( id ) { - - const self = this; - this._panoClient.getPanoramaById( id, function (result, status) { - if (status === google.maps.StreetViewStatus.OK) { - self.result = result; - self.copyright = result.copyright; - self._panoId = result.location.pano; - self.composePanorama(); - } - }); - - }, - - /** - * Set zoom level - * @param {number} z - * @memberOf GoogleStreetviewLoader - * @instance - */ - setZoom: function( z ) { - - this._zoom = z; - this.adaptTextureToZoom(); - } - -} ); - -/** - * @classdesc Google streetview panorama - * @description [How to get Panorama ID]{@link http://stackoverflow.com/questions/29916149/google-maps-streetview-how-to-get-panorama-id} - * @constructor - * @param {string} panoId - Panorama id from Google Streetview - * @param {string} [apiKey] - Google Street View API Key - */ -function GoogleStreetviewPanorama ( panoId, apiKey ) { - - ImagePanorama.call( this ); - - this.panoId = panoId; - - this.gsvLoader = null; - - this.loadRequested = false; - - this.setupGoogleMapAPI( apiKey ); - -} - -GoogleStreetviewPanorama.prototype = Object.assign( Object.create( ImagePanorama.prototype ), { - - constructor: GoogleStreetviewPanorama, - - /** - * Load Google Street View by panorama id - * @param {string} panoId - Gogogle Street View panorama id - * @memberOf GoogleStreetviewPanorama - * @instance - */ - load: function ( panoId ) { - - this.loadRequested = true; - - panoId = ( panoId || this.panoId ) || {}; - - if ( panoId && this.gsvLoader ) { - - this.loadGSVLoader( panoId ); - - } - - }, - - /** - * Setup Google Map API - * @param {string} apiKey - * @memberOf GoogleStreetviewPanorama - * @instance - */ - setupGoogleMapAPI: function ( apiKey ) { - - const script = document.createElement( 'script' ); - script.src = 'https://maps.googleapis.com/maps/api/js?'; - script.src += apiKey ? 'key=' + apiKey : ''; - script.onreadystatechange = this.setGSVLoader.bind( this ); - script.onload = this.setGSVLoader.bind( this ); - - document.querySelector( 'head' ).appendChild( script ); - - }, - - /** - * Set GSV Loader - * @memberOf GoogleStreetviewPanorama - * @instance - */ - setGSVLoader: function () { - - this.gsvLoader = new GoogleStreetviewLoader(); - - if ( this.loadRequested ) { - - this.load(); - - } - - }, - - /** - * Get GSV Loader - * @memberOf GoogleStreetviewPanorama - * @instance - * @return {GoogleStreetviewLoader} GSV Loader instance - */ - getGSVLoader: function () { - - return this.gsvLoader; - - }, - - /** - * Load GSV Loader - * @param {string} panoId - Gogogle Street View panorama id - * @memberOf GoogleStreetviewPanorama - * @instance - */ - loadGSVLoader: function ( panoId ) { - - this.loadRequested = false; - - this.gsvLoader.onProgress = this.onProgress.bind( this ); - - this.gsvLoader.onPanoramaLoad = this.onLoad.bind( this ); - - this.gsvLoader.setZoom( this.getZoomLevel() ); - - this.gsvLoader.load( panoId ); - - this.gsvLoader.loaded = true; - }, - - /** - * This will be called when panorama is loaded - * @param {HTMLCanvasElement} canvas - Canvas where the tiles have been drawn - * @memberOf GoogleStreetviewPanorama - * @instance - */ - onLoad: function ( canvas ) { - - ImagePanorama.prototype.onLoad.call( this, new Texture( canvas ) ); - - }, - - /** - * Reset - * @memberOf GoogleStreetviewPanorama - * @instance - */ - reset: function () { - - this.gsvLoader = undefined; - - ImagePanorama.prototype.reset.call( this ); - - } - -} ); - -/** - * Stereographic projection shader - * based on http://notlion.github.io/streetview-stereographic - * @author pchen66 - */ - -/** - * @description Stereograhpic Shader - * @module StereographicShader - * @property {object} uniforms - * @property {THREE.Texture} uniforms.tDiffuse diffuse map - * @property {number} uniforms.resolution image resolution - * @property {THREE.Matrix4} uniforms.transform transformation matrix - * @property {number} uniforms.zoom image zoom factor - * @property {number} uniforms.opacity image opacity - * @property {string} vertexShader vertex shader - * @property {string} fragmentShader fragment shader - */ -const StereographicShader = { - - uniforms: { - - 'tDiffuse': { value: new Texture() }, - 'resolution': { value: 1.0 }, - 'transform': { value: new Matrix4() }, - 'zoom': { value: 1.0 }, - 'opacity': { value: 1.0 } - - }, - - vertexShader: [ - - 'varying vec2 vUv;', - - 'void main() {', - - 'vUv = uv;', - 'gl_Position = vec4( position, 1.0 );', - - '}' - - ].join( '\n' ), - - fragmentShader: [ - - 'uniform sampler2D tDiffuse;', - 'uniform float resolution;', - 'uniform mat4 transform;', - 'uniform float zoom;', - 'uniform float opacity;', - - 'varying vec2 vUv;', - - 'const float PI = 3.141592653589793;', - - 'void main(){', - - 'vec2 position = -1.0 + 2.0 * vUv;', - - 'position *= vec2( zoom * resolution, zoom * 0.5 );', - - 'float x2y2 = position.x * position.x + position.y * position.y;', - 'vec3 sphere_pnt = vec3( 2. * position, x2y2 - 1. ) / ( x2y2 + 1. );', - - 'sphere_pnt = vec3( transform * vec4( sphere_pnt, 1.0 ) );', - - 'vec2 sampleUV = vec2(', - '(atan(sphere_pnt.y, sphere_pnt.x) / PI + 1.0) * 0.5,', - '(asin(sphere_pnt.z) / PI + 0.5)', - ');', - - 'gl_FragColor = texture2D( tDiffuse, sampleUV );', - - 'gl_FragColor.a *= opacity;', - - '}' - - ].join( '\n' ) - -}; - -/** - * @classdesc Little Planet - * @constructor - * @param {string} type - Type of little planet basic class - * @param {string} source - URL for the image source - * @param {number} [size=10000] - Size of plane geometry - * @param {number} [ratio=0.5] - Ratio of plane geometry's height against width - */ -function LittlePlanet ( type = 'image', source, size = 10000, ratio = 0.5 ) { - - if ( type === 'image' ) { - - ImagePanorama.call( this, source, this.createGeometry( size, ratio ), this.createMaterial( size ) ); - - } - - this.size = size; - this.ratio = ratio; - this.EPS = 0.000001; - this.frameId = null; - - this.dragging = false; - this.userMouse = new Vector2(); - - this.quatA = new Quaternion(); - this.quatB = new Quaternion(); - this.quatCur = new Quaternion(); - this.quatSlerp = new Quaternion(); - - this.vectorX = new Vector3( 1, 0, 0 ); - this.vectorY = new Vector3( 0, 1, 0 ); - - this.addEventListener( 'window-resize', this.onWindowResize ); - -} - -LittlePlanet.prototype = Object.assign( Object.create( ImagePanorama.prototype ), { - - constructor: LittlePlanet, - - add: function ( object ) { - - if ( arguments.length > 1 ) { - - for ( let i = 0; i < arguments.length; i ++ ) { - - this.add( arguments[ i ] ); - - } - - return this; - - } - - if ( object instanceof Infospot ) { - - object.material.depthTest = false; - - } - - ImagePanorama.prototype.add.call( this, object ); - - }, - - createGeometry: function ( size, ratio ) { - - return new PlaneBufferGeometry( size, size * ratio ); - - }, - - createMaterial: function ( size ) { - - const shader = Object.assign( {}, StereographicShader ), uniforms = shader.uniforms; - - uniforms.zoom.value = size; - uniforms.opacity.value = 0.0; - - return new ShaderMaterial( { - - uniforms: uniforms, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader, - side: BackSide, - transparent: true - - } ); - - }, - - registerMouseEvents: function () { - - this.container.addEventListener( 'mousedown', this.onMouseDown.bind( this ), { passive: true } ); - this.container.addEventListener( 'mousemove', this.onMouseMove.bind( this ), { passive: true } ); - this.container.addEventListener( 'mouseup', this.onMouseUp.bind( this ), { passive: true } ); - this.container.addEventListener( 'touchstart', this.onMouseDown.bind( this ), { passive: true } ); - this.container.addEventListener( 'touchmove', this.onMouseMove.bind( this ), { passive: true } ); - this.container.addEventListener( 'touchend', this.onMouseUp.bind( this ), { passive: true } ); - this.container.addEventListener( 'mousewheel', this.onMouseWheel.bind( this ), { passive: false } ); - this.container.addEventListener( 'DOMMouseScroll', this.onMouseWheel.bind( this ), { passive: false } ); - this.container.addEventListener( 'contextmenu', this.onContextMenu.bind( this ), { passive: true } ); - - }, - - unregisterMouseEvents: function () { - - this.container.removeEventListener( 'mousedown', this.onMouseDown.bind( this ), false ); - this.container.removeEventListener( 'mousemove', this.onMouseMove.bind( this ), false ); - this.container.removeEventListener( 'mouseup', this.onMouseUp.bind( this ), false ); - this.container.removeEventListener( 'touchstart', this.onMouseDown.bind( this ), false ); - this.container.removeEventListener( 'touchmove', this.onMouseMove.bind( this ), false ); - this.container.removeEventListener( 'touchend', this.onMouseUp.bind( this ), false ); - this.container.removeEventListener( 'mousewheel', this.onMouseWheel.bind( this ), false ); - this.container.removeEventListener( 'DOMMouseScroll', this.onMouseWheel.bind( this ), false ); - this.container.removeEventListener( 'contextmenu', this.onContextMenu.bind( this ), false ); - - }, - - onMouseDown: function ( event ) { - - const inputCount = ( event.touches && event.touches.length ) || 1 ; - - switch ( inputCount ) { - - case 1: - - const x = ( event.clientX >= 0 ) ? event.clientX : event.touches[ 0 ].clientX; - const y = ( event.clientY >= 0 ) ? event.clientY : event.touches[ 0 ].clientY; - - this.dragging = true; - this.userMouse.set( x, y ); - - break; - - case 2: - - const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; - const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; - const distance = Math.sqrt( dx * dx + dy * dy ); - this.userMouse.pinchDistance = distance; - - break; - - default: - - break; - - } - - this.onUpdateCallback(); - - }, - - onMouseMove: function ( event ) { - - const inputCount = ( event.touches && event.touches.length ) || 1 ; - - switch ( inputCount ) { - - case 1: - - const x = ( event.clientX >= 0 ) ? event.clientX : event.touches[ 0 ].clientX; - const y = ( event.clientY >= 0 ) ? event.clientY : event.touches[ 0 ].clientY; - - const angleX = Math$1.degToRad( x - this.userMouse.x ) * 0.4; - const angleY = Math$1.degToRad( y - this.userMouse.y ) * 0.4; - - if ( this.dragging ) { - this.quatA.setFromAxisAngle( this.vectorY, angleX ); - this.quatB.setFromAxisAngle( this.vectorX, angleY ); - this.quatCur.multiply( this.quatA ).multiply( this.quatB ); - this.userMouse.set( x, y ); - } - - break; - - case 2: - - const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; - const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; - const distance = Math.sqrt( dx * dx + dy * dy ); - - this.addZoomDelta( this.userMouse.pinchDistance - distance ); - - break; - - default: - - break; - - } - - }, - - onMouseUp: function () { - - this.dragging = false; - - }, - - onMouseWheel: function ( event ) { - - event.preventDefault(); - event.stopPropagation(); - - let delta = 0; - - if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 - - delta = event.wheelDelta; - - } else if ( event.detail !== undefined ) { // Firefox - - delta = - event.detail; - - } - - this.addZoomDelta( delta ); - this.onUpdateCallback(); - - }, - - addZoomDelta: function ( delta ) { - - const uniforms = this.material.uniforms; - const lowerBound = this.size * 0.1; - const upperBound = this.size * 10; - - uniforms.zoom.value += delta; - - if ( uniforms.zoom.value <= lowerBound ) { - - uniforms.zoom.value = lowerBound; - - } else if ( uniforms.zoom.value >= upperBound ) { - - uniforms.zoom.value = upperBound; - - } - - }, - - onUpdateCallback: function () { - - this.frameId = window.requestAnimationFrame( this.onUpdateCallback.bind( this ) ); - - this.quatSlerp.slerp( this.quatCur, 0.1 ); - - if ( this.material ) { - - this.material.uniforms.transform.value.makeRotationFromQuaternion( this.quatSlerp ); - - } - - if ( !this.dragging && 1.0 - this.quatSlerp.clone().dot( this.quatCur ) < this.EPS ) { - - window.cancelAnimationFrame( this.frameId ); - - } - - }, - - reset: function () { - - this.quatCur.set( 0, 0, 0, 1 ); - this.quatSlerp.set( 0, 0, 0, 1 ); - this.onUpdateCallback(); - - }, - - onLoad: function ( texture ) { - - this.material.uniforms.resolution.value = this.container.clientWidth / this.container.clientHeight; - - this.registerMouseEvents(); - this.onUpdateCallback(); - - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'disableControl' } ); - - ImagePanorama.prototype.onLoad.call( this, texture ); - - }, - - onLeave: function () { - - this.unregisterMouseEvents(); - - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'enableControl', data: CONTROLS.ORBIT } ); - - window.cancelAnimationFrame( this.frameId ); - - ImagePanorama.prototype.onLeave.call( this ); - - }, - - onWindowResize: function () { - - this.material.uniforms.resolution.value = this.container.clientWidth / this.container.clientHeight; - - }, - - onContextMenu: function () { - - this.dragging = false; - - }, - - dispose: function () { - - this.unregisterMouseEvents(); - - ImagePanorama.prototype.dispose.call( this ); - - } - -}); - -/** - * @classdesc Image Little Planet - * @constructor - * @param {string} source - URL for the image source - * @param {number} [size=10000] - Size of plane geometry - * @param {number} [ratio=0.5] - Ratio of plane geometry's height against width - */ -function ImageLittlePlanet ( source, size, ratio ) { - - LittlePlanet.call( this, 'image', source, size, ratio ); - -} - -ImageLittlePlanet.prototype = Object.assign( Object.create( LittlePlanet.prototype ), { - - constructor: ImageLittlePlanet, - - /** - * On loaded with texture - * @param {THREE.Texture} texture - * @memberOf ImageLittlePlanet - * @instance - */ - onLoad: function ( texture ) { - - this.updateTexture( texture ); - - LittlePlanet.prototype.onLoad.call( this, texture ); - - }, - - /** - * Update texture - * @param {THREE.Texture} texture - * @memberOf ImageLittlePlanet - * @instance - */ - updateTexture: function ( texture ) { - - texture.minFilter = texture.magFilter = LinearFilter; - - this.material.uniforms[ 'tDiffuse' ].value = texture; - - }, - - /** - * Dispose - * @memberOf ImageLittlePlanet - * @instance - */ - dispose: function () { - - const tDiffuse = this.material.uniforms[ 'tDiffuse' ]; - - if ( tDiffuse && tDiffuse.value ) { - - tDiffuse.value.dispose(); - - } - - LittlePlanet.prototype.dispose.call( this ); - - } - -} ); - -/** - * @classdesc Camera panorama - * @description See {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints|MediaStreamConstraints} for constraints - * @param {object} - camera constraints - * @constructor - */ -function CameraPanorama ( constraints ) { - - const radius = 5000; - const geometry = new SphereBufferGeometry( radius, 60, 40 ); - const material = new MeshBasicMaterial( { visible: false }); - - Panorama.call( this, geometry, material ); - - this.media = new Media( constraints ); - this.radius = radius; - - this.addEventListener( 'enter', this.start.bind( this ) ); - this.addEventListener( 'leave', this.stop.bind( this ) ); - this.addEventListener( 'panolens-container', this.onPanolensContainer.bind( this ) ); - this.addEventListener( 'panolens-scene', this.onPanolensScene.bind( this ) ); - -} - -CameraPanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { - - constructor: CameraPanorama, - - /** - * On container event - * @param {object} event - * @memberOf CameraPanorama - * @instance - */ - onPanolensContainer: function ( { container } ) { - - this.media.setContainer( container ); - - }, - - /** - * On scene event - * @param {object} event - * @memberOf CameraPanorama - * @instance - */ - onPanolensScene: function ( { scene } ) { - - this.media.setScene( scene ); - - }, - - /** - * Start camera streaming - * @memberOf CameraPanorama - * @instance - * @returns {Promise} - */ - start: function () { - - return this.media.start(); - - }, - - /** - * Stop camera streaming - * @memberOf CameraPanorama - * @instance - */ - stop: function () { - - this.media.stop(); - - }, - -} ); - -/** - * @classdesc Orbit Controls - * @constructor - * @external OrbitControls - * @param {THREE.Object} object - * @param {HTMLElement} domElement - */ -function OrbitControls ( object, domElement ) { - - this.object = object; - this.domElement = ( domElement !== undefined ) ? domElement : document; - this.frameId = null; - - // API - - // Set to false to disable this control - this.enabled = true; - - /* - * "target" sets the location of focus, where the control orbits around - * and where it pans with respect to. - */ - this.target = new Vector3(); - - // center is old, deprecated; use "target" instead - this.center = this.target; - - /* - * This option actually enables dollying in and out; left as "zoom" for - * backwards compatibility - */ - this.noZoom = false; - this.zoomSpeed = 1.0; - - // Limits to how far you can dolly in and out ( PerspectiveCamera only ) - this.minDistance = 0; - this.maxDistance = Infinity; - - // Limits to how far you can zoom in and out ( OrthographicCamera only ) - this.minZoom = 0; - this.maxZoom = Infinity; - - // Set to true to disable this control - this.noRotate = false; - this.rotateSpeed = -0.15; - - // Set to true to disable this control - this.noPan = true; - this.keyPanSpeed = 7.0; // pixels moved per arrow key push - - // Set to true to automatically rotate around the target - this.autoRotate = false; - this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 - - /* - * How far you can orbit vertically, upper and lower limits. - * Range is 0 to Math.PI radians. - */ - this.minPolarAngle = 0; // radians - this.maxPolarAngle = Math.PI; // radians - - // Momentum - this.momentumDampingFactor = 0.90; - this.momentumScalingFactor = -0.005; - this.momentumKeydownFactor = 20; - - // Fov - this.minFov = 30; - this.maxFov = 120; - - /* - * How far you can orbit horizontally, upper and lower limits. - * If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. - */ - this.minAzimuthAngle = - Infinity; // radians - this.maxAzimuthAngle = Infinity; // radians - - // Set to true to disable use of the keys - this.noKeys = false; - - // The four arrow keys - this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; - - // Mouse buttons - this.mouseButtons = { ORBIT: MOUSE.LEFT, ZOOM: MOUSE.MIDDLE, PAN: MOUSE.RIGHT }; - - /* - * ////////// - * internals - */ - - var scope = this; - - var EPS = 10e-8; - var MEPS = 10e-5; - - var rotateStart = new Vector2(); - var rotateEnd = new Vector2(); - var rotateDelta = new Vector2(); - - var panStart = new Vector2(); - var panEnd = new Vector2(); - var panDelta = new Vector2(); - var panOffset = new Vector3(); - - var offset = new Vector3(); - - var dollyStart = new Vector2(); - var dollyEnd = new Vector2(); - var dollyDelta = new Vector2(); - - var theta = 0; - var phi = 0; - var phiDelta = 0; - var thetaDelta = 0; - var scale = 1; - var pan = new Vector3(); - - var lastPosition = new Vector3(); - var lastQuaternion = new Quaternion(); - - var momentumLeft = 0, momentumUp = 0; - var eventPrevious; - var momentumOn = false; - - var keyUp, keyBottom, keyLeft, keyRight; - - var STATE = { NONE: -1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 }; - - var state = STATE.NONE; - - // for reset - - this.target0 = this.target.clone(); - this.position0 = this.object.position.clone(); - this.zoom0 = this.object.zoom; - - // so camera.up is the orbit axis - - var quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); - var quatInverse = quat.clone().inverse(); - - // events - - var changeEvent = { type: 'change' }; - var startEvent = { type: 'start' }; - var endEvent = { type: 'end' }; - - this.setLastQuaternion = function ( quaternion ) { - lastQuaternion.copy( quaternion ); - scope.object.quaternion.copy( quaternion ); - }; - - this.getLastPosition = function () { - return lastPosition; - }; - - this.rotateLeft = function ( angle ) { - - if ( angle === undefined ) { - - angle = getAutoRotationAngle(); - - } - - thetaDelta -= angle; - - - }; - - this.rotateUp = function ( angle ) { - - if ( angle === undefined ) { - - angle = getAutoRotationAngle(); - - } - - phiDelta -= angle; - - }; - - // pass in distance in world space to move left - this.panLeft = function ( distance ) { - - var te = this.object.matrix.elements; - - // get X column of matrix - panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] ); - panOffset.multiplyScalar( - distance ); - - pan.add( panOffset ); - - }; - - // pass in distance in world space to move up - this.panUp = function ( distance ) { - - var te = this.object.matrix.elements; - - // get Y column of matrix - panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] ); - panOffset.multiplyScalar( distance ); - - pan.add( panOffset ); - - }; - - /* - * pass in x,y of change desired in pixel space, - * right and down are positive - */ - this.pan = function ( deltaX, deltaY ) { - - var element = scope.domElement === document ? scope.domElement.body : scope.domElement; - - if ( scope.object instanceof PerspectiveCamera ) { - - // perspective - var position = scope.object.position; - var offset = position.clone().sub( scope.target ); - var targetDistance = offset.length(); - - // half of the fov is center to top of screen - targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); - - // we actually don't use screenWidth, since perspective camera is fixed to screen height - scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight ); - scope.panUp( 2 * deltaY * targetDistance / element.clientHeight ); - - } else if ( scope.object instanceof OrthographicCamera ) { - - // orthographic - scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth ); - scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight ); - - } else { - - // camera neither orthographic or perspective - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); - - } - - }; - - this.momentum = function(){ - - if ( !momentumOn ) return; - - if ( Math.abs( momentumLeft ) < MEPS && Math.abs( momentumUp ) < MEPS ) { - - momentumOn = false; - return; - } - - momentumUp *= this.momentumDampingFactor; - momentumLeft *= this.momentumDampingFactor; - - thetaDelta -= this.momentumScalingFactor * momentumLeft; - phiDelta -= this.momentumScalingFactor * momentumUp; - - }; - - this.dollyIn = function ( dollyScale ) { - - if ( dollyScale === undefined ) { - - dollyScale = getZoomScale(); - - } - - if ( scope.object instanceof PerspectiveCamera ) { - - scale /= dollyScale; - - } else if ( scope.object instanceof OrthographicCamera ) { - - scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom * dollyScale ) ); - scope.object.updateProjectionMatrix(); - scope.dispatchEvent( changeEvent ); - - } else { - - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); - - } - - }; - - this.dollyOut = function ( dollyScale ) { - - if ( dollyScale === undefined ) { - - dollyScale = getZoomScale(); - - } - - if ( scope.object instanceof PerspectiveCamera ) { - - scale *= dollyScale; - - } else if ( scope.object instanceof OrthographicCamera ) { - - scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / dollyScale ) ); - scope.object.updateProjectionMatrix(); - scope.dispatchEvent( changeEvent ); - - } else { - - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); - - } - - }; - - this.update = function ( ignoreUpdate ) { - - var position = this.object.position; - - offset.copy( position ).sub( this.target ); - - // rotate offset to "y-axis-is-up" space - offset.applyQuaternion( quat ); - - // angle from z-axis around y-axis - - theta = Math.atan2( offset.x, offset.z ); - - // angle from y-axis - - phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); - - if ( this.autoRotate && state === STATE.NONE ) { - - this.rotateLeft( getAutoRotationAngle() ); - - } - - this.momentum(); - - theta += thetaDelta; - phi += phiDelta; - - // restrict theta to be between desired limits - theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) ); - - // restrict phi to be between desired limits - phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); - - // restrict phi to be betwee EPS and PI-EPS - phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); - - var radius = offset.length() * scale; - - // restrict radius to be between desired limits - radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); - - // move target to panned location - this.target.add( pan ); - - offset.x = radius * Math.sin( phi ) * Math.sin( theta ); - offset.y = radius * Math.cos( phi ); - offset.z = radius * Math.sin( phi ) * Math.cos( theta ); - - // rotate offset back to "camera-up-vector-is-up" space - offset.applyQuaternion( quatInverse ); - - position.copy( this.target ).add( offset ); - - this.object.lookAt( this.target ); - - thetaDelta = 0; - phiDelta = 0; - scale = 1; - pan.set( 0, 0, 0 ); - - /* - * update condition is: - * min(camera displacement, camera rotation in radians)^2 > EPS - * using small-angle approximation cos(x/2) = 1 - x^2 / 8 - */ - if ( lastPosition.distanceToSquared( this.object.position ) > EPS - || 8 * (1 - lastQuaternion.dot(this.object.quaternion)) > EPS ) { - - if ( ignoreUpdate !== true ) { this.dispatchEvent( changeEvent ); } - - lastPosition.copy( this.object.position ); - lastQuaternion.copy (this.object.quaternion ); - - } - - }; - - - this.reset = function () { - - state = STATE.NONE; - - this.target.copy( this.target0 ); - this.object.position.copy( this.position0 ); - this.object.zoom = this.zoom0; - - this.object.updateProjectionMatrix(); - this.dispatchEvent( changeEvent ); - - this.update(); - - }; - - this.getPolarAngle = function () { - - return phi; - - }; - - this.getAzimuthalAngle = function () { - - return theta; - - }; - - function getAutoRotationAngle() { - - return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; - - } - - function getZoomScale() { - - return Math.pow( 0.95, scope.zoomSpeed ); - - } - - function onMouseDown( event ) { - - momentumOn = false; - - momentumLeft = momentumUp = 0; - - if ( scope.enabled === false ) return; - event.preventDefault(); - - if ( event.button === scope.mouseButtons.ORBIT ) { - if ( scope.noRotate === true ) return; - - state = STATE.ROTATE; - - rotateStart.set( event.clientX, event.clientY ); - - } else if ( event.button === scope.mouseButtons.ZOOM ) { - if ( scope.noZoom === true ) return; - - state = STATE.DOLLY; - - dollyStart.set( event.clientX, event.clientY ); - - } else if ( event.button === scope.mouseButtons.PAN ) { - if ( scope.noPan === true ) return; - - state = STATE.PAN; - - panStart.set( event.clientX, event.clientY ); - - } - - if ( state !== STATE.NONE ) { - document.addEventListener( 'mousemove', onMouseMove, false ); - document.addEventListener( 'mouseup', onMouseUp, false ); - scope.dispatchEvent( startEvent ); - } - - scope.update(); - - } - - function onMouseMove( event ) { - - if ( scope.enabled === false ) return; - - event.preventDefault(); - - var element = scope.domElement === document ? scope.domElement.body : scope.domElement; - - if ( state === STATE.ROTATE ) { - - if ( scope.noRotate === true ) return; - - rotateEnd.set( event.clientX, event.clientY ); - rotateDelta.subVectors( rotateEnd, rotateStart ); - - // rotating across whole screen goes 360 degrees around - scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); - - // rotating up and down along whole screen attempts to go 360, but limited to 180 - scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); - - rotateStart.copy( rotateEnd ); - - if( eventPrevious ){ - momentumLeft = event.clientX - eventPrevious.clientX; - momentumUp = event.clientY - eventPrevious.clientY; - } - - eventPrevious = event; - - } else if ( state === STATE.DOLLY ) { - - if ( scope.noZoom === true ) return; - - dollyEnd.set( event.clientX, event.clientY ); - dollyDelta.subVectors( dollyEnd, dollyStart ); - - if ( dollyDelta.y > 0 ) { - - scope.dollyIn(); - - } else if ( dollyDelta.y < 0 ) { - - scope.dollyOut(); - - } - - dollyStart.copy( dollyEnd ); - - } else if ( state === STATE.PAN ) { - - if ( scope.noPan === true ) return; - - panEnd.set( event.clientX, event.clientY ); - panDelta.subVectors( panEnd, panStart ); - - scope.pan( panDelta.x, panDelta.y ); - - panStart.copy( panEnd ); - - } - - if ( state !== STATE.NONE ) scope.update(); - - } - - function onMouseUp( /* event */ ) { - - momentumOn = true; - - eventPrevious = undefined; - - if ( scope.enabled === false ) return; - - document.removeEventListener( 'mousemove', onMouseMove, false ); - document.removeEventListener( 'mouseup', onMouseUp, false ); - scope.dispatchEvent( endEvent ); - state = STATE.NONE; - - } - - function onMouseWheel( event ) { - - if ( scope.enabled === false || scope.noZoom === true || state !== STATE.NONE ) return; - - event.preventDefault(); - event.stopPropagation(); - - var delta = 0; - - if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 - - delta = event.wheelDelta; - - } else if ( event.detail !== undefined ) { // Firefox - - delta = - event.detail; - - } - - if ( delta > 0 ) { - - // scope.dollyOut(); - scope.object.fov = ( scope.object.fov < scope.maxFov ) - ? scope.object.fov + 1 - : scope.maxFov; - scope.object.updateProjectionMatrix(); - - } else if ( delta < 0 ) { - - // scope.dollyIn(); - scope.object.fov = ( scope.object.fov > scope.minFov ) - ? scope.object.fov - 1 - : scope.minFov; - scope.object.updateProjectionMatrix(); - - } - - scope.update(); - scope.dispatchEvent( changeEvent ); - scope.dispatchEvent( startEvent ); - scope.dispatchEvent( endEvent ); - - } - - function onKeyUp ( event ) { - - switch ( event.keyCode ) { - - case scope.keys.UP: - keyUp = false; - break; - - case scope.keys.BOTTOM: - keyBottom = false; - break; - - case scope.keys.LEFT: - keyLeft = false; - break; - - case scope.keys.RIGHT: - keyRight = false; - break; - - } - - } - - function onKeyDown( event ) { - - if ( scope.enabled === false || scope.noKeys === true || scope.noRotate === true ) return; - - switch ( event.keyCode ) { - - case scope.keys.UP: - keyUp = true; - break; - - case scope.keys.BOTTOM: - keyBottom = true; - break; - - case scope.keys.LEFT: - keyLeft = true; - break; - - case scope.keys.RIGHT: - keyRight = true; - break; - - } - - if (keyUp || keyBottom || keyLeft || keyRight) { - - momentumOn = true; - - if (keyUp) momentumUp = - scope.rotateSpeed * scope.momentumKeydownFactor; - if (keyBottom) momentumUp = scope.rotateSpeed * scope.momentumKeydownFactor; - if (keyLeft) momentumLeft = - scope.rotateSpeed * scope.momentumKeydownFactor; - if (keyRight) momentumLeft = scope.rotateSpeed * scope.momentumKeydownFactor; - - } - - } - - function touchstart( event ) { - - momentumOn = false; - - momentumLeft = momentumUp = 0; - - if ( scope.enabled === false ) return; - - switch ( event.touches.length ) { - - case 1: // one-fingered touch: rotate - - if ( scope.noRotate === true ) return; - - state = STATE.TOUCH_ROTATE; - - rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); - break; - - case 2: // two-fingered touch: dolly - - if ( scope.noZoom === true ) return; - - state = STATE.TOUCH_DOLLY; - - var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; - var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; - var distance = Math.sqrt( dx * dx + dy * dy ); - - dollyStart.set( 0, distance ); - - break; - - case 3: // three-fingered touch: pan - - if ( scope.noPan === true ) return; - - state = STATE.TOUCH_PAN; - - panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); - break; - - default: - - state = STATE.NONE; - - } - - if ( state !== STATE.NONE ) scope.dispatchEvent( startEvent ); - - } - - function touchmove( event ) { - - if ( scope.enabled === false ) return; - - event.preventDefault(); - event.stopPropagation(); - - var element = scope.domElement === document ? scope.domElement.body : scope.domElement; - - switch ( event.touches.length ) { - - case 1: // one-fingered touch: rotate - - if ( scope.noRotate === true ) return; - if ( state !== STATE.TOUCH_ROTATE ) return; - - rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); - rotateDelta.subVectors( rotateEnd, rotateStart ); - - // rotating across whole screen goes 360 degrees around - scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); - // rotating up and down along whole screen attempts to go 360, but limited to 180 - scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); - - rotateStart.copy( rotateEnd ); - - if( eventPrevious ){ - momentumLeft = event.touches[ 0 ].pageX - eventPrevious.pageX; - momentumUp = event.touches[ 0 ].pageY - eventPrevious.pageY; - } - - eventPrevious = { - pageX: event.touches[ 0 ].pageX, - pageY: event.touches[ 0 ].pageY, - }; - - scope.update(); - break; - - case 2: // two-fingered touch: dolly - - if ( scope.noZoom === true ) return; - if ( state !== STATE.TOUCH_DOLLY ) return; - - var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; - var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; - var distance = Math.sqrt( dx * dx + dy * dy ); - - dollyEnd.set( 0, distance ); - dollyDelta.subVectors( dollyEnd, dollyStart ); - - if ( dollyDelta.y < 0 ) { - - scope.object.fov = ( scope.object.fov < scope.maxFov ) - ? scope.object.fov + 1 - : scope.maxFov; - scope.object.updateProjectionMatrix(); - - } else if ( dollyDelta.y > 0 ) { - - scope.object.fov = ( scope.object.fov > scope.minFov ) - ? scope.object.fov - 1 - : scope.minFov; - scope.object.updateProjectionMatrix(); - - } - - dollyStart.copy( dollyEnd ); - - scope.update(); - scope.dispatchEvent( changeEvent ); - break; - - case 3: // three-fingered touch: pan - - if ( scope.noPan === true ) return; - if ( state !== STATE.TOUCH_PAN ) return; - - panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); - panDelta.subVectors( panEnd, panStart ); - - scope.pan( panDelta.x, panDelta.y ); - - panStart.copy( panEnd ); - - scope.update(); - break; - - default: - - state = STATE.NONE; - - } - - } - - function touchend( /* event */ ) { - - momentumOn = true; - - eventPrevious = undefined; - - if ( scope.enabled === false ) return; - - scope.dispatchEvent( endEvent ); - state = STATE.NONE; - - } - - this.dispose = function() { - - this.domElement.removeEventListener( 'mousedown', onMouseDown ); - this.domElement.removeEventListener( 'mousewheel', onMouseWheel ); - this.domElement.removeEventListener( 'DOMMouseScroll', onMouseWheel ); - - this.domElement.removeEventListener( 'touchstart', touchstart ); - this.domElement.removeEventListener( 'touchend', touchend ); - this.domElement.removeEventListener( 'touchmove', touchmove ); - - window.removeEventListener( 'keyup', onKeyUp ); - window.removeEventListener( 'keydown', onKeyDown ); - - }; - - // this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); - this.domElement.addEventListener( 'mousedown', onMouseDown, { passive: false } ); - this.domElement.addEventListener( 'mousewheel', onMouseWheel, { passive: false } ); - this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, { passive: false } ); // firefox - - this.domElement.addEventListener( 'touchstart', touchstart, { passive: false } ); - this.domElement.addEventListener( 'touchend', touchend, { passive: false } ); - this.domElement.addEventListener( 'touchmove', touchmove, { passive: false } ); - - window.addEventListener( 'keyup', onKeyUp, { passive: false } ); - window.addEventListener( 'keydown', onKeyDown, { passive: false } ); - - // force an update at start - this.update(); - -} -OrbitControls.prototype = Object.assign( Object.create( EventDispatcher.prototype ), { - - constructor: OrbitControls - -} ); - -/** - * @classdesc Device Orientation Control - * @constructor - * @external DeviceOrientationControls - * @param {THREE.Camera} camera - * @param {HTMLElement} domElement - */ -function DeviceOrientationControls ( camera, domElement ) { - - var scope = this; - var changeEvent = { type: 'change' }; - - var rotY = 0; - var rotX = 0; - var tempX = 0; - var tempY = 0; - - this.camera = camera; - this.camera.rotation.reorder( 'YXZ' ); - this.domElement = ( domElement !== undefined ) ? domElement : document; - - this.enabled = true; - - this.deviceOrientation = {}; - this.screenOrientation = 0; - - this.alpha = 0; - this.alphaOffsetAngle = 0; - - - var onDeviceOrientationChangeEvent = function( event ) { - - scope.deviceOrientation = event; - - }; - - var onScreenOrientationChangeEvent = function() { - - scope.screenOrientation = window.orientation || 0; - - }; - - var onTouchStartEvent = function (event) { - - event.preventDefault(); - event.stopPropagation(); - - tempX = event.touches[ 0 ].pageX; - tempY = event.touches[ 0 ].pageY; - - }; - - var onTouchMoveEvent = function (event) { - - event.preventDefault(); - event.stopPropagation(); - - rotY += Math$1.degToRad( ( event.touches[ 0 ].pageX - tempX ) / 4 ); - rotX += Math$1.degToRad( ( tempY - event.touches[ 0 ].pageY ) / 4 ); - - scope.updateAlphaOffsetAngle( rotY ); - - tempX = event.touches[ 0 ].pageX; - tempY = event.touches[ 0 ].pageY; - - }; - - // The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y'' - - var setCameraQuaternion = function( quaternion, alpha, beta, gamma, orient ) { - - var zee = new Vector3( 0, 0, 1 ); - - var euler = new Euler(); - - var q0 = new Quaternion(); - - var q1 = new Quaternion( - Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis - - var vectorFingerY; - var fingerQY = new Quaternion(); - var fingerQX = new Quaternion(); - - if ( scope.screenOrientation == 0 ) { - - vectorFingerY = new Vector3( 1, 0, 0 ); - fingerQY.setFromAxisAngle( vectorFingerY, -rotX ); - - } else if ( scope.screenOrientation == 180 ) { - - vectorFingerY = new Vector3( 1, 0, 0 ); - fingerQY.setFromAxisAngle( vectorFingerY, rotX ); - - } else if ( scope.screenOrientation == 90 ) { - - vectorFingerY = new Vector3( 0, 1, 0 ); - fingerQY.setFromAxisAngle( vectorFingerY, rotX ); - - } else if ( scope.screenOrientation == - 90) { - - vectorFingerY = new Vector3( 0, 1, 0 ); - fingerQY.setFromAxisAngle( vectorFingerY, -rotX ); - - } - - q1.multiply( fingerQY ); - q1.multiply( fingerQX ); - - euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us - - quaternion.setFromEuler( euler ); // orient the device - - quaternion.multiply( q1 ); // camera looks out the back of the device, not the top - - quaternion.multiply( q0.setFromAxisAngle( zee, - orient ) ); // adjust for screen orientation - - }; - - this.connect = function() { - - onScreenOrientationChangeEvent(); // run once on load - - window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent, { passive: true } ); - window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, { passive: true } ); - window.addEventListener( 'deviceorientation', this.update.bind( this ), { passive: true } ); - - scope.domElement.addEventListener( 'touchstart', onTouchStartEvent, { passive: false } ); - scope.domElement.addEventListener( 'touchmove', onTouchMoveEvent, { passive: false } ); - - scope.enabled = true; - - }; - - this.disconnect = function() { - - window.removeEventListener( 'orientationchange', onScreenOrientationChangeEvent, false ); - window.removeEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false ); - window.removeEventListener( 'deviceorientation', this.update.bind( this ), false ); - - scope.domElement.removeEventListener( 'touchstart', onTouchStartEvent, false ); - scope.domElement.removeEventListener( 'touchmove', onTouchMoveEvent, false ); - - scope.enabled = false; - - }; - - this.update = function( ignoreUpdate ) { - - if ( scope.enabled === false ) return; - - var alpha = scope.deviceOrientation.alpha ? Math$1.degToRad( scope.deviceOrientation.alpha ) + scope.alphaOffsetAngle : 0; // Z - var beta = scope.deviceOrientation.beta ? Math$1.degToRad( scope.deviceOrientation.beta ) : 0; // X' - var gamma = scope.deviceOrientation.gamma ? Math$1.degToRad( scope.deviceOrientation.gamma ) : 0; // Y'' - var orient = scope.screenOrientation ? Math$1.degToRad( scope.screenOrientation ) : 0; // O - - setCameraQuaternion( scope.camera.quaternion, alpha, beta, gamma, orient ); - scope.alpha = alpha; - - if ( ignoreUpdate !== true ) { scope.dispatchEvent( changeEvent ); } - - }; - - this.updateAlphaOffsetAngle = function( angle ) { - - this.alphaOffsetAngle = angle; - this.update(); - - }; - - this.dispose = function() { - - this.disconnect(); - - }; - - this.connect(); - -} -DeviceOrientationControls.prototype = Object.assign( Object.create( EventDispatcher.prototype), { - - constructor: DeviceOrientationControls - -} ); - -/** - * @classdesc Google Cardboard Effect Composer - * @constructor - * @external CardboardEffect - * @param {THREE.WebGLRenderer} renderer - */ -function CardboardEffect ( renderer ) { - - var _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); - - var _scene = new Scene(); - - var _stereo = new StereoCamera(); - _stereo.aspect = 0.5; - - var _params = { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat }; - - var _renderTarget = new WebGLRenderTarget( 512, 512, _params ); - _renderTarget.scissorTest = true; - _renderTarget.texture.generateMipmaps = false; - - /* - * Distortion Mesh ported from: - * https://github.com/borismus/webvr-boilerplate/blob/master/src/distortion/barrel-distortion-fragment.js - */ - - var distortion = new Vector2( 0.441, 0.156 ); - - var geometry = new PlaneBufferGeometry( 1, 1, 10, 20 ).removeAttribute( 'normal' ).toNonIndexed(); - - var positions = geometry.attributes.position.array; - var uvs = geometry.attributes.uv.array; - - // duplicate - geometry.attributes.position.count *= 2; - geometry.attributes.uv.count *= 2; - - var positions2 = new Float32Array( positions.length * 2 ); - positions2.set( positions ); - positions2.set( positions, positions.length ); - - var uvs2 = new Float32Array( uvs.length * 2 ); - uvs2.set( uvs ); - uvs2.set( uvs, uvs.length ); - - var vector = new Vector2(); - var length = positions.length / 3; - - for ( var i = 0, l = positions2.length / 3; i < l; i ++ ) { - - vector.x = positions2[ i * 3 + 0 ]; - vector.y = positions2[ i * 3 + 1 ]; - - var dot = vector.dot( vector ); - var scalar = 1.5 + ( distortion.x + distortion.y * dot ) * dot; - - var offset = i < length ? 0 : 1; - - positions2[ i * 3 + 0 ] = ( vector.x / scalar ) * 1.5 - 0.5 + offset; - positions2[ i * 3 + 1 ] = ( vector.y / scalar ) * 3.0; - - uvs2[ i * 2 ] = ( uvs2[ i * 2 ] + offset ) * 0.5; - - } - - geometry.attributes.position.array = positions2; - geometry.attributes.uv.array = uvs2; - - // - - var material = new MeshBasicMaterial( { map: _renderTarget.texture } ); - var mesh = new Mesh( geometry, material ); - _scene.add( mesh ); - - // - - this.setSize = function ( width, height ) { - - renderer.setSize( width, height ); - - var pixelRatio = renderer.getPixelRatio(); - - _renderTarget.setSize( width * pixelRatio, height * pixelRatio ); - - }; - - this.render = function ( scene, camera ) { - - scene.updateMatrixWorld(); - - if ( camera.parent === null ) camera.updateMatrixWorld(); - - _stereo.update( camera ); - - var width = _renderTarget.width / 2; - var height = _renderTarget.height; - - if ( renderer.autoClear ) renderer.clear(); - - _renderTarget.scissor.set( 0, 0, width, height ); - _renderTarget.viewport.set( 0, 0, width, height ); - renderer.setRenderTarget( _renderTarget ); - renderer.render( scene, _stereo.cameraL ); - - renderer.clearDepth(); - - _renderTarget.scissor.set( width, 0, width, height ); - _renderTarget.viewport.set( width, 0, width, height ); - renderer.setRenderTarget( _renderTarget ); - renderer.render( scene, _stereo.cameraR ); - - renderer.clearDepth(); - - renderer.setRenderTarget( null ); - renderer.render( _scene, _camera ); - }; - -} - -/** - * @classdesc Stereo Effect Composer - * @constructor - * @external StereoEffect - * @param {THREE.WebGLRenderer} renderer - */ -const StereoEffect = function ( renderer ) { - - var _stereo = new StereoCamera(); - _stereo.aspect = 0.5; - var size = new Vector2(); - - this.setEyeSeparation = function ( eyeSep ) { - - _stereo.eyeSep = eyeSep; - - }; - - this.setSize = function ( width, height ) { - - renderer.setSize( width, height ); - - }; - - this.render = function ( scene, camera ) { - - scene.updateMatrixWorld(); - - if ( camera.parent === null ) camera.updateMatrixWorld(); - - _stereo.update( camera ); - - renderer.getSize( size ); - - if ( renderer.autoClear ) renderer.clear(); - renderer.setScissorTest( true ); - - renderer.setScissor( 0, 0, size.width / 2, size.height ); - renderer.setViewport( 0, 0, size.width / 2, size.height ); - renderer.render( scene, _stereo.cameraL ); - - renderer.setScissor( size.width / 2, 0, size.width / 2, size.height ); - renderer.setViewport( size.width / 2, 0, size.width / 2, size.height ); - renderer.render( scene, _stereo.cameraR ); - - renderer.setScissorTest( false ); - - }; - -}; - -/** - * @classdesc Viewer contains pre-defined scene, camera and renderer - * @constructor - * @param {object} [options] - Use custom or default config options - * @param {HTMLElement} [options.container] - A HTMLElement to host the canvas - * @param {THREE.Scene} [options.scene=THREE.Scene] - A THREE.Scene which contains panorama and 3D objects - * @param {THREE.Camera} [options.camera=THREE.PerspectiveCamera] - A THREE.Camera to view the scene - * @param {THREE.WebGLRenderer} [options.renderer=THREE.WebGLRenderer] - A THREE.WebGLRenderer to render canvas - * @param {boolean} [options.controlBar=true] - Show/hide control bar on the bottom of the container - * @param {array} [options.controlButtons=[]] - Button names to mount on controlBar if controlBar exists, Defaults to ['fullscreen', 'setting', 'video'] - * @param {boolean} [options.autoHideControlBar=false] - Auto hide control bar when click on non-active area - * @param {boolean} [options.autoHideInfospot=true] - Auto hide infospots when click on non-active area - * @param {boolean} [options.horizontalView=false] - Allow only horizontal camera control - * @param {number} [options.clickTolerance=10] - Distance tolerance to tigger click / tap event - * @param {number} [options.cameraFov=60] - Camera field of view value - * @param {boolean} [options.reverseDragging=false] - Reverse dragging direction - * @param {boolean} [options.enableReticle=false] - Enable reticle for mouseless interaction other than VR mode - * @param {number} [options.dwellTime=1500] - Dwell time for reticle selection in ms - * @param {boolean} [options.autoReticleSelect=true] - Auto select a clickable target after dwellTime - * @param {boolean} [options.viewIndicator=false] - Adds an angle view indicator in upper left corner - * @param {number} [options.indicatorSize=30] - Size of View Indicator - * @param {string} [options.output='none'] - Whether and where to output raycast position. Could be 'event', 'console' or 'overlay'. - * @param {boolean} [options.autoRotate=false] - Auto rotate - * @param {number} [options.autoRotateSpeed=2.0] - Auto rotate speed as in degree per second. Positive is counter-clockwise and negative is clockwise. - * @param {number} [options.autoRotateActivationDuration=5000] - Duration before auto rotatation when no user interactivity in ms - */ -function Viewer ( options ) { - - let container; - - options = options || {}; - options.controlBar = options.controlBar !== undefined ? options.controlBar : true; - options.controlButtons = options.controlButtons || [ 'fullscreen', 'setting', 'video' ]; - options.autoHideControlBar = options.autoHideControlBar !== undefined ? options.autoHideControlBar : false; - options.autoHideInfospot = options.autoHideInfospot !== undefined ? options.autoHideInfospot : true; - options.horizontalView = options.horizontalView !== undefined ? options.horizontalView : false; - options.clickTolerance = options.clickTolerance || 10; - options.cameraFov = options.cameraFov || 60; - options.reverseDragging = options.reverseDragging || false; - options.enableReticle = options.enableReticle || false; - options.dwellTime = options.dwellTime || 1500; - options.autoReticleSelect = options.autoReticleSelect !== undefined ? options.autoReticleSelect : true; - options.viewIndicator = options.viewIndicator !== undefined ? options.viewIndicator : false; - options.indicatorSize = options.indicatorSize || 30; - options.output = options.output ? options.output : 'none'; - options.autoRotate = options.autoRotate || false; - options.autoRotateSpeed = options.autoRotateSpeed || 2.0; - options.autoRotateActivationDuration = options.autoRotateActivationDuration || 5000; - - this.options = options; - - /* - * CSS Icon - * const styleLoader = new StyleLoader(); - * styleLoader.inject( 'icono' ); - */ - - // Container - if ( options.container ) { - - container = options.container; - container._width = container.clientWidth; - container._height = container.clientHeight; - - } else { - - container = document.createElement( 'div' ); - container.classList.add( 'panolens-container' ); - container.style.width = '100%'; - container.style.height = '100%'; - container._width = window.innerWidth; - container._height = window.innerHeight; - document.body.appendChild( container ); - - } - - this.container = container; - - this.camera = options.camera || new PerspectiveCamera( this.options.cameraFov, this.container.clientWidth / this.container.clientHeight, 1, 10000 ); - this.scene = options.scene || new Scene(); - this.renderer = options.renderer || new WebGLRenderer( { alpha: true, antialias: false } ); - this.sceneReticle = new Scene(); - - this.viewIndicatorSize = this.options.indicatorSize; - - this.reticle = {}; - this.tempEnableReticle = this.options.enableReticle; - - this.mode = MODES.NORMAL; - - this.panorama = null; - this.widget = null; - - this.hoverObject = null; - this.infospot = null; - this.pressEntityObject = null; - this.pressObject = null; - - this.raycaster = new Raycaster(); - this.raycasterPoint = new Vector2(); - this.userMouse = new Vector2(); - this.updateCallbacks = []; - this.requestAnimationId = null; - - this.cameraFrustum = new Frustum(); - this.cameraViewProjectionMatrix = new Matrix4(); - - this.autoRotateRequestId = null; - - this.outputDivElement = null; - - this.touchSupported = 'ontouchstart' in window || window.DocumentTouch && document instanceof DocumentTouch; - - // Handler references - this.HANDLER_MOUSE_DOWN = this.onMouseDown.bind( this ); - this.HANDLER_MOUSE_UP = this.onMouseUp.bind( this ); - this.HANDLER_MOUSE_MOVE = this.onMouseMove.bind( this ); - this.HANDLER_WINDOW_RESIZE = this.onWindowResize.bind( this ); - this.HANDLER_KEY_DOWN = this.onKeyDown.bind( this ); - this.HANDLER_KEY_UP = this.onKeyUp.bind( this ); - this.HANDLER_TAP = this.onTap.bind( this, { - clientX: this.container.clientWidth / 2, - clientY: this.container.clientHeight / 2 - } ); - - // Flag for infospot output - this.OUTPUT_INFOSPOT = false; - - // Animations - this.tweenLeftAnimation = new Tween.Tween(); - this.tweenUpAnimation = new Tween.Tween(); - - // Renderer - this.renderer.setPixelRatio( window.devicePixelRatio ); - this.renderer.setSize( this.container.clientWidth, this.container.clientHeight ); - this.renderer.setClearColor( 0x000000, 0 ); - this.renderer.autoClear = false; - - // Append Renderer Element to container - this.renderer.domElement.classList.add( 'panolens-canvas' ); - this.renderer.domElement.style.display = 'block'; - this.container.style.backgroundColor = '#000'; - this.container.appendChild( this.renderer.domElement ); - - // Camera Controls - this.OrbitControls = new OrbitControls( this.camera, this.container ); - this.OrbitControls.id = 'orbit'; - this.OrbitControls.minDistance = 1; - this.OrbitControls.noPan = true; - this.OrbitControls.autoRotate = this.options.autoRotate; - this.OrbitControls.autoRotateSpeed = this.options.autoRotateSpeed; - - this.DeviceOrientationControls = new DeviceOrientationControls( this.camera, this.container ); - this.DeviceOrientationControls.id = 'device-orientation'; - this.DeviceOrientationControls.enabled = false; - this.camera.position.z = 1; - - // Register change event if passiveRenering - if ( this.options.passiveRendering ) { - - console.warn( 'passiveRendering is now deprecated' ); - - } - - // Controls - this.controls = [ this.OrbitControls, this.DeviceOrientationControls ]; - this.control = this.OrbitControls; - - // Cardboard effect - this.CardboardEffect = new CardboardEffect( this.renderer ); - this.CardboardEffect.setSize( this.container.clientWidth, this.container.clientHeight ); - - // Stereo effect - this.StereoEffect = new StereoEffect( this.renderer ); - this.StereoEffect.setSize( this.container.clientWidth, this.container.clientHeight ); - - this.effect = this.CardboardEffect; - - // Add default hidden reticle - this.addReticle(); - - // Lock horizontal view - if ( this.options.horizontalView ) { - this.OrbitControls.minPolarAngle = Math.PI / 2; - this.OrbitControls.maxPolarAngle = Math.PI / 2; - } - - // Add Control UI - if ( this.options.controlBar !== false ) { - this.addDefaultControlBar( this.options.controlButtons ); - } - - // Add View Indicator - if ( this.options.viewIndicator ) { - this.addViewIndicator(); - } - - // Reverse dragging direction - if ( this.options.reverseDragging ) { - this.reverseDraggingDirection(); - } - - // Register event if reticle is enabled, otherwise defaults to mouse - if ( this.options.enableReticle ) { - this.enableReticleControl(); - } else { - this.registerMouseAndTouchEvents(); - } - - // Output infospot position to an overlay container if specified - if ( this.options.output === 'overlay' ) { - this.addOutputElement(); - } - - // Register dom event listeners - this.registerEventListeners(); - - // Animate - this.animate.call( this ); - -} -Viewer.prototype = Object.assign( Object.create( EventDispatcher.prototype ), { - - constructor: Viewer, - - /** - * Add an object to the scene - * Automatically hookup with panolens-viewer-handler listener - * to communicate with viewer method - * @param {THREE.Object3D} object - The object to be added - * @memberOf Viewer - * @instance - */ - add: function ( object ) { - - if ( arguments.length > 1 ) { - - for ( let i = 0; i < arguments.length; i ++ ) { - - this.add( arguments[ i ] ); - - } - - return this; - - } - - this.scene.add( object ); - - // All object added to scene has 'panolens-viewer-handler' event to handle viewer communication - if ( object.addEventListener ) { - - object.addEventListener( 'panolens-viewer-handler', this.eventHandler.bind( this ) ); - - } - - // All object added to scene being passed with container - if ( object instanceof Panorama && object.dispatchEvent ) { - - object.dispatchEvent( { type: 'panolens-container', container: this.container } ); - - } - - if ( object instanceof CameraPanorama ) { - - object.dispatchEvent( { type: 'panolens-scene', scene: this.scene } ); - - } - - // Hookup default panorama event listeners - if ( object.type === 'panorama' ) { - - this.addPanoramaEventListener( object ); - - if ( !this.panorama ) { - - this.setPanorama( object ); - - } - - } - - }, - - /** - * Remove an object from the scene - * @param {THREE.Object3D} object - Object to be removed - * @memberOf Viewer - * @instance - */ - remove: function ( object ) { - - if ( object.removeEventListener ) { - - object.removeEventListener( 'panolens-viewer-handler', this.eventHandler.bind( this ) ); - - } - - this.scene.remove( object ); - - }, - - /** - * Add default control bar - * @param {array} array - The control buttons array - * @memberOf Viewer - * @instance - */ - addDefaultControlBar: function ( array ) { - - if ( this.widget ) { - - console.warn( 'Default control bar exists' ); - return; - - } - - const widget = new Widget( this.container ); - widget.addEventListener( 'panolens-viewer-handler', this.eventHandler.bind( this ) ); - widget.addControlBar(); - array.forEach( buttonName => { - - widget.addControlButton( buttonName ); - - } ); - - this.widget = widget; - - }, - - /** - * Set a panorama to be the current one - * @param {Panorama} pano - Panorama to be set - * @memberOf Viewer - * @instance - */ - setPanorama: function ( pano ) { - - const leavingPanorama = this.panorama; - - if ( pano.type === 'panorama' && leavingPanorama !== pano ) { - - // Clear exisiting infospot - this.hideInfospot(); - - const afterEnterComplete = function () { - - if ( leavingPanorama ) { leavingPanorama.onLeave(); } - pano.removeEventListener( 'enter-fade-start', afterEnterComplete ); - - }; - - pano.addEventListener( 'enter-fade-start', afterEnterComplete ); - - // Assign and enter panorama - (this.panorama = pano).onEnter(); - - } - - }, - - /** - * Event handler to execute commands from child objects - * @param {object} event - The dispatched event with method as function name and data as an argument - * @memberOf Viewer - * @instance - */ - eventHandler: function ( event ) { - - if ( event.method && this[ event.method ] ) { - - this[ event.method ]( event.data ); - - } - - }, - - /** - * Dispatch event to all descendants - * @param {object} event - Event to be passed along - * @memberOf Viewer - * @instance - */ - dispatchEventToChildren: function ( event ) { - - this.scene.traverse( function ( object ) { - - if ( object.dispatchEvent ) { - - object.dispatchEvent( event ); - - } - - }); - - }, - - /** - * Set widget content - * @method activateWidgetItem - * @param {integer} controlIndex - Control index - * @param {integer} mode - Modes for effects - * @memberOf Viewer - * @instance - */ - activateWidgetItem: function ( controlIndex, mode ) { - - const mainMenu = this.widget.mainMenu; - const ControlMenuItem = mainMenu.children[ 0 ]; - const ModeMenuItem = mainMenu.children[ 1 ]; - - let item; - - if ( controlIndex !== undefined ) { - - switch ( controlIndex ) { - - case 0: - - item = ControlMenuItem.subMenu.children[ 1 ]; - - break; - - case 1: - - item = ControlMenuItem.subMenu.children[ 2 ]; - - break; - - default: - - item = ControlMenuItem.subMenu.children[ 1 ]; - - break; - - } - - ControlMenuItem.subMenu.setActiveItem( item ); - ControlMenuItem.setSelectionTitle( item.textContent ); - - } - - if ( mode !== undefined ) { - - switch( mode ) { - - case MODES.CARDBOARD: - - item = ModeMenuItem.subMenu.children[ 2 ]; - - break; - - case MODES.STEREO: - - item = ModeMenuItem.subMenu.children[ 3 ]; - - break; - - default: - - item = ModeMenuItem.subMenu.children[ 1 ]; - - break; - } - - ModeMenuItem.subMenu.setActiveItem( item ); - ModeMenuItem.setSelectionTitle( item.textContent ); - - } - - }, - - /** - * Enable rendering effect - * @param {MODES} mode - Modes for effects - * @memberOf Viewer - * @instance - */ - enableEffect: function ( mode ) { - - if ( this.mode === mode ) { return; } - if ( mode === MODES.NORMAL ) { this.disableEffect(); return; } - else { this.mode = mode; } - - const fov = this.camera.fov; - - switch( mode ) { - - case MODES.CARDBOARD: - - this.effect = this.CardboardEffect; - this.enableReticleControl(); - - break; - - case MODES.STEREO: - - this.effect = this.StereoEffect; - this.enableReticleControl(); - - break; - - default: - - this.effect = null; - this.disableReticleControl(); - - break; - - } - - this.activateWidgetItem( undefined, this.mode ); - - /** - * Dual eye effect event - * @type {object} - * @event Infospot#panolens-dual-eye-effect - * @property {MODES} mode - Current display mode - */ - this.dispatchEventToChildren( { type: 'panolens-dual-eye-effect', mode: this.mode } ); - - // Force effect stereo camera to update by refreshing fov - this.camera.fov = fov + 10e-3; - this.effect.setSize( this.container.clientWidth, this.container.clientHeight ); - this.render(); - this.camera.fov = fov; - - /** - * Dispatch mode change event - * @type {object} - * @event Viewer#mode-change - * @property {MODES} mode - Current display mode - */ - this.dispatchEvent( { type: 'mode-change', mode: this.mode } ); - - }, - - /** - * Disable additional rendering effect - * @memberOf Viewer - * @instance - */ - disableEffect: function () { - - if ( this.mode === MODES.NORMAL ) { return; } - - this.mode = MODES.NORMAL; - this.disableReticleControl(); - - this.activateWidgetItem( undefined, this.mode ); - - /** - * Dual eye effect event - * @type {object} - * @event Infospot#panolens-dual-eye-effect - * @property {MODES} mode - Current display mode - */ - this.dispatchEventToChildren( { type: 'panolens-dual-eye-effect', mode: this.mode } ); - - this.renderer.setSize( this.container.clientWidth, this.container.clientHeight ); - this.render(); - - /** - * Dispatch mode change event - * @type {object} - * @event Viewer#mode-change - * @property {MODES} mode - Current display mode - */ - this.dispatchEvent( { type: 'mode-change', mode: this.mode } ); - }, - - /** - * Enable reticle control - * @memberOf Viewer - * @instance - */ - enableReticleControl: function () { - - if ( this.reticle.visible ) { return; } - - this.tempEnableReticle = true; - - // Register reticle event and unregister mouse event - this.unregisterMouseAndTouchEvents(); - this.reticle.show(); - this.registerReticleEvent(); - this.updateReticleEvent(); - - }, - - /** - * Disable reticle control - * @memberOf Viewer - * @instance - */ - disableReticleControl: function () { - - this.tempEnableReticle = false; - - // Register mouse event and unregister reticle event - if ( !this.options.enableReticle ) { - - this.reticle.hide(); - this.unregisterReticleEvent(); - this.registerMouseAndTouchEvents(); - - } else { - - this.updateReticleEvent(); - - } - - }, - - /** - * Enable auto rotation - * @memberOf Viewer - * @instance - */ - enableAutoRate: function () { - - this.options.autoRotate = true; - this.OrbitControls.autoRotate = true; - - }, - - /** - * Disable auto rotation - * @memberOf Viewer - * @instance - */ - disableAutoRate: function () { - - clearTimeout( this.autoRotateRequestId ); - this.options.autoRotate = false; - this.OrbitControls.autoRotate = false; - - }, - - /** - * Toggle video play or stop - * @param {boolean} pause - * @memberOf Viewer - * @instance - * @fires Viewer#video-toggle - */ - toggleVideoPlay: function ( pause ) { - - if ( this.panorama instanceof VideoPanorama ) { - - /** - * Toggle video event - * @type {object} - * @event Viewer#video-toggle - */ - this.panorama.dispatchEvent( { type: 'video-toggle', pause: pause } ); - - } - - }, - - /** - * Set currentTime in a video - * @param {number} percentage - Percentage of a video. Range from 0.0 to 1.0 - * @memberOf Viewer - * @instance - * @fires Viewer#video-time - */ - setVideoCurrentTime: function ( percentage ) { - - if ( this.panorama instanceof VideoPanorama ) { - - /** - * Setting video time event - * @type {object} - * @event Viewer#video-time - * @property {number} percentage - Percentage of a video. Range from 0.0 to 1.0 - */ - this.panorama.dispatchEvent( { type: 'video-time', percentage: percentage } ); - - } - - }, - - /** - * This will be called when video updates if an widget is present - * @param {number} percentage - Percentage of a video. Range from 0.0 to 1.0 - * @memberOf Viewer - * @instance - * @fires Viewer#video-update - */ - onVideoUpdate: function ( percentage ) { - - const { widget } = this; - - /** - * Video update event - * @type {object} - * @event Viewer#video-update - * @property {number} percentage - Percentage of a video. Range from 0.0 to 1.0 - */ - if( widget ) { widget.dispatchEvent( { type: 'video-update', percentage: percentage } ); } - - }, - - /** - * Add update callback to be called every animation frame - * @param {function} callback - * @memberOf Viewer - * @instance - */ - addUpdateCallback: function ( fn ) { - - if ( fn ) { - - this.updateCallbacks.push( fn ); - - } - - }, - - /** - * Remove update callback - * @param {function} fn - The function to be removed - * @memberOf Viewer - * @instance - */ - removeUpdateCallback: function ( fn ) { - - const index = this.updateCallbacks.indexOf( fn ); - - if ( fn && index >= 0 ) { - - this.updateCallbacks.splice( index, 1 ); - - } - - }, - - /** - * Show video widget - * @memberOf Viewer - * @instance - */ - showVideoWidget: function () { - - const { widget } = this; - - /** - * Show video widget event - * @type {object} - * @event Viewer#video-control-show - */ - if( widget ) { widget.dispatchEvent( { type: 'video-control-show' } ); } - - }, - - /** - * Hide video widget - * @memberOf Viewer - * @instance - */ - hideVideoWidget: function () { - - const { widget } = this; - - /** - * Hide video widget - * @type {object} - * @event Viewer#video-control-hide - */ - if( widget ) { widget.dispatchEvent( { type: 'video-control-hide' } ); } - - }, - - /** - * Update video play button - * @param {boolean} paused - * @memberOf Viewer - * @instance - */ - updateVideoPlayButton: function ( paused ) { - - const { widget } = this; - - if ( widget && widget.videoElement && widget.videoElement.controlButton ) { - - widget.videoElement.controlButton.update( paused ); - - } - - }, - - /** - * Add default panorama event listeners - * @param {Panorama} pano - The panorama to be added with event listener - * @memberOf Viewer - * @instance - */ - addPanoramaEventListener: function ( pano ) { - - // Set camera control on every panorama - pano.addEventListener( 'enter-fade-start', this.setCameraControl.bind( this ) ); - - // Show and hide widget event only when it's VideoPanorama - if ( pano instanceof VideoPanorama ) { - - pano.addEventListener( 'enter-fade-start', this.showVideoWidget.bind( this ) ); - pano.addEventListener( 'leave', function () { - - if ( !(this.panorama instanceof VideoPanorama) ) { - - this.hideVideoWidget.call( this ); - - } - - }.bind( this ) ); - - } - - }, - - /** - * Set camera control - * @memberOf Viewer - * @instance - */ - setCameraControl: function () { - - this.OrbitControls.target.copy( this.panorama.position ); - - }, - - /** - * Get current camera control - * @return {object} - Current navigation control - * @memberOf Viewer - * @instance - * @returns {THREE.OrbitControls|THREE.DeviceOrientationControls} - */ - getControl: function () { - - return this.control; - - }, - - /** - * Get scene - * @memberOf Viewer - * @instance - * @return {THREE.Scene} - Current scene which the viewer is built on - */ - getScene: function () { - - return this.scene; - - }, - - /** - * Get camera - * @memberOf Viewer - * @instance - * @return {THREE.Camera} - The scene camera - */ - getCamera: function () { - - return this.camera; - - }, - - /** - * Get renderer - * @memberOf Viewer - * @instance - * @return {THREE.WebGLRenderer} - The renderer using webgl - */ - getRenderer: function () { - - return this.renderer; - - }, - - /** - * Get container - * @memberOf Viewer - * @instance - * @return {HTMLElement} - The container holds rendererd canvas - */ - getContainer: function () { - - return this.container; - - }, - - /** - * Get control id - * @memberOf Viewer - * @instance - * @return {string} - Control id. 'orbit' or 'device-orientation' - */ - getControlId: function () { - - return this.control.id; - - }, - - /** - * Get next navigation control id - * @memberOf Viewer - * @instance - * @return {string} - Next control id - */ - getNextControlId: function () { - - return this.controls[ this.getNextControlIndex() ].id; - - }, - - /** - * Get next navigation control index - * @memberOf Viewer - * @instance - * @return {number} - Next control index - */ - getNextControlIndex: function () { - - const controls = this.controls; - const control = this.control; - const nextIndex = controls.indexOf( control ) + 1; - - return ( nextIndex >= controls.length ) ? 0 : nextIndex; - - }, - - /** - * Set field of view of camera - * @param {number} fov - * @memberOf Viewer - * @instance - */ - setCameraFov: function ( fov ) { - - this.camera.fov = fov; - this.camera.updateProjectionMatrix(); - - }, - - /** - * Enable control by index - * @param {CONTROLS} index - Index of camera control - * @memberOf Viewer - * @instance - */ - enableControl: function ( index ) { - - index = ( index >= 0 && index < this.controls.length ) ? index : 0; - - this.control.enabled = false; - - this.control = this.controls[ index ]; - - this.control.enabled = true; - - switch ( index ) { - - case CONTROLS.ORBIT: - - this.camera.position.copy( this.panorama.position ); - this.camera.position.z += 1; - - break; - - case CONTROLS.DEVICEORIENTATION: - - this.camera.position.copy( this.panorama.position ); - - break; - - default: - - break; - } - - this.control.update(); - - this.activateWidgetItem( index, undefined ); - - }, - - /** - * Disable current control - * @memberOf Viewer - * @instance - */ - disableControl: function () { - - this.control.enabled = false; - - }, - - /** - * Toggle next control - * @memberOf Viewer - * @instance - */ - toggleNextControl: function () { - - this.enableControl( this.getNextControlIndex() ); - - }, - - /** - * Screen Space Projection - * @memberOf Viewer - * @instance - */ - getScreenVector: function ( worldVector ) { - - const vector = worldVector.clone(); - const widthHalf = ( this.container.clientWidth ) / 2; - const heightHalf = this.container.clientHeight / 2; - - vector.project( this.camera ); - - vector.x = ( vector.x * widthHalf ) + widthHalf; - vector.y = - ( vector.y * heightHalf ) + heightHalf; - vector.z = 0; - - return vector; - - }, - - /** - * Check Sprite in Viewport - * @memberOf Viewer - * @instance - */ - checkSpriteInViewport: function ( sprite ) { - - this.camera.matrixWorldInverse.getInverse( this.camera.matrixWorld ); - this.cameraViewProjectionMatrix.multiplyMatrices( this.camera.projectionMatrix, this.camera.matrixWorldInverse ); - this.cameraFrustum.setFromMatrix( this.cameraViewProjectionMatrix ); - - return sprite.visible && this.cameraFrustum.intersectsSprite( sprite ); - - }, - - /** - * Reverse dragging direction - * @memberOf Viewer - * @instance - */ - reverseDraggingDirection: function () { - - this.OrbitControls.rotateSpeed *= -1; - this.OrbitControls.momentumScalingFactor *= -1; - - }, - - /** - * Add reticle - * @memberOf Viewer - * @instance - */ - addReticle: function () { - - this.reticle = new Reticle( 0xffffff, true, this.options.dwellTime ); - this.reticle.hide(); - this.camera.add( this.reticle ); - this.sceneReticle.add( this.camera ); - - }, - - /** - * Tween control looking center - * @param {THREE.Vector3} vector - Vector to be looked at the center - * @param {number} [duration=1000] - Duration to tween - * @param {function} [easing=TWEEN.Easing.Exponential.Out] - Easing function - * @memberOf Viewer - * @instance - */ - tweenControlCenter: function ( vector, duration, easing ) { - - if ( this.control !== this.OrbitControls ) { - - return; - - } - - // Pass in arguments as array - if ( vector instanceof Array ) { - - duration = vector[ 1 ]; - easing = vector[ 2 ]; - vector = vector[ 0 ]; - - } - - duration = duration !== undefined ? duration : 1000; - easing = easing || Tween.Easing.Exponential.Out; - - let scope, ha, va, chv, cvv, hv, vv, vptc, ov, nv; - - scope = this; - - chv = this.camera.getWorldDirection( new Vector3() ); - cvv = chv.clone(); - - vptc = this.panorama.getWorldPosition( new Vector3() ).sub( this.camera.getWorldPosition( new Vector3() ) ); - - hv = vector.clone(); - // Scale effect - hv.x *= -1; - hv.add( vptc ).normalize(); - vv = hv.clone(); - - chv.y = 0; - hv.y = 0; - - ha = Math.atan2( hv.z, hv.x ) - Math.atan2( chv.z, chv.x ); - ha = ha > Math.PI ? ha - 2 * Math.PI : ha; - ha = ha < -Math.PI ? ha + 2 * Math.PI : ha; - va = Math.abs( cvv.angleTo( chv ) + ( cvv.y * vv.y <= 0 ? vv.angleTo( hv ) : -vv.angleTo( hv ) ) ); - va *= vv.y < cvv.y ? 1 : -1; - - ov = { left: 0, up: 0 }; - nv = { left: 0, up: 0 }; - - this.tweenLeftAnimation.stop(); - this.tweenUpAnimation.stop(); - - this.tweenLeftAnimation = new Tween.Tween( ov ) - .to( { left: ha }, duration ) - .easing( easing ) - .onUpdate(function(ov){ - scope.control.rotateLeft( ov.left - nv.left ); - nv.left = ov.left; - }) - .start(); - - this.tweenUpAnimation = new Tween.Tween( ov ) - .to( { up: va }, duration ) - .easing( easing ) - .onUpdate(function(ov){ - scope.control.rotateUp( ov.up - nv.up ); - nv.up = ov.up; - }) - .start(); - - }, - - /** - * Tween control looking center by object - * @param {THREE.Object3D} object - Object to be looked at the center - * @param {number} [duration=1000] - Duration to tween - * @param {function} [easing=TWEEN.Easing.Exponential.Out] - Easing function - * @memberOf Viewer - * @instance - */ - tweenControlCenterByObject: function ( object, duration, easing ) { - - let isUnderScalePlaceHolder = false; - - object.traverseAncestors( function ( ancestor ) { - - if ( ancestor.scalePlaceHolder ) { - - isUnderScalePlaceHolder = true; - - } - } ); - - if ( isUnderScalePlaceHolder ) { - - const invertXVector = new Vector3( -1, 1, 1 ); - - this.tweenControlCenter( object.getWorldPosition( new Vector3() ).multiply( invertXVector ), duration, easing ); - - } else { - - this.tweenControlCenter( object.getWorldPosition( new Vector3() ), duration, easing ); - - } - - }, - - /** - * This is called when window size is changed - * @fires Viewer#window-resize - * @param {number} [windowWidth] - Specify if custom element has changed width - * @param {number} [windowHeight] - Specify if custom element has changed height - * @memberOf Viewer - * @instance - */ - onWindowResize: function ( windowWidth, windowHeight ) { - - let width, height; - - const expand = this.container.classList.contains( 'panolens-container' ) || this.container.isFullscreen; - - if ( windowWidth !== undefined && windowHeight !== undefined ) { - - width = windowWidth; - height = windowHeight; - this.container._width = windowWidth; - this.container._height = windowHeight; - - } else { - - const isAndroid = /(android)/i.test(window.navigator.userAgent); - - const adjustWidth = isAndroid - ? Math.min(document.documentElement.clientWidth, window.innerWidth || 0) - : Math.max(document.documentElement.clientWidth, window.innerWidth || 0); - - const adjustHeight = isAndroid - ? Math.min(document.documentElement.clientHeight, window.innerHeight || 0) - : Math.max(document.documentElement.clientHeight, window.innerHeight || 0); - - width = expand ? adjustWidth : this.container.clientWidth; - height = expand ? adjustHeight : this.container.clientHeight; - - this.container._width = width; - this.container._height = height; - - } - - this.camera.aspect = width / height; - this.camera.updateProjectionMatrix(); - - this.renderer.setSize( width, height ); - - // Update reticle - if ( this.options.enableReticle || this.tempEnableReticle ) { - - this.updateReticleEvent(); - - } - - /** - * Window resizing event - * @type {object} - * @event Viewer#window-resize - * @property {number} width - Width of the window - * @property {number} height - Height of the window - */ - this.dispatchEvent( { type: 'window-resize', width: width, height: height }); - this.scene.traverse( function ( object ) { - - if ( object.dispatchEvent ) { - - object.dispatchEvent( { type: 'window-resize', width: width, height: height }); - - } - - } ); - - }, - - /** - * Add output element - * @memberOf Viewer - * @instance - */ - addOutputElement: function () { - - const element = document.createElement( 'div' ); - element.style.position = 'absolute'; - element.style.right = '10px'; - element.style.top = '10px'; - element.style.color = '#fff'; - this.container.appendChild( element ); - this.outputDivElement = element; - - }, - - /** - * Output position in developer console by holding down Ctrl button - * @memberOf Viewer - * @instance - */ - outputPosition: function () { - - const intersects = this.raycaster.intersectObject( this.panorama, true ); - - if ( intersects.length > 0 ) { - - const point = intersects[ 0 ].point.clone(); - const converter = new Vector3( -1, 1, 1 ); - const world = this.panorama.getWorldPosition( new Vector3() ); - point.sub( world ).multiply( converter ); - - const position = { - x: point.x.toFixed(2), - y: point.y.toFixed(2), - z: point.z.toFixed(2), - }; - - const message = `${position.x}, ${position.y}, ${position.z}`; - - if ( point.length() === 0 ) { return; } - - switch ( this.options.output ) { - - case 'event': - /** - * Dispatch raycast position as event - * @type {object} - * @event Viewer#position-output - */ - this.dispatchEvent( { type: 'position-output', position: position } ); - break; - - case 'console': - console.info( message ); - break; - - case 'overlay': - this.outputDivElement.textContent = message; - break; - - default: - break; - - } - - } - - }, - - /** - * On mouse down - * @param {MouseEvent} event - * @memberOf Viewer - * @instance - */ - onMouseDown: function ( event ) { - - event.preventDefault(); - - this.userMouse.x = ( event.clientX >= 0 ) ? event.clientX : event.touches[0].clientX; - this.userMouse.y = ( event.clientY >= 0 ) ? event.clientY : event.touches[0].clientY; - this.userMouse.type = 'mousedown'; - this.onTap( event ); - - }, - - /** - * On mouse move - * @param {MouseEvent} event - * @memberOf Viewer - * @instance - */ - onMouseMove: function ( event ) { - - event.preventDefault(); - this.userMouse.type = 'mousemove'; - this.onTap( event ); - - }, - - /** - * On mouse up - * @param {MouseEvent} event - * @memberOf Viewer - * @instance - */ - onMouseUp: function ( event ) { - - let onTarget = false; - - this.userMouse.type = 'mouseup'; - - const type = ( this.userMouse.x >= event.clientX - this.options.clickTolerance - && this.userMouse.x <= event.clientX + this.options.clickTolerance - && this.userMouse.y >= event.clientY - this.options.clickTolerance - && this.userMouse.y <= event.clientY + this.options.clickTolerance ) - || ( event.changedTouches - && this.userMouse.x >= event.changedTouches[0].clientX - this.options.clickTolerance - && this.userMouse.x <= event.changedTouches[0].clientX + this.options.clickTolerance - && this.userMouse.y >= event.changedTouches[0].clientY - this.options.clickTolerance - && this.userMouse.y <= event.changedTouches[0].clientY + this.options.clickTolerance ) - ? 'click' : undefined; - - // Event should happen on canvas - if ( event && event.target && !event.target.classList.contains( 'panolens-canvas' ) ) { return; } - - event.preventDefault(); - - if ( event.changedTouches && event.changedTouches.length === 1 ) { - - onTarget = this.onTap( { clientX: event.changedTouches[0].clientX, clientY: event.changedTouches[0].clientY }, type ); - - } else { - - onTarget = this.onTap( event, type ); - - } - - this.userMouse.type = 'none'; - - if ( onTarget ) { - - return; - - } - - if ( type === 'click' ) { - - const { options: { autoHideInfospot, autoHideControlBar }, panorama, toggleControlBar } = this; - - if ( autoHideInfospot && panorama ) { - - panorama.toggleInfospotVisibility(); - - } - - if ( autoHideControlBar ) { - - toggleControlBar(); - - } - - } - - }, - - /** - * On tap eveny frame - * @param {MouseEvent} event - * @param {string} type - * @memberOf Viewer - * @instance - */ - onTap: function ( event, type ) { - - const { left, top } = this.container.getBoundingClientRect(); - const { clientWidth, clientHeight } = this.container; - - this.raycasterPoint.x = ( ( event.clientX - left ) / clientWidth ) * 2 - 1; - this.raycasterPoint.y = - ( ( event.clientY - top ) / clientHeight ) * 2 + 1; - - this.raycaster.setFromCamera( this.raycasterPoint, this.camera ); - - // Return if no panorama - if ( !this.panorama ) { - - return; - - } - - // output infospot information - if ( event.type !== 'mousedown' && this.touchSupported || this.OUTPUT_INFOSPOT ) { - - this.outputPosition(); - - } - - - const intersects = this.raycaster.intersectObjects( this.panorama.children, true ); - const intersect_entity = this.getConvertedIntersect( intersects ); - const intersect = ( intersects.length > 0 ) ? intersects[0].object : undefined; - - if ( this.userMouse.type === 'mouseup' ) { - - if ( intersect_entity && this.pressEntityObject === intersect_entity && this.pressEntityObject.dispatchEvent ) { - - this.pressEntityObject.dispatchEvent( { type: 'pressstop-entity', mouseEvent: event } ); - - } - - this.pressEntityObject = undefined; - - } - - if ( this.userMouse.type === 'mouseup' ) { - - if ( intersect && this.pressObject === intersect && this.pressObject.dispatchEvent ) { - - this.pressObject.dispatchEvent( { type: 'pressstop', mouseEvent: event } ); - - } - - this.pressObject = undefined; - - } - - if ( type === 'click' ) { - - this.panorama.dispatchEvent( { type: 'click', intersects: intersects, mouseEvent: event } ); - - if ( intersect_entity && intersect_entity.dispatchEvent ) { - - intersect_entity.dispatchEvent( { type: 'click-entity', mouseEvent: event } ); - - } - - if ( intersect && intersect.dispatchEvent ) { - - intersect.dispatchEvent( { type: 'click', mouseEvent: event } ); - - } - - } else { - - this.panorama.dispatchEvent( { type: 'hover', intersects: intersects, mouseEvent: event } ); - - if ( ( this.hoverObject && intersects.length > 0 && this.hoverObject !== intersect_entity ) - || ( this.hoverObject && intersects.length === 0 ) ){ - - if ( this.hoverObject.dispatchEvent ) { - - this.hoverObject.dispatchEvent( { type: 'hoverleave', mouseEvent: event } ); - - this.reticle.end(); - - } - - this.hoverObject = undefined; - - } - - if ( intersect_entity && intersects.length > 0 ) { - - if ( this.hoverObject !== intersect_entity ) { - - this.hoverObject = intersect_entity; - - if ( this.hoverObject.dispatchEvent ) { - - this.hoverObject.dispatchEvent( { type: 'hoverenter', mouseEvent: event } ); - - // Start reticle timer - if ( this.options.autoReticleSelect && this.options.enableReticle || this.tempEnableReticle ) { - this.reticle.start( this.onTap.bind( this, event, 'click' ) ); - } - - } - - } - - if ( this.userMouse.type === 'mousedown' && this.pressEntityObject != intersect_entity ) { - - this.pressEntityObject = intersect_entity; - - if ( this.pressEntityObject.dispatchEvent ) { - - this.pressEntityObject.dispatchEvent( { type: 'pressstart-entity', mouseEvent: event } ); - - } - - } - - if ( this.userMouse.type === 'mousedown' && this.pressObject != intersect ) { - - this.pressObject = intersect; - - if ( this.pressObject.dispatchEvent ) { - - this.pressObject.dispatchEvent( { type: 'pressstart', mouseEvent: event } ); - - } - - } - - if ( this.userMouse.type === 'mousemove' || this.options.enableReticle ) { - - if ( intersect && intersect.dispatchEvent ) { - - intersect.dispatchEvent( { type: 'hover', mouseEvent: event } ); - - } - - if ( this.pressEntityObject && this.pressEntityObject.dispatchEvent ) { - - this.pressEntityObject.dispatchEvent( { type: 'pressmove-entity', mouseEvent: event } ); - - } - - if ( this.pressObject && this.pressObject.dispatchEvent ) { - - this.pressObject.dispatchEvent( { type: 'pressmove', mouseEvent: event } ); - - } - - } - - } - - if ( !intersect_entity && this.pressEntityObject && this.pressEntityObject.dispatchEvent ) { - - this.pressEntityObject.dispatchEvent( { type: 'pressstop-entity', mouseEvent: event } ); - - this.pressEntityObject = undefined; - - } - - if ( !intersect && this.pressObject && this.pressObject.dispatchEvent ) { - - this.pressObject.dispatchEvent( { type: 'pressstop', mouseEvent: event } ); - - this.pressObject = undefined; - - } - - } - - // Infospot handler - if ( intersect && intersect instanceof Infospot ) { - - this.infospot = intersect; - - if ( type === 'click' ) { - - return true; - - } - - - } else if ( this.infospot ) { - - this.hideInfospot(); - - } - - // Auto rotate - if ( this.options.autoRotate && this.userMouse.type !== 'mousemove' ) { - - // Auto-rotate idle timer - clearTimeout( this.autoRotateRequestId ); - - if ( this.control === this.OrbitControls ) { - - this.OrbitControls.autoRotate = false; - this.autoRotateRequestId = window.setTimeout( this.enableAutoRate.bind( this ), this.options.autoRotateActivationDuration ); - - } - - } - - }, - - /** - * Get converted intersect - * @param {array} intersects - * @memberOf Viewer - * @instance - */ - getConvertedIntersect: function ( intersects ) { - - let intersect; - - for ( let i = 0; i < intersects.length; i++ ) { - - if ( intersects[i].distance >= 0 && intersects[i].object && !intersects[i].object.passThrough ) { - - if ( intersects[i].object.entity && intersects[i].object.entity.passThrough ) { - continue; - } else if ( intersects[i].object.entity && !intersects[i].object.entity.passThrough ) { - intersect = intersects[i].object.entity; - break; - } else { - intersect = intersects[i].object; - break; - } - - } - - } - - return intersect; - - }, - - /** - * Hide infospot - * @memberOf Viewer - * @instance - */ - hideInfospot: function () { - - if ( this.infospot ) { - - this.infospot.onHoverEnd(); - - this.infospot = undefined; - - } - - }, - - /** - * Toggle control bar - * @memberOf Viewer - * @instance - * @fires Viewer#control-bar-toggle - */ - toggleControlBar: function () { - - const { widget } = this; - - /** - * Toggle control bar event - * @type {object} - * @event Viewer#control-bar-toggle - */ - if ( widget ) { - - widget.dispatchEvent( { type: 'control-bar-toggle' } ); - - } - - }, - - /** - * On key down - * @param {KeyboardEvent} event - * @memberOf Viewer - * @instance - */ - onKeyDown: function ( event ) { - - if ( this.options.output && this.options.output !== 'none' && event.key === 'Control' ) { - - this.OUTPUT_INFOSPOT = true; - - } - - }, - - /** - * On key up - * @param {KeyboardEvent} event - * @memberOf Viewer - * @instance - */ - onKeyUp: function () { - - this.OUTPUT_INFOSPOT = false; - - }, - - /** - * Update control and callbacks - * @memberOf Viewer - * @instance - */ - update: function () { - - Tween.update(); - - this.updateCallbacks.forEach( function( callback ){ callback(); } ); - - this.control.update(); - - this.scene.traverse( function( child ){ - if ( child instanceof Infospot - && child.element - && ( this.hoverObject === child - || child.element.style.display !== 'none' - || (child.element.left && child.element.left.style.display !== 'none') - || (child.element.right && child.element.right.style.display !== 'none') ) ) { - if ( this.checkSpriteInViewport( child ) ) { - const { x, y } = this.getScreenVector( child.getWorldPosition( new Vector3() ) ); - child.translateElement( x, y ); - } else { - child.onDismiss(); - } - - } - }.bind( this ) ); - - }, - - /** - * Rendering function to be called on every animation frame - * Render reticle last - * @memberOf Viewer - * @instance - */ - render: function () { - - if ( this.mode === MODES.CARDBOARD || this.mode === MODES.STEREO ) { - - this.renderer.clear(); - this.effect.render( this.scene, this.camera ); - this.effect.render( this.sceneReticle, this.camera ); - - - } else { - - this.renderer.clear(); - this.renderer.render( this.scene, this.camera ); - this.renderer.clearDepth(); - this.renderer.render( this.sceneReticle, this.camera ); - - } - - }, - - /** - * Animate - * @memberOf Viewer - * @instance - */ - animate: function () { - - this.requestAnimationId = window.requestAnimationFrame( this.animate.bind( this ) ); - - this.onChange(); - - }, - - /** - * On change - * @memberOf Viewer - * @instance - */ - onChange: function () { - - this.update(); - this.render(); - - }, - - /** - * Register mouse and touch event on container - * @memberOf Viewer - * @instance - */ - registerMouseAndTouchEvents: function () { - - const options = { passive: false }; - - this.container.addEventListener( 'mousedown' , this.HANDLER_MOUSE_DOWN, options ); - this.container.addEventListener( 'mousemove' , this.HANDLER_MOUSE_MOVE, options ); - this.container.addEventListener( 'mouseup' , this.HANDLER_MOUSE_UP , options ); - this.container.addEventListener( 'touchstart', this.HANDLER_MOUSE_DOWN, options ); - this.container.addEventListener( 'touchend' , this.HANDLER_MOUSE_UP , options ); - - }, - - /** - * Unregister mouse and touch event on container - * @memberOf Viewer - * @instance - */ - unregisterMouseAndTouchEvents: function () { - - this.container.removeEventListener( 'mousedown' , this.HANDLER_MOUSE_DOWN, false ); - this.container.removeEventListener( 'mousemove' , this.HANDLER_MOUSE_MOVE, false ); - this.container.removeEventListener( 'mouseup' , this.HANDLER_MOUSE_UP , false ); - this.container.removeEventListener( 'touchstart', this.HANDLER_MOUSE_DOWN, false ); - this.container.removeEventListener( 'touchend' , this.HANDLER_MOUSE_UP , false ); - - }, - - /** - * Register reticle event - * @memberOf Viewer - * @instance - */ - registerReticleEvent: function () { - - this.addUpdateCallback( this.HANDLER_TAP ); - - }, - - /** - * Unregister reticle event - * @memberOf Viewer - * @instance - */ - unregisterReticleEvent: function () { - - this.removeUpdateCallback( this.HANDLER_TAP ); - - }, - - /** - * Update reticle event - * @memberOf Viewer - * @instance - */ - updateReticleEvent: function () { - - const clientX = this.container.clientWidth / 2 + this.container.offsetLeft; - const clientY = this.container.clientHeight / 2; - - this.removeUpdateCallback( this.HANDLER_TAP ); - this.HANDLER_TAP = this.onTap.bind( this, { clientX, clientY } ); - this.addUpdateCallback( this.HANDLER_TAP ); - - }, - - /** - * Register container and window listeners - * @memberOf Viewer - * @instance - */ - registerEventListeners: function () { - - // Resize Event - window.addEventListener( 'resize' , this.HANDLER_WINDOW_RESIZE, true ); - - // Keyboard Event - window.addEventListener( 'keydown', this.HANDLER_KEY_DOWN, true ); - window.addEventListener( 'keyup' , this.HANDLER_KEY_UP , true ); - - }, - - /** - * Unregister container and window listeners - * @memberOf Viewer - * @instance - */ - unregisterEventListeners: function () { - - // Resize Event - window.removeEventListener( 'resize' , this.HANDLER_WINDOW_RESIZE, true ); - - // Keyboard Event - window.removeEventListener( 'keydown', this.HANDLER_KEY_DOWN, true ); - window.removeEventListener( 'keyup' , this.HANDLER_KEY_UP , true ); - - }, - - /** - * Dispose all scene objects and clear cache - * @memberOf Viewer - * @instance - */ - dispose: function () { - - this.tweenLeftAnimation.stop(); - this.tweenUpAnimation.stop(); - - // Unregister dom event listeners - this.unregisterEventListeners(); - - // recursive disposal on 3d objects - function recursiveDispose ( object ) { - - for ( let i = object.children.length - 1; i >= 0; i-- ) { - - recursiveDispose( object.children[i] ); - object.remove( object.children[i] ); - - } - - if ( object instanceof Panorama || object instanceof Infospot ) { - - object.dispose(); - object = null; - - } else if ( object.dispatchEvent ){ - - object.dispatchEvent( 'dispose' ); - - } - - } - - recursiveDispose( this.scene ); - - // dispose widget - if ( this.widget ) { - - this.widget.dispose(); - this.widget = null; - - } - - // clear cache - if ( Cache && Cache.enabled ) { - - Cache.clear(); - - } - - }, - - /** - * Destroy viewer by disposing and stopping requestAnimationFrame - * @memberOf Viewer - * @instance - */ - destroy: function () { - - this.dispose(); - this.render(); - window.cancelAnimationFrame( this.requestAnimationId ); - - }, - - /** - * On panorama dispose - * @memberOf Viewer - * @instance - */ - onPanoramaDispose: function ( panorama ) { - - if ( panorama instanceof VideoPanorama ) { - - this.hideVideoWidget(); - - } - - if ( panorama === this.panorama ) { - - this.panorama = null; - - } - - }, - - /** - * Load ajax call - * @param {string} url - URL to be requested - * @param {function} [callback] - Callback after request completes - * @memberOf Viewer - * @instance - */ - loadAsyncRequest: function ( url, callback = () => {} ) { - - const request = new window.XMLHttpRequest(); - request.onloadend = function ( event ) { - callback( event ); - }; - request.open( 'GET', url, true ); - request.send( null ); - - }, - - /** - * View indicator in upper left - * @memberOf Viewer - * @instance - */ - addViewIndicator: function () { - - const scope = this; - - function loadViewIndicator ( asyncEvent ) { - - if ( asyncEvent.loaded === 0 ) return; - - const viewIndicatorDiv = asyncEvent.target.responseXML.documentElement; - viewIndicatorDiv.style.width = scope.viewIndicatorSize + 'px'; - viewIndicatorDiv.style.height = scope.viewIndicatorSize + 'px'; - viewIndicatorDiv.style.position = 'absolute'; - viewIndicatorDiv.style.top = '10px'; - viewIndicatorDiv.style.left = '10px'; - viewIndicatorDiv.style.opacity = '0.5'; - viewIndicatorDiv.style.cursor = 'pointer'; - viewIndicatorDiv.id = 'panolens-view-indicator-container'; - - scope.container.appendChild( viewIndicatorDiv ); - - const indicator = viewIndicatorDiv.querySelector( '#indicator' ); - const setIndicatorD = function () { - - scope.radius = scope.viewIndicatorSize * 0.225; - scope.currentPanoAngle = scope.camera.rotation.y - Math$1.degToRad( 90 ); - scope.fovAngle = Math$1.degToRad( scope.camera.fov ) ; - scope.leftAngle = -scope.currentPanoAngle - scope.fovAngle / 2; - scope.rightAngle = -scope.currentPanoAngle + scope.fovAngle / 2; - scope.leftX = scope.radius * Math.cos( scope.leftAngle ); - scope.leftY = scope.radius * Math.sin( scope.leftAngle ); - scope.rightX = scope.radius * Math.cos( scope.rightAngle ); - scope.rightY = scope.radius * Math.sin( scope.rightAngle ); - scope.indicatorD = 'M ' + scope.leftX + ' ' + scope.leftY + ' A ' + scope.radius + ' ' + scope.radius + ' 0 0 1 ' + scope.rightX + ' ' + scope.rightY; - - if ( scope.leftX && scope.leftY && scope.rightX && scope.rightY && scope.radius ) { - - indicator.setAttribute( 'd', scope.indicatorD ); - - } - - }; - - scope.addUpdateCallback( setIndicatorD ); - - const indicatorOnMouseEnter = function () { - - this.style.opacity = '1'; - - }; - - const indicatorOnMouseLeave = function () { - - this.style.opacity = '0.5'; - - }; - - viewIndicatorDiv.addEventListener( 'mouseenter', indicatorOnMouseEnter ); - viewIndicatorDiv.addEventListener( 'mouseleave', indicatorOnMouseLeave ); - } - - this.loadAsyncRequest( DataImage.ViewIndicator, loadViewIndicator ); - - }, - - /** - * Append custom control item to existing control bar - * @param {object} [option={}] - Style object to overwirte default element style. It takes 'style', 'onTap' and 'group' properties. - * @memberOf Viewer - * @instance - */ - appendControlItem: function ( option ) { - - const item = this.widget.createCustomItem( option ); - - if ( option.group === 'video' ) { - - this.widget.videoElement.appendChild( item ); - - } else { - - this.widget.barElement.appendChild( item ); - - } - - return item; - - }, - - /** - * Clear all cached files - * @memberOf Viewer - * @instance - */ - clearAllCache: function () { - - Cache.clear(); - - } - -} ); - -if ( REVISION$1 != THREE_REVISION ) { - - console.warn( `three.js version is not matched. Please consider use the target revision ${THREE_REVISION}` ); - -} - -/** - * Panolens.js - * @author pchen66 - * @namespace PANOLENS - */ -window.TWEEN = Tween; - -export { BasicPanorama, CONTROLS, CameraPanorama, CubePanorama, CubeTextureLoader, DataImage, EmptyPanorama, GoogleStreetviewPanorama, ImageLittlePlanet, ImageLoader, ImagePanorama, Infospot, LittlePlanet, MODES, Media, Panorama, REVISION, Reticle, THREE_REVISION, THREE_VERSION, TextureLoader, VERSION, VideoPanorama, Viewer, Widget }; From 9fab3b0e679614891bea8f4f2a084614b53f6d9e Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Wed, 2 Mar 2022 11:34:17 +0100 Subject: [PATCH 02/42] Remove usage of process variable in ImageLoader --- src/loaders/ImageLoader.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/loaders/ImageLoader.js b/src/loaders/ImageLoader.js index f2ea0760..e8d05db2 100644 --- a/src/loaders/ImageLoader.js +++ b/src/loaders/ImageLoader.js @@ -88,14 +88,6 @@ const ImageLoader = { request = new window.XMLHttpRequest(); request.open('GET', url, true); - if (process.env.npm_lifecycle_event !== 'test') { - /* istanbul ignore next */ - request.onreadystatechange = function () { - if (this.readyState === 4 && this.status >= 400) { - onError(); - } - }; - } request.responseType = 'arraybuffer'; request.addEventListener( 'error', onError ); request.addEventListener( 'progress', event => { From 1ae6ae90f5e487bf3f4ecd5ab573ba80c94ce62b Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Wed, 2 Mar 2022 13:38:29 +0100 Subject: [PATCH 03/42] update three.js, use class for Infospot, Reticle, ImagePanorama, Panorama and Widget --- package-lock.json | 10220 +++++++++++++++++++++++++++++++- package.json | 2 +- src/infospot/Infospot.js | 264 +- src/interface/Reticle.js | 223 +- src/panorama/ImagePanorama.js | 52 +- src/panorama/Panorama.js | 401 +- src/widget/Widget.js | 167 +- 7 files changed, 10726 insertions(+), 603 deletions(-) diff --git a/package-lock.json b/package-lock.json index 87f66466..fb00d725 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,10172 @@ { "name": "panolens", - "version": "0.11.0", - "lockfileVersion": 1, + "version": "0.12.1", + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "panolens", + "version": "0.12.1", + "license": "MIT", + "dependencies": { + "three": "^0.136" + }, + "devDependencies": { + "@tweenjs/tween.js": "^17.4.0", + "ava": "^2.1.0", + "browser-env": "^3.2.6", + "concurrently": "^4.1.0", + "coveralls": "^3.0.4", + "docdash": "^1.1.1", + "eslint": "^5.16.0", + "google-closure-compiler": "^20190528.0.0", + "http-server": "^0.12.3", + "jsdoc": "^3.6.6", + "local-web-server": "^3.0.0", + "nyc": "^14.1.1", + "rollup": "^1.15.1", + "rollup-plugin-commonjs": "^10.0.0", + "rollup-plugin-inject": "^2.2.0", + "rollup-plugin-json": "^4.0.0", + "rollup-plugin-node-resolve": "^5.0.1", + "xmlhttprequest": "^1.8.0" + } + }, + "node_modules/@ava/babel-plugin-throws-helper": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@ava/babel-plugin-throws-helper/-/babel-plugin-throws-helper-3.0.0.tgz", + "integrity": "sha512-mN9UolOs4WX09QkheU1ELkVy2WPnwonlO3XMdN8JF8fQqRVgVTR21xDbvEOUsbwz6Zwjq7ji9yzyjuXqDPalxg==", + "dev": true, + "engines": { + "node": ">=6.12.3 <7 || >=8.9.4 <9 || >=10.0.0" + } + }, + "node_modules/@ava/babel-preset-stage-4": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@ava/babel-preset-stage-4/-/babel-preset-stage-4-3.0.0.tgz", + "integrity": "sha512-uI5UBx++UsckkfnbF0HH6jvTIvM4r/Kxt1ROO2YXKu5H15sScAtxUIAHiUVbPIw24zPqz/PlF3xxlIDuyFzlQw==", + "dev": true, + "dependencies": { + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", + "@babel/plugin-transform-dotall-regex": "^7.4.3", + "@babel/plugin-transform-modules-commonjs": "^7.4.3" + }, + "engines": { + "node": ">=8.9.4 <9 || >=10.0.0" + } + }, + "node_modules/@ava/babel-preset-transform-test-files": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@ava/babel-preset-transform-test-files/-/babel-preset-transform-test-files-5.0.0.tgz", + "integrity": "sha512-rqgyQwkT0+j2JzYP51dOv80u33rzAvjBtXRzUON+7+6u26mjoudRXci2+1s18rat8r4uOlZfbzm114YS6pwmYw==", + "dev": true, + "dependencies": { + "@ava/babel-plugin-throws-helper": "^3.0.0", + "babel-plugin-espower": "^3.0.1" + }, + "engines": { + "node": ">=6.12.3 <7 || >=8.9.4 <9 || >=10.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.0.0" + } + }, + "node_modules/@babel/core": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.5.tgz", + "integrity": "sha512-OvjIh6aqXtlsA8ujtGKfC7LYWksYSX8yQcM8Ay3LuvVeQ63lcOKgoZWVqcpFwkd29aYU9rVx7jxhfhiEDV9MZA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.4", + "@babel/helpers": "^7.4.4", + "@babel/parser": "^7.4.5", + "@babel/template": "^7.4.4", + "@babel/traverse": "^7.4.5", + "@babel/types": "^7.4.4", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.11", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz", + "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.4.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", + "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "dependencies": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.4.4.tgz", + "integrity": "sha512-3Z1yp8TVQf+B4ynN7WoHPKS8EkdTbgAEy0nU0rs/1Kw4pDgmvYH3rz3aI11KgxKCba2cn7N+tqzV1mY2HMN96w==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/template": "^7.4.4", + "@babel/types": "^7.4.4", + "lodash": "^4.17.11" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "node_modules/@babel/helper-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.4.4.tgz", + "integrity": "sha512-Y5nuB/kESmR3tKjU8Nkn1wMGEx1tjJX076HBMeL3XLQCu6vA/YRzuTW0bbb+qRnXvQGn+d6Rx953yffl8vEy7Q==", + "dev": true, + "dependencies": { + "lodash": "^4.17.11" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", + "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-wrap-function": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", + "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.4.4" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", + "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.2.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.4.tgz", + "integrity": "sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==", + "dev": true, + "dependencies": { + "@babel/template": "^7.4.4", + "@babel/traverse": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "node_modules/@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "dependencies": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.5.tgz", + "integrity": "sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", + "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0", + "@babel/plugin-syntax-async-generators": "^7.2.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", + "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz", + "integrity": "sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.5.4" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.4.tgz", + "integrity": "sha512-4sfBOJt58sEo9a2BQXnZq+Q3ZTSAUXyK3E30o36BOGnJ+tvJ6YSxF0PG6kERvbeISgProodWuI9UVG3/FMY6iw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.4.4", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", + "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "node_modules/@babel/traverse": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.5.tgz", + "integrity": "sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.4", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.4.5", + "@babel/types": "^7.4.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + } + }, + "node_modules/@babel/types": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz", + "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "node_modules/@concordance/react": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@concordance/react/-/react-2.0.0.tgz", + "integrity": "sha512-huLSkUuM2/P+U0uy2WwlKuixMsTODD8p4JVQBI4VKeopkiN0C7M3N9XYVawb4M+4spN5RrO/eLhk7KoQX6nsfA==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1" + }, + "engines": { + "node": ">=6.12.3 <7 || >=8.9.4 <9 || >=10.0.0" + } + }, + "node_modules/@concordance/react/node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@koa/cors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-3.0.0.tgz", + "integrity": "sha512-hDp+cXj6vTYSwHRJfiSpnf5dTMv3FmqNKh1or9BPJk4SHOviHnK9GoL9dT0ypt/E+hlkRkZ9edHylcosW3Ghrw==", + "dev": true, + "dependencies": { + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "dev": true, + "dependencies": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "dependencies": { + "defer-to-connect": "^1.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@tweenjs/tween.js": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-17.4.0.tgz", + "integrity": "sha512-J3fzl1F6wvh8KXVVcIuHN12xi1ZDcPA/0Vix+ZcJYwZWVHUwfIqfvzYXXEw7ybeev6477KCTt9fKydU+ajUqcg==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "node_modules/@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "node_modules/@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "dependencies": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz", + "integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==", + "dev": true + }, + "node_modules/@types/resolve": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", + "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abab": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.2.tgz", + "integrity": "sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ==", + "dev": true, + "dependencies": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + } + }, + "node_modules/acorn-jsx": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "node_modules/ansi-align": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", + "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "dev": true, + "dependencies": { + "string-width": "^3.0.0" + } + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escape-sequences": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-4.1.0.tgz", + "integrity": "sha512-dzW9kHxH011uBsidTXd14JXgzye/YLb2LzeKZ4bsgl/Knwx8AtbSFkkGxagdNOoh0DlqHCmfiEjWKBaqjOanVw==", + "dev": true, + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.0.2.tgz", + "integrity": "sha512-rUe9SxpRQlVg4EM8It7JMNWWYHAirTPpbTuvaSKybb5IejNgWB3PGBBX9rrPKDx2pM/p3Wh+7+ASaWRyyAbxmQ==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "node_modules/append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, + "dependencies": { + "default-require-extensions": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true + }, + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array-uniq": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-2.1.0.tgz", + "integrity": "sha512-bdHxtev7FN6+MXI1YFW0Q8mQ8dTJc2S8AMfju+ZR77pbg2yAdVyDlwkaUI7Har0LyOMRFPHrJ9lYdyjZZswdlQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "node_modules/async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/ava": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ava/-/ava-2.1.0.tgz", + "integrity": "sha512-IaS+l1KfYtFpJlDZXrNG0M6SGr/DUvwJmTiaTaW2lMdEezCAJc5J/hNAQWIKigkmxIOURYT29atD/qyp+zq+wg==", + "dev": true, + "dependencies": { + "@ava/babel-preset-stage-4": "^3.0.0", + "@ava/babel-preset-transform-test-files": "^5.0.0", + "@babel/core": "^7.4.5", + "@babel/generator": "^7.4.4", + "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", + "@concordance/react": "^2.0.0", + "ansi-escapes": "^4.1.0", + "ansi-styles": "^4.0.0", + "arr-flatten": "^1.1.0", + "array-union": "^2.1.0", + "array-uniq": "^2.1.0", + "arrify": "^2.0.1", + "bluebird": "^3.5.5", + "chalk": "^2.4.2", + "chokidar": "^3.0.1", + "chunkd": "^1.0.0", + "ci-parallel-vars": "^1.0.0", + "clean-stack": "^2.1.0", + "clean-yaml-object": "^0.1.0", + "cli-cursor": "^3.0.0", + "cli-truncate": "^1.1.0", + "code-excerpt": "^2.1.1", + "common-path-prefix": "^1.0.0", + "concordance": "^4.0.0", + "convert-source-map": "^1.6.0", + "currently-unhandled": "^0.4.1", + "debug": "^4.1.1", + "del": "^4.1.1", + "dot-prop": "^5.0.1", + "emittery": "^0.4.1", + "empower-core": "^1.2.0", + "equal-length": "^1.0.0", + "escape-string-regexp": "^2.0.0", + "esm": "^3.2.25", + "figures": "^3.0.0", + "find-up": "^4.0.0", + "get-port": "^5.0.0", + "globby": "^9.2.0", + "ignore-by-default": "^1.0.0", + "import-local": "^2.0.0", + "indent-string": "^4.0.0", + "is-ci": "^2.0.0", + "is-error": "^2.2.2", + "is-observable": "^2.0.0", + "is-plain-object": "^3.0.0", + "is-promise": "^2.1.0", + "lodash": "^4.17.11", + "loud-rejection": "^2.1.0", + "make-dir": "^3.0.0", + "matcher": "^2.0.0", + "md5-hex": "^3.0.0", + "meow": "^5.0.0", + "micromatch": "^4.0.2", + "ms": "^2.1.2", + "observable-to-promise": "^1.0.0", + "ora": "^3.4.0", + "package-hash": "^4.0.0", + "pkg-conf": "^3.1.0", + "plur": "^3.1.1", + "pretty-ms": "^5.0.0", + "require-precompiled": "^0.1.0", + "resolve-cwd": "^3.0.0", + "slash": "^3.0.0", + "source-map-support": "^0.5.12", + "stack-utils": "^1.0.2", + "strip-ansi": "^5.2.0", + "strip-bom-buf": "^2.0.0", + "supertap": "^1.0.0", + "supports-color": "^6.1.0", + "trim-off-newlines": "^1.0.1", + "trim-right": "^1.0.1", + "unique-temp-dir": "^1.0.0", + "update-notifier": "^3.0.0", + "write-file-atomic": "^3.0.0" + }, + "bin": { + "ava": "cli.js" + }, + "engines": { + "node": ">=8.9.4 <9 || >=10.0.0" + } + }, + "node_modules/ava/node_modules/ansi-escapes": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.2.0.tgz", + "integrity": "sha512-0+VX4uhi8m3aNbzoqKmkAVOEj6uQzcUHXoFPkKjhZPTpGRUBqVh930KbB6PS4zIyDZccphlLIYlu8nsjFzkXwg==", + "dev": true, + "dependencies": { + "type-fest": "^0.5.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ava/node_modules/ansi-styles": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.0.0.tgz", + "integrity": "sha512-8zjUtFJ3db/QoPXuuEMloS2AUf79/yeyttJ7Abr3hteopJu9HK8vsgGviGUMq+zyA6cZZO6gAyZoMTF6TgaEjA==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ava/node_modules/bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", + "dev": true + }, + "node_modules/ava/node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ava/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ava/node_modules/color-convert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.0.tgz", + "integrity": "sha512-hzTicsCJIHdxih9+2aLR1tNGZX5qSJGRHDPVwSY26tVrEf55XNajLOBWz2UuWSIergszA09/bqnOiHyqx9fxQg==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ava/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ava/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ava/node_modules/figures": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.0.0.tgz", + "integrity": "sha512-HKri+WoWoUgr83pehn/SIgLOMZ9nAWC6dcGj26RY2R4F50u4+RTUz0RCrUlOV3nKRAICW1UGzyb+kcX2qK1S/g==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ava/node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/ava/node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ava/node_modules/find-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.0.0.tgz", + "integrity": "sha512-zoH7ZWPkRdgwYCDVoQTzqjG8JSPANhtvLhh4KVUHyKnaUJJrNeFmWIkTcNuJmR3GLMEmGYEf2S2bjgx26JTF+Q==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ava/node_modules/hasha": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.0.0.tgz", + "integrity": "sha512-PqWdhnQhq6tqD32hZv+l1e5mJHNSudjnaAzgAHfkGiU0ABN6lmbZF8abJIulQHbZ7oiHhP8yL6O910ICMc+5pw==", + "dev": true, + "dependencies": { + "is-stream": "^1.1.0", + "type-fest": "^0.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ava/node_modules/hasha/node_modules/type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ava/node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/ava/node_modules/is-plain-object": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", + "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", + "dev": true, + "dependencies": { + "isobject": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ava/node_modules/isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ava/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ava/node_modules/make-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", + "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ava/node_modules/micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ava/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ava/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/ava/node_modules/onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ava/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ava/node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ava/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ava/node_modules/semver": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", + "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/ava/node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ava/node_modules/write-file-atomic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.0.tgz", + "integrity": "sha512-EIgkf60l2oWsffja2Sf2AL384dx328c0B+cIYPTQq5q2rOYuDV00/iPFBOUiDKKwKMOhkymH8AidPaRvzfxY+Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "node_modules/babel-plugin-espower": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-espower/-/babel-plugin-espower-3.0.1.tgz", + "integrity": "sha512-Ms49U7VIAtQ/TtcqRbD6UBmJBUCSxiC3+zPc+eGqxKUIFO1lTshyEDRUjhoAbd2rWfwYf3cZ62oXozrd8W6J0A==", + "dev": true, + "dependencies": { + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "call-matcher": "^1.0.0", + "core-js": "^2.0.0", + "espower-location-detector": "^1.0.0", + "espurify": "^1.6.0", + "estraverse": "^4.1.1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "node_modules/boxen": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-3.2.0.tgz", + "integrity": "sha512-cU4J/+NodM3IHdSL2yN8bqYqnmlBTidDR4RC7nJs61ZmtGz8VZzM3HLQX0zY5mrSmPtR3xWwsq2jOUQqFZN8+A==", + "dev": true, + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^5.3.1", + "chalk": "^2.4.2", + "cli-boxes": "^2.2.0", + "string-width": "^3.0.0", + "term-size": "^1.2.0", + "type-fest": "^0.3.0", + "widest-line": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/boxen/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/boxen/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/boxen/node_modules/type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/browser-env": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/browser-env/-/browser-env-3.2.6.tgz", + "integrity": "sha512-FJ1dwr5gWEP2Igv1/acvLaMPUTwIqdBJsAL3prDfcjRG9G5/y70WN6M8uCNIyITHC+mMj0W9RkkCz9DFE5s4MQ==", + "dev": true, + "dependencies": { + "window": "4.2.6" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/browser-process-hrtime": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "dev": true + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "node_modules/builtin-modules": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", + "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/byte-size": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-5.0.1.tgz", + "integrity": "sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dev": true, + "dependencies": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/caching-transform": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", + "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", + "dev": true, + "dependencies": { + "hasha": "^3.0.0", + "make-dir": "^2.0.0", + "package-hash": "^3.0.0", + "write-file-atomic": "^2.4.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/call-matcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/call-matcher/-/call-matcher-1.1.0.tgz", + "integrity": "sha512-IoQLeNwwf9KTNbtSA7aEBb1yfDbdnzwjCetjkC8io5oGeOmK2CBNdg0xr+tadRYKO0p7uQyZzvon0kXlZbvGrw==", + "dev": true, + "dependencies": { + "core-js": "^2.0.0", + "deep-equal": "^1.0.0", + "espurify": "^1.6.0", + "estraverse": "^4.0.0" + } + }, + "node_modules/call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "dev": true + }, + "node_modules/call-signature": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/call-signature/-/call-signature-0.0.2.tgz", + "integrity": "sha1-qEq8glpV70yysCi9dOIFpluaSZY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "node_modules/catharsis": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.11.tgz", + "integrity": "sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/chokidar": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.0.1.tgz", + "integrity": "sha512-2ww34sJWehnbpV0Q4k4V5Hh7juo7po6z7LUWkcIQnSGN1lHOL8GGtLtfwabKvLFQw/hbSUQ0u6V7OgGYgBzlkQ==", + "dev": true, + "dependencies": { + "anymatch": "^3.0.1", + "async-each": "^1.0.3", + "braces": "^3.0.2", + "glob-parent": "^5.0.0", + "is-binary-path": "^2.1.0", + "is-glob": "^4.0.1", + "normalize-path": "^3.0.0", + "readdirp": "^3.0.2" + }, + "engines": { + "node": ">= 8" + }, + "optionalDependencies": { + "fsevents": "^2.0.6" + } + }, + "node_modules/chokidar/node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar/node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar/node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/chokidar/node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/chunkd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/chunkd/-/chunkd-1.0.0.tgz", + "integrity": "sha512-xx3Pb5VF9QaqCotolyZ1ywFBgyuJmu6+9dLiqBxgelEse9Xsr3yUlpoX3O4Oh11M00GT2kYMsRByTKIMJW2Lkg==", + "dev": true + }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "node_modules/ci-parallel-vars": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ci-parallel-vars/-/ci-parallel-vars-1.0.0.tgz", + "integrity": "sha512-u6dx20FBXm+apMi+5x7UVm6EH7BL1gc4XrcnQewjcB7HWRcor/V5qWc3RG2HwpgDJ26gIi2DSEu3B7sXynAw/g==", + "dev": true + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-stack": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.1.0.tgz", + "integrity": "sha512-uQWrpRm+iZZUCAp7ZZJQbd4Za9I3AjR/3YTjmcnAtkauaIm/T5CT6U8zVI6e60T6OANqBFAzuR9/HB3NzuZCRA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/clean-yaml-object": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz", + "integrity": "sha1-Y/sRDcLOGoTcIfbZM0h20BCui2g=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cli-boxes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", + "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "dependencies": { + "restore-cursor": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-spinners": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.1.0.tgz", + "integrity": "sha512-8B00fJOEh1HPrx4fo5eW16XmE1PcL1tGpGrxy63CXGP9nHdPBN63X75hA1zhvQuhVztJWLqV58Roj2qlNM7cAA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-truncate": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-1.1.0.tgz", + "integrity": "sha512-bAtZo0u82gCfaAGfSNxUdTI9mNyza7D8w4CVCcaOsy7sgwDzvx6ekr6cuWJqY3UGzgnQ1+4wgENup5eIhgxEYA==", + "dev": true, + "dependencies": { + "slice-ansi": "^1.0.0", + "string-width": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "node_modules/cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "dependencies": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + } + }, + "node_modules/clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "node_modules/cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/co-body": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.0.0.tgz", + "integrity": "sha512-9ZIcixguuuKIptnY8yemEOuhb71L/lLf+Rl5JfJEUiDNJk0e02MBt7BPxR2GEh5mw8dPthQYR4jPI/BnS1MQgw==", + "dev": true, + "dependencies": { + "inflation": "^2.0.0", + "qs": "^6.5.2", + "raw-body": "^2.3.3", + "type-is": "^1.6.16" + } + }, + "node_modules/co-body/node_modules/qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/code-excerpt": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-2.1.1.tgz", + "integrity": "sha512-tJLhH3EpFm/1x7heIW0hemXJTUU5EWl2V0EIX558jp05Mt1U6DVryCgkp3l37cxqs+DNbNgxG43SkwJXpQ14Jw==", + "dev": true, + "dependencies": { + "convert-to-spaces": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/command-line-args": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.1.tgz", + "integrity": "sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg==", + "dev": true, + "dependencies": { + "array-back": "^3.0.1", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/command-line-args/node_modules/typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/command-line-usage": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-5.0.5.tgz", + "integrity": "sha512-d8NrGylA5oCXSbGoKz05FkehDAzSmIm4K03S5VDh4d5lZAtTWfc3D1RuETtuQCn8129nYfJfDdF7P/lwcz1BlA==", + "dev": true, + "dependencies": { + "array-back": "^2.0.0", + "chalk": "^2.4.1", + "table-layout": "^0.4.3", + "typical": "^2.6.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/command-line-usage/node_modules/array-back": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", + "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", + "dev": true, + "dependencies": { + "typical": "^2.6.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/command-line-usage/node_modules/typical": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", + "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true, + "optional": true + }, + "node_modules/common-log-format": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/common-log-format/-/common-log-format-0.1.4.tgz", + "integrity": "sha512-BXcgq+wzr2htmBmnT7cL7YHzPAWketWbr4kozjoM9kWe4sk3+zMgjcH0HO+EddjDlEw2LZysqLpVRwbF318tDw==", + "dev": true, + "bin": { + "clf": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0 <9.0.0" + } + }, + "node_modules/common-path-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-1.0.0.tgz", + "integrity": "sha1-zVL28HEuC6q5fW+XModPIvR3UsA=", + "dev": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", + "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.40.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/concordance": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/concordance/-/concordance-4.0.0.tgz", + "integrity": "sha512-l0RFuB8RLfCS0Pt2Id39/oCPykE01pyxgAFypWTlaGRgvLkZrtczZ8atEHpTeEIW+zYWXTBuA9cCSeEOScxReQ==", + "dev": true, + "dependencies": { + "date-time": "^2.1.0", + "esutils": "^2.0.2", + "fast-diff": "^1.1.2", + "js-string-escape": "^1.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.flattendeep": "^4.4.0", + "lodash.islength": "^4.0.1", + "lodash.merge": "^4.6.1", + "md5-hex": "^2.0.0", + "semver": "^5.5.1", + "well-known-symbols": "^2.0.0" + }, + "engines": { + "node": ">=6.12.3 <7 || >=8.9.4 <9 || >=10.0.0" + } + }, + "node_modules/concordance/node_modules/md5-hex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-2.0.0.tgz", + "integrity": "sha1-0FiOnxx0lUSS7NJKwKxs6ZfZLjM=", + "dev": true, + "dependencies": { + "md5-o-matic": "^0.1.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/concurrently": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-4.1.0.tgz", + "integrity": "sha512-pwzXCE7qtOB346LyO9eFWpkFJVO3JQZ/qU/feGeaAHiX1M3Rw3zgXKc5cZ8vSH5DGygkjzLFDzA/pwoQDkRNGg==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "date-fns": "^1.23.0", + "lodash": "^4.17.10", + "read-pkg": "^4.0.1", + "rxjs": "^6.3.3", + "spawn-command": "^0.0.2-1", + "supports-color": "^4.5.0", + "tree-kill": "^1.1.0", + "yargs": "^12.0.1" + }, + "bin": { + "concurrently": "bin/concurrently.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/concurrently/node_modules/has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concurrently/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/concurrently/node_modules/read-pkg": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", + "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", + "dev": true, + "dependencies": { + "normalize-package-data": "^2.3.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "dependencies": { + "has-flag": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/configstore": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-4.0.0.tgz", + "integrity": "sha512-CmquAXFBocrzaSM8mtGPMM/HiWmyIpr4CcJl/rgY2uCObZ/S7cKU0silxslqJejl+t/T9HS8E0PUNQD81JGUEQ==", + "dev": true, + "dependencies": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/configstore/node_modules/dot-prop": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz", + "integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==", + "dev": true, + "dependencies": { + "is-obj": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/configstore/node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/configstore/node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/configstore/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/convert-to-spaces": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-1.0.2.tgz", + "integrity": "sha1-fj5Iu+bZl7FBfdyihoIEtNPYVxU=", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/cookies": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.3.tgz", + "integrity": "sha512-+gixgxYSgQLTaTIilDHAdlNPZDENDQernEMiIcZpYYP14zgHsCt4Ce1FEjFtcp6GefhozebB6orvhAAWx/IS0A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "keygrip": "~1.0.3" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-to": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/copy-to/-/copy-to-2.0.1.tgz", + "integrity": "sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU=", + "dev": true + }, + "node_modules/core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", + "deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true, + "hasInstallScript": true + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/coveralls": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.4.tgz", + "integrity": "sha512-eyqUWA/7RT0JagiL0tThVhjbIjoiEUyWCjtUJoOPcWoeofP5WK/jb2OJYoBFrR6DvplR+AxOyuBqk4JHkk5ykA==", + "dev": true, + "dependencies": { + "growl": "~> 1.10.0", + "js-yaml": "^3.11.0", + "lcov-parse": "^0.0.10", + "log-driver": "^1.2.7", + "minimist": "^1.2.0", + "request": "^2.86.0" + }, + "bin": { + "coveralls": "bin/coveralls.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/cp-file": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", + "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "make-dir": "^2.0.0", + "nested-error-stacks": "^2.0.0", + "pify": "^4.0.1", + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/create-mixin": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/create-mixin/-/create-mixin-2.0.1.tgz", + "integrity": "sha512-r11aTk2z5x2C9ZI85oxuZ0EzgjBH0BdGacnXeuA+9d59xV2Hsy+ZpRmU+LUjJT9KdnLgzaxZoCbqrJ5qjl0bAA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssom": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", + "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.2.tgz", + "integrity": "sha512-43wY3kl1CVQSvL7wUY1qXkxVGkStjpkDmVjiIKX8R97uhajy8Bybay78uOtqvh7Q5GK75dNPfW0geWjE6qQQow==", + "dev": true, + "dependencies": { + "cssom": "0.3.x" + } + }, + "node_modules/currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "dependencies": { + "array-find-index": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + } + }, + "node_modules/date-fns": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", + "dev": true + }, + "node_modules/date-time": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/date-time/-/date-time-2.1.0.tgz", + "integrity": "sha512-/9+C44X7lot0IeiyfgJmETtRMhBidBYM2QFFIkGa0U1k+hSyY87Nw7PY3eDqpvCBm7I3WCSfPeZskW/YYq6m4g==", + "dev": true, + "dependencies": { + "time-zone": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "node_modules/default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "dependencies": { + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + } + }, + "node_modules/defer-to-connect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.0.2.tgz", + "integrity": "sha512-k09hcQcTDY+cwgiwa6PYKLm3jlagNzQ+RSvhjzESOGOx+MNOuXkxTfEvPrO1IOQ81tArCFYQgi631clB70RpQw==", + "dev": true + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "dependencies": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/del/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/globby/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "node_modules/dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "dependencies": { + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/docdash": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/docdash/-/docdash-1.1.1.tgz", + "integrity": "sha512-WQkkr01zL6kcIfq9YCSXtqqevM6NYoTXLdl+Td0OYCEcX0RgsuEMeqHXQaXFt+p6Lo15RIgA5XhLAn7RL+erhA==", + "dev": true + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "dependencies": { + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ecstatic": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz", + "integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==", + "deprecated": "This package is unmaintained and deprecated. See the GH Issue 259.", + "dev": true, + "dependencies": { + "he": "^1.1.1", + "mime": "^1.6.0", + "minimist": "^1.1.0", + "url-join": "^2.0.5" + }, + "bin": { + "ecstatic": "lib/ecstatic.js" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "node_modules/emittery": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.4.1.tgz", + "integrity": "sha512-r4eRSeStEGf6M5SKdrQhhLK5bOwOBxQhIE3YSTnZE3GpKiLfnnhE+tPtrJE79+eDJgm39BM6LSoI8SCx4HbwlQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/empower-core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/empower-core/-/empower-core-1.2.0.tgz", + "integrity": "sha512-g6+K6Geyc1o6FdXs9HwrXleCFan7d66G5xSCfSF7x1mJDCes6t0om9lFQG3zOrzh3Bkb/45N0cZ5Gqsf7YrzGQ==", + "dev": true, + "dependencies": { + "call-signature": "0.0.2", + "core-js": "^2.0.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", + "dev": true + }, + "node_modules/equal-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/equal-length/-/equal-length-1.0.1.tgz", + "integrity": "sha1-IcoRLUirJLTh5//A5TOdMf38J0w=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-inject": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/error-inject/-/error-inject-1.0.0.tgz", + "integrity": "sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=", + "dev": true + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", + "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", + "dev": true, + "dependencies": { + "esprima": "^3.1.3", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", + "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.3", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.2.2", + "js-yaml": "^3.13.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^6.14.0 || ^8.10.0 || >=9.10.0" + } + }, + "node_modules/eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz", + "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/espower-location-detector": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/espower-location-detector/-/espower-location-detector-1.0.0.tgz", + "integrity": "sha1-oXt+zFnTDheeK+9z+0E3cEyzMbU=", + "dev": true, + "dependencies": { + "is-url": "^1.2.1", + "path-is-absolute": "^1.0.0", + "source-map": "^0.5.0", + "xtend": "^4.0.0" + } + }, + "node_modules/espree": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "dev": true, + "dependencies": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/espurify": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/espurify/-/espurify-1.8.1.tgz", + "integrity": "sha512-ZDko6eY/o+D/gHCWyHTU85mKDgYcS4FJj7S+YD6WIInm7GQ6AnOjmcL4+buFV/JOztVLELi/7MmuGU5NHta0Mg==", + "dev": true, + "dependencies": { + "core-js": "^2.0.0" + } + }, + "node_modules/esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "dependencies": { + "estraverse": "^4.0.0" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "dependencies": { + "estraverse": "^4.1.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/estree-walker": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.0.tgz", + "integrity": "sha512-peq1RfVAVzr3PU/jL31RaOjUKLoZJpObQWJJ+LgfcxDUifyLZ1RjPQZTl0pzj2uJ45b7A7XpyppXvxdEqzo4rw==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", + "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", + "dev": true, + "dependencies": { + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.1.2", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.3", + "micromatch": "^3.1.10" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "dependencies": { + "flat-cache": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dev": true, + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "dependencies": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/flatted": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz", + "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz", + "integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/foreground-child": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", + "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", + "dev": true, + "dependencies": { + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + } + }, + "node_modules/foreground-child/node_modules/cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.7.tgz", + "integrity": "sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "node_modules/get-port": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.0.0.tgz", + "integrity": "sha512-imzMU0FjsZqNa6BqOjbbW6w5BivHIuQKopjpPqcnx0AVHJQKCxK1O+Ab3OrVXhrekqfVMjwA9ZYu062R+KcIsQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/get-port/node_modules/type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glob-parent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", + "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", + "dev": true + }, + "node_modules/global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "dependencies": { + "ini": "^1.3.4" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", + "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", + "dev": true, + "dependencies": { + "@types/glob": "^7.1.1", + "array-union": "^1.0.2", + "dir-glob": "^2.2.2", + "fast-glob": "^2.2.6", + "glob": "^7.1.3", + "ignore": "^4.0.3", + "pify": "^4.0.1", + "slash": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/globby/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/globby/node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/globby/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/google-closure-compiler": { + "version": "20190528.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20190528.0.0.tgz", + "integrity": "sha512-/Eq5s7SAKzdG00tbdAxUlG7jH8nqhG0sYWZdDu39xkl+rcYDA2f8+MPM55ZgX0Ixh1+jg7Z6ohTeWm0aIqQAsA==", + "dev": true, + "dependencies": { + "chalk": "^1.0.0", + "google-closure-compiler-java": "^20190528.0.0", + "google-closure-compiler-js": "^20190528.0.0", + "minimist": "^1.2.0", + "vinyl": "^2.0.1", + "vinyl-sourcemaps-apply": "^0.2.0" + }, + "bin": { + "google-closure-compiler": "cli.js" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "google-closure-compiler-linux": "^20190528.0.0", + "google-closure-compiler-osx": "^20190528.0.0" + } + }, + "node_modules/google-closure-compiler-java": { + "version": "20190528.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20190528.0.0.tgz", + "integrity": "sha512-p3fmyM7ls+GebeEkUbN4jmvOTCrr00zGjhhCGIxo609JS36NA8pv6zWFptUt6ShqMfjn3Wc7LjRiGfJcUeyIdQ==", + "dev": true + }, + "node_modules/google-closure-compiler-js": { + "version": "20190528.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-js/-/google-closure-compiler-js-20190528.0.0.tgz", + "integrity": "sha512-r6Wgu+ZYU/7CpZaVtzan2SvXeCRuP64KcwQqGmJajA/3NDmbhOXzfvhzmbD3KP4wcgq1pBVKIQu7JTbWvFiWMA==", + "dev": true + }, + "node_modules/google-closure-compiler-linux": { + "version": "20190528.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20190528.0.0.tgz", + "integrity": "sha512-K4NGkwBjuh2crnHqV7IQZ/qJKo5PDNPIvl8Eo4GPodxSryLW84C5Gq+UZnZabvoHupO3YzB7nHgZYih4IIElhQ==", + "cpu": [ + "x64", + "x86" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/google-closure-compiler-osx": { + "version": "20190528.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20190528.0.0.tgz", + "integrity": "sha512-56N3VlGlghE3e5wACPMBthvmPR2GaQPh0y5Y6otD4qEBB3Ic8V0Ebz1cUDh/qXav32PuCh29ieVrOMx8XJv/nw==", + "cpu": [ + "x64", + "x86" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/google-closure-compiler/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/google-closure-compiler/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/google-closure-compiler/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/google-closure-compiler/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/google-closure-compiler/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/got/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "deprecated": "this library is no longer supported", + "dev": true, + "dependencies": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasha": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", + "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", + "dev": true, + "dependencies": { + "is-stream": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "node_modules/html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^1.0.1" + } + }, + "node_modules/http-assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz", + "integrity": "sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==", + "dev": true, + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.7.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz", + "integrity": "sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew==", + "dev": true + }, + "node_modules/http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-server": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz", + "integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==", + "dev": true, + "dependencies": { + "basic-auth": "^1.0.3", + "colors": "^1.4.0", + "corser": "^2.0.1", + "ecstatic": "^3.3.2", + "http-proxy": "^1.18.0", + "minimist": "^1.2.5", + "opener": "^1.5.1", + "portfinder": "^1.0.25", + "secure-compare": "3.0.1", + "union": "~0.5.0" + }, + "bin": { + "hs": "bin/http-server", + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/http-server/node_modules/basic-auth": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", + "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", + "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "dependencies": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/import-local/node_modules/resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "dependencies": { + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflation": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.0.0.tgz", + "integrity": "sha1-i0F+R8KPklpFEz2RTKH9OJEH8w8=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", + "dev": true + }, + "node_modules/inquirer": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz", + "integrity": "sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==", + "dev": true, + "dependencies": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.11", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/irregular-plurals": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-2.0.0.tgz", + "integrity": "sha512-Y75zBYLkh0lJ9qxeHlMjQ7bSbyiSqNW/UOPWDmzC7cXskL1hekSITh1Oc6JV0XCWWZ9DE8VYSB71xocLk3gmGw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-error": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-error/-/is-error-2.2.2.tgz", + "integrity": "sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg==", + "dev": true + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "dev": true, + "dependencies": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-installed-globally/node_modules/is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "dependencies": { + "path-is-inside": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, + "node_modules/is-npm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-3.0.0.tgz", + "integrity": "sha512-wsigDr1Kkschp2opC4G3yA6r9EgVA6NjRpWzIi9axXqeIaAATPRJc4uLujXe3Nd9uO8KoDyA4MD6aZSeXTADhA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-observable": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-2.0.0.tgz", + "integrity": "sha512-fhBZv3eFKUbyHXZ1oHujdo2tZ+CNbdpdzzlENgCGZUC8keoGxUew2jYFLYcUB4qo7LDD03o4KK11m/QYD7kEjg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.1.0.tgz", + "integrity": "sha512-Sc5j3/YnM8tDeyCsVeKlm/0p95075DyLmDEIkSgQ7mXkrOX+uTCtmQFm0CYzVyJwcCCmO3k8qfJt17SxQwB5Zw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "dependencies": { + "is-path-inside": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "dependencies": { + "path-is-inside": "^1.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "node_modules/is-reference": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.2.tgz", + "integrity": "sha512-Kn5g8c7XHKejFOpTf2QN9YjiHHKl5xRj+2uAZf9iM2//nkBNi/NNeB5JMoun28nEaUVHyPUzqzhfRlfAirEjXg==", + "dev": true, + "dependencies": { + "@types/estree": "0.0.39" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", + "dev": true + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", + "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", + "dev": true, + "dependencies": { + "append-transform": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "dependencies": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", + "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", + "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", + "dev": true, + "dependencies": { + "handlebars": "^4.1.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/js-string-escape": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", + "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js2xmlparser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz", + "integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==", + "dev": true, + "dependencies": { + "xmlcreate": "^2.0.3" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "node_modules/jsdoc": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.6.tgz", + "integrity": "sha512-znR99e1BHeyEkSvgDDpX0sTiTu+8aQyDl9DawrkOGZTTW8hv0deIFXx87114zJ7gRaDZKVQD/4tr1ifmJp9xhQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.9.4", + "bluebird": "^3.7.2", + "catharsis": "^0.8.11", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.1", + "klaw": "^3.0.0", + "markdown-it": "^10.0.0", + "markdown-it-anchor": "^5.2.7", + "marked": "^0.8.2", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.10.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=8.15.0" + } + }, + "node_modules/jsdoc/node_modules/@babel/parser": { + "version": "7.12.16", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.16.tgz", + "integrity": "sha512-c/+u9cqV6F0+4Hpq01jnJO+GLp2DdT63ppz9Xa+6cHaajM9VFzK/iDXiKK65YtpeVwu+ctfS6iqlMqRgQRzeCw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/jsdoc/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jsdoc/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsdoc/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jsdom": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-13.2.0.tgz", + "integrity": "sha512-cG1NtMWO9hWpqRNRR3dSvEQa8bFI6iLlqU2x4kwX51FQjp0qus8T9aBaAO6iGp3DeBrhdwuKxckknohkmfvsFw==", + "dev": true, + "dependencies": { + "abab": "^2.0.0", + "acorn": "^6.0.4", + "acorn-globals": "^4.3.0", + "array-equal": "^1.0.0", + "cssom": "^0.3.4", + "cssstyle": "^1.1.1", + "data-urls": "^1.1.0", + "domexception": "^1.0.1", + "escodegen": "^1.11.0", + "html-encoding-sniffer": "^1.0.2", + "nwsapi": "^2.0.9", + "parse5": "5.1.0", + "pn": "^1.1.0", + "request": "^2.88.0", + "request-promise-native": "^1.0.5", + "saxes": "^3.1.5", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.5.0", + "w3c-hr-time": "^1.0.1", + "w3c-xmlserializer": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^7.0.0", + "ws": "^6.1.2", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jsdom/node_modules/ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "dev": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "node_modules/json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/keygrip": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.3.tgz", + "integrity": "sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/koa": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.7.0.tgz", + "integrity": "sha512-7ojD05s2Q+hFudF8tDLZ1CpCdVZw8JQELWSkcfG9bdtoTDzMmkRF6BQBU7JzIzCCOY3xd3tftiy/loHBUYaY2Q==", + "dev": true, + "dependencies": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.7.1", + "debug": "~3.1.0", + "delegates": "^1.0.0", + "depd": "^1.1.2", + "destroy": "^1.0.4", + "error-inject": "^1.0.0", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^1.2.0", + "koa-is-json": "^1.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "engines": { + "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + } + }, + "node_modules/koa-bodyparser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/koa-bodyparser/-/koa-bodyparser-4.2.1.tgz", + "integrity": "sha512-UIjPAlMZfNYDDe+4zBaOAUKYqkwAGcIU6r2ARf1UOXPAlfennQys5IiShaVeNf7KkVBlf88f2LeLvBFvKylttw==", + "dev": true, + "dependencies": { + "co-body": "^6.0.0", + "copy-to": "^2.0.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true + }, + "node_modules/koa-compress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/koa-compress/-/koa-compress-3.0.0.tgz", + "integrity": "sha512-xol+LkNB1mozKJkB5Kj6nYXbJXhkLkZlXl9BsGBPjujVfZ8MsIXwU4GHRTT7TlSfUcl2DU3JtC+j6wOWcovfuQ==", + "dev": true, + "dependencies": { + "bytes": "^3.0.0", + "compressible": "^2.0.0", + "koa-is-json": "^1.0.0", + "statuses": "^1.0.0" + } + }, + "node_modules/koa-conditional-get": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-conditional-get/-/koa-conditional-get-2.0.0.tgz", + "integrity": "sha1-pD83I8HQFLcwo07Oit8wuTyCM/I=", + "dev": true + }, + "node_modules/koa-convert": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", + "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", + "dev": true, + "dependencies": { + "co": "^4.6.0", + "koa-compose": "^3.0.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/koa-convert/node_modules/koa-compose": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", + "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", + "dev": true, + "dependencies": { + "any-promise": "^1.1.0" + } + }, + "node_modules/koa-etag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/koa-etag/-/koa-etag-3.0.0.tgz", + "integrity": "sha1-nvc4Ld1agqsN6xU0FckVg293HT8=", + "dev": true, + "dependencies": { + "etag": "^1.3.0", + "mz": "^2.1.0" + } + }, + "node_modules/koa-is-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/koa-is-json/-/koa-is-json-1.0.0.tgz", + "integrity": "sha1-JzwH7c3Ljfaiwat9We52SRRR7BQ=", + "dev": true + }, + "node_modules/koa-json": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/koa-json/-/koa-json-2.0.2.tgz", + "integrity": "sha1-Nq8U5uofXWRtfESihXAcb4Wk/eQ=", + "dev": true, + "dependencies": { + "koa-is-json": "1", + "streaming-json-stringify": "3" + } + }, + "node_modules/koa-morgan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/koa-morgan/-/koa-morgan-1.0.1.tgz", + "integrity": "sha1-CAUuDODYOdPEMXi5CluzQkvvH5k=", + "dev": true, + "dependencies": { + "morgan": "^1.6.1" + } + }, + "node_modules/koa-range": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/koa-range/-/koa-range-0.3.0.tgz", + "integrity": "sha1-NYjjSWRzqDmhvSZNKkKx2FvX/qw=", + "dev": true, + "dependencies": { + "stream-slice": "^0.1.2" + }, + "engines": { + "node": ">=7" + } + }, + "node_modules/koa-rewrite-75lb": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/koa-rewrite-75lb/-/koa-rewrite-75lb-3.0.1.tgz", + "integrity": "sha512-4sTmiYbUheh4X0xSPEcxSiNYkDbHiR62zCcibmISfMhtyjoLaqjvN99KnIA825Tw7HgN47Yta7dQA5h6Koom6A==", + "dev": true, + "dependencies": { + "path-to-regexp": "^2.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/koa-rewrite-75lb/node_modules/path-to-regexp": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz", + "integrity": "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==", + "dev": true + }, + "node_modules/koa-route": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/koa-route/-/koa-route-3.2.0.tgz", + "integrity": "sha1-dimLmaa8+p44yrb+XHmocz51i84=", + "dev": true, + "dependencies": { + "debug": "*", + "methods": "~1.1.0", + "path-to-regexp": "^1.2.0" + } + }, + "node_modules/koa-route/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/koa-route/node_modules/path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/koa-send": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.0.tgz", + "integrity": "sha512-90ZotV7t0p3uN9sRwW2D484rAaKIsD8tAVtypw/aBU+ryfV+fR2xrcAwhI8Wl6WRkojLUs/cB9SBSCuIb+IanQ==", + "dev": true, + "dependencies": { + "debug": "^3.1.0", + "http-errors": "^1.6.3", + "mz": "^2.7.0", + "resolve-path": "^1.4.0" + }, + "engines": { + "node": ">= 7.6.0" + } + }, + "node_modules/koa-send/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/koa-static": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", + "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", + "dev": true, + "dependencies": { + "debug": "^3.1.0", + "koa-send": "^5.0.0" + }, + "engines": { + "node": ">= 7.6.0" + } + }, + "node_modules/koa-static/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/koa/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/koa/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dev": true, + "dependencies": { + "package-json": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "dependencies": { + "invert-kv": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lcov-parse": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", + "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=", + "dev": true + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/load-module/-/load-module-2.0.1.tgz", + "integrity": "sha512-uPi6sDp/7rcX5hP8jRncyBa63++qOJxczYAoq58DpoAnQmLJ+e7zz8qmRS+zfkOGEhqj10SFwuZrZuen8Ep5zQ==", + "dev": true, + "dependencies": { + "array-back": "^3.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/local-web-server": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/local-web-server/-/local-web-server-3.0.0.tgz", + "integrity": "sha512-Kh6hopvUriCVusLN6zd1aA4YkbrNJb+PEEQjzH392XOyJcv8BukIQhwKf8knUObE85qhDY7ageTzsadYxYwxXQ==", + "dev": true, + "dependencies": { + "lws": "2.0.0", + "lws-basic-auth": "^1.0.3", + "lws-blacklist": "^2.0.2", + "lws-body-parser": "^1.0.2", + "lws-compress": "^1.0.2", + "lws-conditional-get": "^1.0.1", + "lws-cors": "^2.0.1", + "lws-index": "^1.0.4", + "lws-json": "^1.0.1", + "lws-log": "^1.0.2", + "lws-mime": "^1.0.1", + "lws-range": "^2.0.1", + "lws-request-monitor": "^1.0.4", + "lws-rewrite": "^2.0.2", + "lws-spa": "^2.0.1", + "lws-static": "^1.1.2", + "node-version-matches": "^1.0.1" + }, + "bin": { + "ws": "bin/cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "dev": true + }, + "node_modules/lodash.assignwith": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz", + "integrity": "sha1-EnqX8CrcQXUalU0ksN4X4QDgOOs=", + "dev": true + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "node_modules/lodash.islength": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.islength/-/lodash.islength-4.0.1.tgz", + "integrity": "sha1-Tpho1FJXXXUK/9NYyXlUPcIO1Xc=", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.padend": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz", + "integrity": "sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=", + "dev": true + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=", + "dev": true + }, + "node_modules/log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true, + "engines": { + "node": ">=0.8.6" + } + }, + "node_modules/log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "dependencies": { + "chalk": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/loud-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-2.1.0.tgz", + "integrity": "sha512-g/6MQxUXYHeVqZ4PGpPL1fS1fOvlXoi7bay0pizmjAd/3JhyXwxzwrnr74yzdmhuerlslbRJ3x7IOXzFz0cE5w==", + "dev": true, + "dependencies": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/lws": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws/-/lws-2.0.0.tgz", + "integrity": "sha512-OgAJ7Od6nteHv1p8QQPc28xFNZrPLBkyOu8lHsVH0Eo4qYPiY/401LHnIKwJEAQZAV8YF0lSL7/mtSxAM+mAVg==", + "dev": true, + "dependencies": { + "ansi-escape-sequences": "^4.1.0", + "array-back": "^3.1.0", + "byte-size": "^5.0.1", + "command-line-args": "^5.1.1", + "command-line-usage": "^5.0.5", + "create-mixin": "^2.0.1", + "koa": "^2.7.0", + "load-module": "^2.0.1", + "lodash.assignwith": "^4.2.0", + "node-version-matches": "^1.0.1", + "open": "^6.3.0", + "reduce-flatten": "^2.0.0", + "typical": "^5.0.0", + "walk-back": "^3.0.1" + }, + "bin": { + "lws": "bin/cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lws-basic-auth": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lws-basic-auth/-/lws-basic-auth-1.0.3.tgz", + "integrity": "sha512-YMVsYyIcmEgAyjuQuU+rVtrs1vmrc6U5Nqrj0G/A++h96bJVfgxu0p9Qj1ZmeZplya3Jx8TefzppAfuzxebZxw==", + "dev": true, + "dependencies": { + "basic-auth": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lws-blacklist": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lws-blacklist/-/lws-blacklist-2.0.2.tgz", + "integrity": "sha512-7W2z6epqC+V5FSLN9Cblezgqem8jdGvIyIlhrbNfV+Sq818xHU9h5+Bh2rfQmNUKYp59TX+7K3Gp73kGLo37bA==", + "dev": true, + "dependencies": { + "array-back": "^3.1.0", + "path-to-regexp": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lws-body-parser": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lws-body-parser/-/lws-body-parser-1.0.2.tgz", + "integrity": "sha512-Tm/gtNgQQdi6nr1FgD0PLDWohBdEXqXAPI62j/7JbGsQTcR7NBbSYmk1hrCdMKMcZ8BNZEPEDUjOA6v19YceWA==", + "dev": true, + "dependencies": { + "koa-bodyparser": "^4.2.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lws-compress": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lws-compress/-/lws-compress-1.0.2.tgz", + "integrity": "sha512-bQWfg6RopS4UEu9pXU8yCHxJa40do1NM3399oeAz9klMv94h+SAbjqpf1i5ZgEou5p5psiE6LYaGokHdJy/yew==", + "dev": true, + "dependencies": { + "koa-compress": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lws-conditional-get": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lws-conditional-get/-/lws-conditional-get-1.0.1.tgz", + "integrity": "sha512-PK6JOnrkGTlyqaq8mdxKfBVb8begapwk1i9jNvtWqs1FZj0ElLfDhF+57YR1r0aD/CX64RoaD5UHsGb84rQqTw==", + "dev": true, + "dependencies": { + "koa-conditional-get": "^2.0.0", + "koa-etag": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lws-cors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lws-cors/-/lws-cors-2.0.1.tgz", + "integrity": "sha512-FkGoqQrFETdyDYqPhuJufR7131J014QsyHVtDbBs17kMtspzcIrS2BOtO/NIShgs5ybGZ7S/CjuBNuy/0+kV1w==", + "dev": true, + "dependencies": { + "@koa/cors": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lws-index": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lws-index/-/lws-index-1.0.4.tgz", + "integrity": "sha512-DmfnyesC4M43bdO3Jr5fjye7qYaq6kGoOBfWJu64llHxHnOpvowShEtoJeUEEzUf4Myjzzl+xO2IzYqFmpqa4w==", + "dev": true, + "dependencies": { + "serve-index": "^1.9.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lws-json": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lws-json/-/lws-json-1.0.1.tgz", + "integrity": "sha512-uP2YTPymDT2aJW7GBWjXKdfIqkQuUWz3T4lNw8U8AEbyR6sIg8J5nSO9PneT/zqq9KSp3/+nQoFkO31LMeSq5w==", + "dev": true, + "dependencies": { + "koa-json": "^2.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lws-log": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lws-log/-/lws-log-1.0.2.tgz", + "integrity": "sha512-bJVcNiO43nnVtC/+QSnH1noNfBfgXJc98uLwCgofeDieTcQ56fWi+lX3DdXP5yN7p8J4ZrKVFBraA93TOF+p1g==", + "dev": true, + "dependencies": { + "koa-morgan": "^1.0.1", + "stream-log-stats": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lws-mime": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lws-mime/-/lws-mime-1.0.1.tgz", + "integrity": "sha512-+4lRjaJmexwmScZAb50A0J4F8vhSyTBOsva8xFgX9yC/AMR7t56asZpgAAw7WUI8rlSbpJQCfh0nkuAhEWgMEA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/lws-range": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lws-range/-/lws-range-2.0.1.tgz", + "integrity": "sha512-W4phkrh4lGtwe4M4Q5xsKBhme69s0wyrYTxoe8K9ZRDQdCFrP5wUGa5+wz/dsOvlPSaBKWxnGluCYhspGid3Gw==", + "dev": true, + "dependencies": { + "koa-range": "^0.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lws-request-monitor": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lws-request-monitor/-/lws-request-monitor-1.0.4.tgz", + "integrity": "sha512-i3rpVmBnd/QIZe20lupZk+jcI7Qxr3zAddU7BR4FHniyY2ygGL3WobeZqKE0hT25p2N7roGbk/ywZn8EIXXBDQ==", + "dev": true, + "dependencies": { + "byte-size": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lws-rewrite": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lws-rewrite/-/lws-rewrite-2.0.3.tgz", + "integrity": "sha512-Kr1dTnkvWOv4kzMdZZWL+Fe84g2z0UsZw5CrRNPqxQ1ee85v+/CFGde2ky8+PLx5jl6NHWpWmqWKAAid6cd5qw==", + "dev": true, + "dependencies": { + "array-back": "^3.1.0", + "koa-rewrite-75lb": "^3.0.1", + "koa-route": "^3.2.0", + "path-to-regexp": "^3.0.0", + "request": "^2.88.0", + "stream-read-all": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lws-spa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lws-spa/-/lws-spa-2.0.1.tgz", + "integrity": "sha512-E2Q76EW5MHD60w3JnFyJXHjJwSpOpFh/+VKLyiB6fFoo5ABSoPQPXYLwotyqewpMBxsPoooHeoc4+jv1tgz/cQ==", + "dev": true, + "dependencies": { + "koa-send": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lws-static": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/lws-static/-/lws-static-1.1.3.tgz", + "integrity": "sha512-0f9kiddam/QZ05y9X+HwNtvdaOppub3vkZ+9lq2Vkt4tZ9IaXydsjBjOkjIQf70dsgFPlVqkC+NysYTXEuO19g==", + "dev": true, + "dependencies": { + "koa-static": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/magic-string": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.2.tgz", + "integrity": "sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.4" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "dependencies": { + "p-defer": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/markdown-it": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", + "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "entities": "~2.0.0", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-anchor": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz", + "integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==", + "dev": true, + "peerDependencies": { + "markdown-it": "*" + } + }, + "node_modules/marked": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz", + "integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==", + "dev": true, + "bin": { + "marked": "bin/marked" + }, + "engines": { + "node": ">= 8.16.2" + } + }, + "node_modules/matcher": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-2.0.0.tgz", + "integrity": "sha512-nlmfSlgHBFx36j/Pl/KQPbIaqE8Zf0TqmSMjsuddHDg6PMSVgmyW9HpkLs0o0M1n2GIZ/S2BZBLIww/xjhiGng==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/matcher/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/md5-hex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.0.tgz", + "integrity": "sha512-uA+EX5IV1r5lKBJecwTSec3k6xl4ziBUZihRiOpOHCeHjKA0ai6+eImamXQy/cI3Qep5mQgFTeJld9tcwdBNFw==", + "dev": true, + "dependencies": { + "md5-o-matic": "^0.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/md5-o-matic": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/md5-o-matic/-/md5-o-matic-0.1.1.tgz", + "integrity": "sha1-givM1l4RfFFPqxdrJZRdVBAKA8M=", + "dev": true + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", + "dev": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "dependencies": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/mem/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/meow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", + "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "dev": true, + "dependencies": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0", + "yargs-parser": "^10.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/meow/node_modules/loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "dependencies": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/merge-source-map/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge2": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz", + "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==", + "dev": true, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, + "dependencies": { + "mime-db": "1.40.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/minimist-options/node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/morgan": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", + "dev": true, + "dependencies": { + "basic-auth": "~2.0.0", + "debug": "2.6.9", + "depd": "~1.1.2", + "on-finished": "~2.3.0", + "on-headers": "~1.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "node_modules/mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "node_modules/nested-error-stacks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", + "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", + "dev": true + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/node-version-matches": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/node-version-matches/-/node-version-matches-1.0.1.tgz", + "integrity": "sha512-L1GRq9vkwvJkOdJynEph63N1gNdKRsThm6CkLn+8X9mFzmDv63iEMkZETVkcWVJowpqK72lNVuMZmeenhDoRhw==", + "dev": true, + "dependencies": { + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.3.0.tgz", + "integrity": "sha512-0NLtR71o4k6GLP+mr6Ty34c5GA6CMoEsncKJxvQd8NzPxaHRJNnb5gZE8R1XF4CPIS7QPHLJ74IFszwtNVAHVQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nwsapi": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.4.tgz", + "integrity": "sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw==", + "dev": true + }, + "node_modules/nyc": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", + "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "caching-transform": "^3.0.2", + "convert-source-map": "^1.6.0", + "cp-file": "^6.2.0", + "find-cache-dir": "^2.1.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.5", + "istanbul-lib-hook": "^2.0.7", + "istanbul-lib-instrument": "^3.3.0", + "istanbul-lib-report": "^2.0.8", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^2.2.4", + "js-yaml": "^3.13.1", + "make-dir": "^2.1.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.3", + "signal-exit": "^3.0.2", + "spawn-wrap": "^1.4.2", + "test-exclude": "^5.2.3", + "uuid": "^3.3.2", + "yargs": "^13.2.2", + "yargs-parser": "^13.0.0" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/nyc/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/nyc/node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/nyc/node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/nyc/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nyc/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/nyc/node_modules/yargs": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", + "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", + "dev": true, + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.0" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/observable-to-promise": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/observable-to-promise/-/observable-to-promise-1.0.0.tgz", + "integrity": "sha512-cqnGUrNsE6vdVDTPAX9/WeVzwy/z37vdxupdQXU8vgTXRFH72KCZiZga8aca2ulRPIeem8W3vW9rQHBwfIl2WA==", + "dev": true, + "dependencies": { + "is-observable": "^2.0.0", + "symbol-observable": "^1.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=", + "dev": true + }, + "node_modules/open": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.3.0.tgz", + "integrity": "sha512-6AHdrJxPvAXIowO/aIaeHZ8CeMdDf7qCyRNq8NwJpinmCdXhz+NZR7ie1Too94lpciCDsG+qHGO9Mt0svA4OqA==", + "dev": true, + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", + "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-spinners": "^2.0.0", + "log-symbols": "^2.2.0", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "dependencies": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/os-locale/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/os-locale/node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/os-locale/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz", + "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^3.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.4.0.tgz", + "integrity": "sha512-bd1T8OBG7hcvMd9c/udgv6u5v9wISP3Oyl9Cm7Weop8EFwrtcQDnS2sb6zhwqus2WslSr5wSTIPiTTpxxmPm7Q==", + "dev": true, + "dependencies": { + "got": "^9.6.0", + "registry-auth-token": "^3.4.0", + "registry-url": "^5.0.0", + "semver": "^6.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json/node_modules/semver": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", + "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", + "dev": true + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.0.0.tgz", + "integrity": "sha512-ZOtfhPttCrqp2M1PBBH4X13XlvnfhIwD7yCLx+GoGoXRPQyxGOTdQMpIzPSPKXAJT/JQrdfFrgdJOyAzvgpQ9A==", + "dev": true + }, + "node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-type/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", + "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-conf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz", + "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0", + "load-json-file": "^5.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-conf/node_modules/load-json-file": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", + "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "parse-json": "^4.0.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0", + "type-fest": "^0.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-conf/node_modules/type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/plur": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/plur/-/plur-3.1.1.tgz", + "integrity": "sha512-t1Ax8KUvV3FFII8ltczPn2tJdjqbd1sIzu6t4JL7nQ3EyeL/lTrj5PWKb06ic5/6XYDr65rQ4uzQEGN70/6X5w==", + "dev": true, + "dependencies": { + "irregular-plurals": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true + }, + "node_modules/portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "dev": true, + "dependencies": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pretty-ms": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-5.0.0.tgz", + "integrity": "sha512-94VRYjL9k33RzfKiGokPBPpsmloBYSf5Ri+Pq19zlsEcUKFob+admeXr5eFDRuPjFmEOcjJvPGdillYOJyvZ7Q==", + "dev": true, + "dependencies": { + "parse-ms": "^2.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "node_modules/psl": { + "version": "1.1.32", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz", + "integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", + "dev": true, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.0.2.tgz", + "integrity": "sha512-LbyJYv48eywrhOlScq16H/VkCiGKGPC2TpOdZCJ7QXnYEjn3NN/Oblh8QEU3vqfSRBB7OGvh5x45NKiVeNujIQ==", + "dev": true, + "dependencies": { + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "dependencies": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/redent/node_modules/indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reduce-flatten": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", + "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", + "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true, + "engines": { + "node": ">=6.5.0" + } + }, + "node_modules/regexpu-core": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz", + "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.0.2", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/registry-auth-token": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", + "dev": true, + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dev": true, + "dependencies": { + "rc": "^1.2.8" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/regjsgen": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", + "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "node_modules/repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/request-promise-core": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "dev": true, + "dependencies": { + "lodash": "^4.17.11" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request-promise-native": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", + "deprecated": "request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "engines": { + "node": ">=0.12.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "dependencies": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "node_modules/require-precompiled": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/require-precompiled/-/require-precompiled-0.1.0.tgz", + "integrity": "sha1-WhtS63Dr7UPrmC6XTIWrWVceVvo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "node_modules/requizzle": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", + "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/resolve": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", + "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", + "dev": true, + "dependencies": { + "path-parse": "^1.0.6" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-path": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc=", + "dev": true, + "dependencies": { + "http-errors": "~1.6.2", + "path-is-absolute": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/resolve-path/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/resolve-path/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true + }, + "node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "dependencies": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rollup": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.15.1.tgz", + "integrity": "sha512-JErZxFKs0w7wpHZXWonAlom1Jezo0gJ7mf7JHTjOAjFGKAqNMEnlzEjMYhy6cqHgSfSPj/idVscuW+Lo6y6AoQ==", + "dev": true, + "dependencies": { + "@types/estree": "0.0.39", + "@types/node": "^12.0.7", + "acorn": "^6.1.1" + }, + "bin": { + "rollup": "bin/rollup" + } + }, + "node_modules/rollup-plugin-commonjs": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.0.0.tgz", + "integrity": "sha512-B8MoX5GRpj3kW4+YaFO/di2JsZkBxNjVmZ9LWjUoTAjq8N9wc7HObMXPsrvolVV9JXVtYSscflXM14A19dXPNQ==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-commonjs.", + "dev": true, + "dependencies": { + "estree-walker": "^0.6.0", + "is-reference": "^1.1.2", + "magic-string": "^0.25.2", + "resolve": "^1.10.1", + "rollup-pluginutils": "^2.7.0" + }, + "peerDependencies": { + "rollup": ">=1.12.0" + } + }, + "node_modules/rollup-plugin-inject": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-2.2.0.tgz", + "integrity": "sha512-Wow9g+qkKbkK96wjLif2HqWOiuR6ZqkZbHSNt5r1bVUDQG96yzmuxlSl1grPzlTG5BbATUE7nA5HhQVfBXEigQ==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.", + "dev": true, + "dependencies": { + "estree-walker": "^0.5.0", + "magic-string": "^0.25.0", + "rollup-pluginutils": "^2.0.1" + } + }, + "node_modules/rollup-plugin-inject/node_modules/estree-walker": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.5.2.tgz", + "integrity": "sha512-XpCnW/AE10ws/kDAs37cngSkvgIR8aN3G0MS85m7dUpuK2EREo9VJ00uvw6Dg/hXEpfsE1I1TvJOJr+Z+TL+ig==", + "dev": true + }, + "node_modules/rollup-plugin-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-json/-/rollup-plugin-json-4.0.0.tgz", + "integrity": "sha512-hgb8N7Cgfw5SZAkb3jf0QXii6QX/FOkiIq2M7BAQIEydjHvTyxXHQiIzZaTFgx1GK0cRCHOCBHIyEkkLdWKxow==", + "deprecated": "This module has been deprecated and is no longer maintained. Please use @rollup/plugin-json.", + "dev": true, + "dependencies": { + "rollup-pluginutils": "^2.5.0" + } + }, + "node_modules/rollup-plugin-node-resolve": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.0.1.tgz", + "integrity": "sha512-9s3dTu44SKQZM/Pwll42GpqXgT+WdvO0Ga01lF8cwZqJGqRUATtD+GrP3uIzZdpnbPonEJbVasfFt80VGPQqKw==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-node-resolve.", + "dev": true, + "dependencies": { + "@types/resolve": "0.0.8", + "builtin-modules": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.11.0", + "rollup-pluginutils": "^2.8.0" + }, + "peerDependencies": { + "rollup": ">=1.11.0" + } + }, + "node_modules/rollup-plugin-node-resolve/node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + }, + "node_modules/rollup-plugin-node-resolve/node_modules/rollup-pluginutils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.1.tgz", + "integrity": "sha512-J5oAoysWar6GuZo0s+3bZ6sVZAC0pfqKz68De7ZgDi5z63jOVZn1uJL/+z1jeKHNbGII8kAyHF5q8LnxSX5lQg==", + "dev": true, + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "node_modules/rollup-pluginutils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.7.1.tgz", + "integrity": "sha512-3nRf3buQGR9qz/IsSzhZAJyoK663kzseps8itkYHr+Z7ESuaffEPfgRinxbCRA0pf0gzLqkNKkSb8aNVTq75NA==", + "dev": true, + "dependencies": { + "estree-walker": "^0.6.0", + "micromatch": "^3.1.10" + } + }, + "node_modules/rollup/node_modules/@types/node": { + "version": "12.0.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.8.tgz", + "integrity": "sha512-b8bbUOTwzIY3V5vDTY1fIJ+ePKDUBqt2hC2woVGotdQQhG/2Sh62HOKHrT7ab+VerXAcPyAiTEipPu/FsreUtg==", + "dev": true + }, + "node_modules/run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "dependencies": { + "is-promise": "^2.1.0" + }, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", + "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/saxes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.9.tgz", + "integrity": "sha512-FZeKhJglhJHk7eWG5YM0z46VHmI3KJpMBAQm3xa9meDvd+wevB5GuBB0wc0exPInZiBBHqi00DbS8AcvCGCFMw==", + "dev": true, + "dependencies": { + "xmlchars": "^1.3.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=", + "dev": true + }, + "node_modules/semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "dev": true, + "dependencies": { + "semver": "^5.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "dependencies": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated", + "dev": true + }, + "node_modules/sourcemap-codec": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz", + "integrity": "sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg==", + "dev": true + }, + "node_modules/spawn-command": { + "version": "0.0.2-1", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", + "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", + "dev": true + }, + "node_modules/spawn-wrap": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz", + "integrity": "sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw==", + "dev": true, + "dependencies": { + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", + "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", + "dev": true + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stream-log-stats": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-log-stats/-/stream-log-stats-3.0.0.tgz", + "integrity": "sha512-rv7anqmc2fYKfCkLeISQbtw1ebyiapl34AltF04cknL/l1IGbffAB0DN2NvH3ec57kXwzJBOK6ZsjxP41Ai5IA==", + "dev": true, + "dependencies": { + "ansi-escape-sequences": "^4.1.0", + "byte-size": "^5.0.1", + "common-log-format": "~0.1.4", + "JSONStream": "^1.3.5", + "lodash.throttle": "^4.1.1", + "stream-via": "^1.0.4", + "table-layout": "~0.4.4" + }, + "bin": { + "log-stats": "bin/cli.js" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/stream-read-all": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stream-read-all/-/stream-read-all-2.0.0.tgz", + "integrity": "sha512-M4kMuu/EmTTaO/Btsw4DTx6wEyTr+NcX8MIUuiTCzoUBc3twa/RA6OgpnSISjdvSOW5KBVStVggtMYnBzGn+CQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stream-slice": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/stream-slice/-/stream-slice-0.1.2.tgz", + "integrity": "sha1-LcT04bk2+xPz6zmi3vGTJ5jQeks=", + "dev": true + }, + "node_modules/stream-via": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stream-via/-/stream-via-1.0.4.tgz", + "integrity": "sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streaming-json-stringify": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/streaming-json-stringify/-/streaming-json-stringify-3.1.0.tgz", + "integrity": "sha1-gCAEN6mTzDnE/gAmO3s7kDrIevU=", + "dev": true, + "dependencies": { + "json-stringify-safe": "5", + "readable-stream": "2" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-bom-buf": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-2.0.0.tgz", + "integrity": "sha512-gLFNHucd6gzb8jMsl5QmZ3QgnUJmp7qn4uUSHNwEXumAp7YizoGYw19ZUVfuq4aBOQUtyn2k8X/CwzWB73W2lQ==", + "dev": true, + "dependencies": { + "is-utf8": "^0.2.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supertap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supertap/-/supertap-1.0.0.tgz", + "integrity": "sha512-HZJ3geIMPgVwKk2VsmO5YHqnnJYl6bV5A9JW2uzqV43WmpgliNEYbuvukfor7URpaqpxuw3CfZ3ONdVbZjCgIA==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "indent-string": "^3.2.0", + "js-yaml": "^3.10.0", + "serialize-error": "^2.1.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supertap/node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supertap/node_modules/indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/supertap/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", + "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", + "dev": true + }, + "node_modules/table": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/table/-/table-5.3.3.tgz", + "integrity": "sha512-3wUNCgdWX6PNpOe3amTTPWPuF6VGvgzjKCaO1snFj0z7Y3mUPWf5+zDtxUVGispJkDECPmR29wbzh6bVMOHbcw==", + "dev": true, + "dependencies": { + "ajv": "^6.9.1", + "lodash": "^4.17.11", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/table-layout": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.4.tgz", + "integrity": "sha512-uNaR3SRMJwfdp9OUr36eyEi6LLsbcTqTO/hfTsNviKsNeyMBPICJCC7QXRF3+07bAP6FRwA8rczJPBqXDc0CkQ==", + "dev": true, + "dependencies": { + "array-back": "^2.0.0", + "deep-extend": "~0.6.0", + "lodash.padend": "^4.6.1", + "typical": "^2.6.1", + "wordwrapjs": "^3.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/table-layout/node_modules/array-back": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", + "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", + "dev": true, + "dependencies": { + "typical": "^2.6.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/table-layout/node_modules/typical": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", + "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=", + "dev": true + }, + "node_modules/table/node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/table/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", + "dev": true + }, + "node_modules/term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "dev": true, + "dependencies": { + "execa": "^0.7.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "dev": true, + "dependencies": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/test-exclude/node_modules/read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/test-exclude/node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/thenify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", + "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/three": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.136.0.tgz", + "integrity": "sha512-+fEMX7nYLz2ZesVP/dyifli5Jf8gR3XPAnFJveQ80aMhibFduzrADnjMbARXh8+W9qLK7rshJCjAIL/6cDxC+A==" + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/time-zone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", + "integrity": "sha1-mcW/VZWJZq9tBtg73zgA3IL67F0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/trim-off-newlines": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz", + "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.5.2.tgz", + "integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typical": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.0.0.tgz", + "integrity": "sha512-EiYNG3wi45n7ZvDJaU/ekW8OiAJ0EfYNq8tQJUMVok5k8+g7mj54pFWOXFsstmQoVHfy4CjzgNgJtGUAVR8kFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "node_modules/uglify-js": { + "version": "3.5.15", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.15.tgz", + "integrity": "sha512-fe7aYFotptIddkwcm6YuA0HmknBZ52ZzOsUxZEdhhkSsz7RfjHDX2QDxwKTiv4JQ5t5NhfmpgAK+J7LiDhKSqg==", + "dev": true, + "optional": true, + "dependencies": { + "commander": "~2.20.0", + "source-map": "~0.6.1" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uglify-js/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uid2": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", + "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=", + "dev": true + }, + "node_modules/underscore": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", + "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==", + "dev": true + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", + "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", + "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "dev": true, + "dependencies": { + "crypto-random-string": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-temp-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-temp-dir/-/unique-temp-dir-1.0.0.tgz", + "integrity": "sha1-bc6VsmgcoAPuv7MEpBX5y6vMU4U=", + "dev": true, + "dependencies": { + "mkdirp": "^0.5.1", + "os-tmpdir": "^1.0.1", + "uid2": "0.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/update-notifier": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-3.0.0.tgz", + "integrity": "sha512-6Xe3oF2bvuoj4YECUc52yxVs94yWrxwqHbzyveDktTS1WhnlTRpNcQMxUshcB7nRVGi1jEXiqL5cW1S5WSyzKg==", + "dev": true, + "dependencies": { + "boxen": "^3.0.0", + "chalk": "^2.0.1", + "configstore": "^4.0.0", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.1.0", + "is-npm": "^3.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/url-join": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", + "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=", + "dev": true + }, + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "dev": true, + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "dev": true, + "dependencies": { + "source-map": "^0.5.1" + } + }, + "node_modules/vinyl/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/w3c-hr-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", + "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "dev": true, + "dependencies": { + "browser-process-hrtime": "^0.1.2" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz", + "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==", + "dev": true, + "dependencies": { + "domexception": "^1.0.1", + "webidl-conversions": "^4.0.2", + "xml-name-validator": "^3.0.0" + } + }, + "node_modules/walk-back": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-3.0.1.tgz", + "integrity": "sha512-umiNB2qLO731Sxbp6cfZ9pwURJzTnftxE4Gc7hq8n/ehkuXC//s9F65IEIJA2ZytQZ1ZOsm/Fju4IWx0bivkUQ==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "node_modules/well-known-symbols": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz", + "integrity": "sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "dev": true, + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "dev": true, + "dependencies": { + "string-width": "^2.1.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/window": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/window/-/window-4.2.6.tgz", + "integrity": "sha512-vk5Uv4hlPkZjUTAUVJUyvJQrbA05T99Qm3CTk0krXHKdoghxV70uPbHK3uGmrI1SfyJpdYncvZ7CbewJ30e9MQ==", + "dev": true, + "dependencies": { + "jsdom": "13.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "node_modules/wordwrapjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-3.0.0.tgz", + "integrity": "sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==", + "dev": true, + "dependencies": { + "reduce-flatten": "^1.0.1", + "typical": "^2.6.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/wordwrapjs/node_modules/reduce-flatten": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz", + "integrity": "sha1-JYx479FT3fk8tWEjf2EYTzaW4yc=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrapjs/node_modules/typical": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", + "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "dependencies": { + "mkdirp": "^0.5.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/write-file-atomic": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.2.tgz", + "integrity": "sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "node_modules/xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "node_modules/xmlchars": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-1.3.1.tgz", + "integrity": "sha512-tGkGJkN8XqCod7OT+EvGYK5Z4SfDQGD30zAa58OcnAa0RRWgzUEK72tkXhsX1FZd+rgnhRxFtmO+ihkp8LHSkw==", + "dev": true + }, + "node_modules/xmlcreate": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", + "integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==", + "dev": true + }, + "node_modules/xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "node_modules/yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "dependencies": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "node_modules/yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0" + } + }, + "node_modules/yargs/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/ylru": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", + "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + } + }, "dependencies": { "@ava/babel-plugin-throws-helper": { "version": "3.0.0", @@ -431,16 +10595,6 @@ "@types/node": "*" } }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, "abab": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", @@ -477,7 +10631,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "6.1.1", @@ -4295,6 +14450,16 @@ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -5010,7 +15175,8 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz", "integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==", - "dev": true + "dev": true, + "requires": {} }, "marked": { "version": "0.8.2", @@ -7186,10 +17352,10 @@ "integrity": "sha512-rv7anqmc2fYKfCkLeISQbtw1ebyiapl34AltF04cknL/l1IGbffAB0DN2NvH3ec57kXwzJBOK6ZsjxP41Ai5IA==", "dev": true, "requires": { - "JSONStream": "^1.3.5", "ansi-escape-sequences": "^4.1.0", "byte-size": "^5.0.1", "common-log-format": "~0.1.4", + "JSONStream": "^1.3.5", "lodash.throttle": "^4.1.1", "stream-via": "^1.0.4", "table-layout": "~0.4.4" @@ -7223,6 +17389,15 @@ "readable-stream": "2" } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -7244,15 +17419,6 @@ } } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -7496,9 +17662,9 @@ } }, "three": { - "version": "0.105.2", - "resolved": "https://registry.npmjs.org/three/-/three-0.105.2.tgz", - "integrity": "sha512-L3Al37k4g3hVbgFFS251UVtIc25chhyN0/RvXzR0C+uIBToV6EKDG+MZzEXm9L2miGUVMK27W46/VkP6WUZXMg==" + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.136.0.tgz", + "integrity": "sha512-+fEMX7nYLz2ZesVP/dyifli5Jf8gR3XPAnFJveQ80aMhibFduzrADnjMbARXh8+W9qLK7rshJCjAIL/6cDxC+A==" }, "through": { "version": "2.3.8", diff --git a/package.json b/package.json index f0be6bf8..8f9a9e15 100644 --- a/package.json +++ b/package.json @@ -125,7 +125,7 @@ "xmlhttprequest": "^1.8.0" }, "dependencies": { - "three": "^0.105.2" + "three": "^0.136" }, "jspm": { "files": [ diff --git a/src/infospot/Infospot.js b/src/infospot/Infospot.js index c5ac8264..498d455c 100644 --- a/src/infospot/Infospot.js +++ b/src/infospot/Infospot.js @@ -11,105 +11,101 @@ import TWEEN from '@tweenjs/tween.js'; * @param {string} [imageSrc=PANOLENS.DataImage.Info] - Image overlay info * @param {boolean} [animated=true] - Enable default hover animation */ -function Infospot ( scale = 300, imageSrc, animated ) { - - const duration = 500, scaleFactor = 1.3; - - imageSrc = imageSrc || DataImage.Info; +class Infospot extends THREE.Sprite { - THREE.Sprite.call( this ); + constructor( scale = 300, imageSrc, animated ) { + super(); + const duration = 500, scaleFactor = 1.3; - this.type = 'infospot'; + imageSrc = imageSrc || DataImage.Info; - this.animated = animated !== undefined ? animated : true; - this.isHovering = false; - - /* - * TODO: Three.js bug hotfix for sprite raycasting r104 - * https://github.com/mrdoob/three.js/issues/14624 - */ - this.frustumCulled = false; + this.type = 'infospot'; - this.element = null; - this.toPanorama = null; - this.cursorStyle = null; - - this.mode = MODES.NORMAL; + this.animated = animated !== undefined ? animated : true; + this.isHovering = false; - this.scale.set( scale, scale, 1 ); - this.rotation.y = Math.PI; + /* + * TODO: Three.js bug hotfix for sprite raycasting r104 + * https://github.com/mrdoob/three.js/issues/14624 + */ + this.frustumCulled = false; - this.container = null; + this.element = null; + this.toPanorama = null; + this.cursorStyle = null; - this.originalRaycast = this.raycast; + this.mode = MODES.NORMAL; - // Event Handler - this.HANDLER_FOCUS = null; + this.scale.set( scale, scale, 1 ); + this.rotation.y = Math.PI; - this.material.side = THREE.DoubleSide; - this.material.depthTest = false; - this.material.transparent = true; - this.material.opacity = 0; + this.container = null; - this.scaleUpAnimation = new TWEEN.Tween(); - this.scaleDownAnimation = new TWEEN.Tween(); + this.originalRaycast = this.raycast; + // Event Handler + this.HANDLER_FOCUS = null; - const postLoad = function ( texture ) { + this.material.side = THREE.DoubleSide; + this.material.depthTest = false; + this.material.transparent = true; + this.material.opacity = 0; - if ( !this.material ) { return; } + this.scaleUpAnimation = new TWEEN.Tween(); + this.scaleDownAnimation = new TWEEN.Tween(); - const ratio = texture.image.width / texture.image.height; - const textureScale = new THREE.Vector3(); - texture.image.width = texture.image.naturalWidth || 64; - texture.image.height = texture.image.naturalHeight || 64; + const postLoad = function ( texture ) { - this.scale.set( ratio * scale, scale, 1 ); + if ( !this.material ) { return; } - textureScale.copy( this.scale ); + const ratio = texture.image.width / texture.image.height; + const textureScale = new THREE.Vector3(); - this.scaleUpAnimation = new TWEEN.Tween( this.scale ) - .to( { x: textureScale.x * scaleFactor, y: textureScale.y * scaleFactor }, duration ) - .easing( TWEEN.Easing.Elastic.Out ); + texture.image.width = texture.image.naturalWidth || 64; + texture.image.height = texture.image.naturalHeight || 64; - this.scaleDownAnimation = new TWEEN.Tween( this.scale ) - .to( { x: textureScale.x, y: textureScale.y }, duration ) - .easing( TWEEN.Easing.Elastic.Out ); + this.scale.set( ratio * scale, scale, 1 ); - this.material.map = texture; - this.material.needsUpdate = true; + textureScale.copy( this.scale ); - }.bind( this ); + this.scaleUpAnimation = new TWEEN.Tween( this.scale ) + .to( { x: textureScale.x * scaleFactor, y: textureScale.y * scaleFactor }, duration ) + .easing( TWEEN.Easing.Elastic.Out ); - // Add show and hide animations - this.showAnimation = new TWEEN.Tween( this.material ) - .to( { opacity: 1 }, duration ) - .onStart( this.enableRaycast.bind( this, true ) ) - .easing( TWEEN.Easing.Quartic.Out ); + this.scaleDownAnimation = new TWEEN.Tween( this.scale ) + .to( { x: textureScale.x, y: textureScale.y }, duration ) + .easing( TWEEN.Easing.Elastic.Out ); - this.hideAnimation = new TWEEN.Tween( this.material ) - .to( { opacity: 0 }, duration ) - .onStart( this.enableRaycast.bind( this, false ) ) - .easing( TWEEN.Easing.Quartic.Out ); + this.material.map = texture; + this.material.needsUpdate = true; - // Attach event listeners - this.addEventListener( 'click', this.onClick ); - this.addEventListener( 'hover', this.onHover ); - this.addEventListener( 'hoverenter', this.onHoverStart ); - this.addEventListener( 'hoverleave', this.onHoverEnd ); - this.addEventListener( 'panolens-dual-eye-effect', this.onDualEyeEffect ); - this.addEventListener( 'panolens-container', this.setContainer.bind( this ) ); - this.addEventListener( 'dismiss', this.onDismiss ); - this.addEventListener( 'panolens-infospot-focus', this.setFocusMethod ); + }.bind( this ); - TextureLoader.load( imageSrc, postLoad ); + // Add show and hide animations + this.showAnimation = new TWEEN.Tween( this.material ) + .to( { opacity: 1 }, duration ) + .onStart( this.enableRaycast.bind( this, true ) ) + .easing( TWEEN.Easing.Quartic.Out ); -}; + this.hideAnimation = new TWEEN.Tween( this.material ) + .to( { opacity: 0 }, duration ) + .onStart( this.enableRaycast.bind( this, false ) ) + .easing( TWEEN.Easing.Quartic.Out ); -Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { + // Attach event listeners + this.addEventListener( 'click', this.onClick ); + this.addEventListener( 'hover', this.onHover ); + this.addEventListener( 'hoverenter', this.onHoverStart ); + this.addEventListener( 'hoverleave', this.onHoverEnd ); + this.addEventListener( 'panolens-dual-eye-effect', this.onDualEyeEffect ); + this.addEventListener( 'panolens-container', this.setContainer.bind( this ) ); + this.addEventListener( 'dismiss', this.onDismiss ); + this.addEventListener( 'panolens-infospot-focus', this.setFocusMethod ); - constructor: Infospot, + TextureLoader.load( imageSrc, postLoad ); + } + /** * Set infospot container @@ -117,30 +113,30 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @memberOf Infospot * @instance */ - setContainer: function ( data ) { + setContainer ( data ) { let container; - + if ( data instanceof HTMLElement ) { - + container = data; - + } else if ( data && data.container ) { - + container = data.container; - + } - + // Append element if exists if ( container && this.element ) { - + container.appendChild( this.element ); - + } - + this.container = container; - - }, + + } /** * Get container @@ -148,11 +144,11 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @instance * @return {HTMLElement} - The container of this infospot */ - getContainer: function () { + getContainer () { return this.container; - }, + } /** * This will be called by a click event @@ -161,7 +157,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @memberOf Infospot * @instance */ - onClick: function ( event ) { + onClick ( event ) { if ( this.element && this.getContainer() ) { @@ -172,7 +168,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { } - }, + } /** * Dismiss current element if any @@ -180,7 +176,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @memberOf Infospot * @instance */ - onDismiss: function () { + onDismiss () { if ( this.element ) { @@ -189,7 +185,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { } - }, + } /** * This will be called by a mouse hover event @@ -198,7 +194,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @memberOf Infospot * @instance */ - onHover: function () {}, + onHover () {} /** * This will be called on a mouse hover start @@ -207,7 +203,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @memberOf Infospot * @instance */ - onHoverStart: function ( event ) { + onHoverStart ( event ) { if ( !this.getContainer() ) { return; } @@ -216,14 +212,14 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { this.isHovering = true; this.container.style.cursor = cursorStyle; - + if ( this.animated ) { scaleDownAnimation.stop(); scaleUpAnimation.start(); } - + if ( element && event.mouseEvent.clientX >= 0 && event.mouseEvent.clientY >= 0 ) { const { left, right, style } = element; @@ -249,10 +245,10 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { element._height = element.clientHeight; } - + } - }, + } /** * This will be called on a mouse hover end @@ -260,7 +256,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @memberOf Infospot * @instance */ - onHoverEnd: function () { + onHoverEnd () { if ( !this.getContainer() ) { return; } @@ -288,7 +284,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { } - }, + } /** * On dual eye effect handler @@ -297,8 +293,8 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @memberOf Infospot * @instance */ - onDualEyeEffect: function ( event ) { - + onDualEyeEffect ( event ) { + if ( !this.getContainer() ) { return; } let element, halfWidth, halfHeight; @@ -343,7 +339,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { this.container.appendChild( element.left ); this.container.appendChild( element.right ); - }, + } /** * Translate the hovering element by css transform @@ -352,7 +348,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @memberOf Infospot * @instance */ - translateElement: function ( x, y ) { + translateElement ( x, y ) { if ( !this.element._width || !this.element._height || !this.getContainer() ) { @@ -372,8 +368,8 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { top = y - height - delta; if ( ( this.mode === MODES.CARDBOARD || this.mode === MODES.STEREO ) - && element.left && element.right - && !( x === container.clientWidth / 2 && y === container.clientHeight / 2 ) ) { + && element.left && element.right + && !( x === container.clientWidth / 2 && y === container.clientHeight / 2 ) ) { left = container.clientWidth / 4 - width + ( x - container.clientWidth / 2 ); top = container.clientHeight / 2 - height - delta + ( y - container.clientHeight / 2 ); @@ -390,7 +386,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { } - }, + } /** * Set vendor specific css @@ -400,7 +396,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @memberOf Infospot * @instance */ - setElementStyle: function ( type, element, value ) { + setElementStyle ( type, element, value ) { const style = element.style; @@ -410,7 +406,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { } - }, + } /** * Set hovering text content @@ -418,7 +414,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @memberOf Infospot * @instance */ - setText: function ( text ) { + setText ( text ) { if ( this.element ) { @@ -426,18 +422,18 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { } - }, + } /** * Set cursor css style on hover * @memberOf Infospot * @instance */ - setCursorHoverStyle: function ( style ) { + setCursorHoverStyle ( style ) { this.cursorStyle = style; - }, + } /** * Add hovering text element @@ -446,7 +442,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @memberOf Infospot * @instance */ - addHoverText: function ( text, delta = 40 ) { + addHoverText ( text, delta = 40 ) { if ( !this.element ) { @@ -466,7 +462,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { this.setText( text ); - }, + } /** * Add hovering element by cloning an element @@ -475,7 +471,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @memberOf Infospot * @instance */ - addHoverElement: function ( el, delta = 40 ) { + addHoverElement ( el, delta = 40 ) { if ( !this.element ) { @@ -488,14 +484,14 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { } - }, + } /** * Remove hovering element * @memberOf Infospot * @instance */ - removeHoverElement: function () { + removeHoverElement () { if ( this.element ) { @@ -518,14 +514,14 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { } - }, + } /** * Lock hovering element * @memberOf Infospot * @instance */ - lockHoverElement: function () { + lockHoverElement () { if ( this.element ) { @@ -533,14 +529,14 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { } - }, + } /** * Unlock hovering element * @memberOf Infospot * @instance */ - unlockHoverElement: function () { + unlockHoverElement () { if ( this.element ) { @@ -548,7 +544,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { } - }, + } /** * Enable raycasting @@ -556,7 +552,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @memberOf Infospot * @instance */ - enableRaycast: function ( enabled = true ) { + enableRaycast ( enabled = true ) { if ( enabled ) { @@ -568,7 +564,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { } - }, + } /** * Show infospot @@ -576,7 +572,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @memberOf Infospot * @instance */ - show: function ( delay = 0 ) { + show ( delay = 0 ) { const { animated, hideAnimation, showAnimation, material } = this; @@ -592,7 +588,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { } - }, + } /** * Hide infospot @@ -600,7 +596,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @memberOf Infospot * @instance */ - hide: function ( delay = 0 ) { + hide ( delay = 0 ) { const { animated, hideAnimation, showAnimation, material, element } = this; @@ -620,15 +616,15 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { material.opacity = 0; } - - }, + + } /** * Set focus event handler * @memberOf Infospot * @instance */ - setFocusMethod: function ( event ) { + setFocusMethod ( event ) { if ( event ) { @@ -636,7 +632,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { } - }, + } /** * Focus camera center to this infospot @@ -645,7 +641,7 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @memberOf Infospot * @instance */ - focus: function ( duration, easing ) { + focus ( duration, easing ) { if ( this.HANDLER_FOCUS ) { @@ -654,14 +650,14 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { } - }, + } /** * Dispose * @memberOf Infospot * @instance */ - dispose: function () { + dispose () { const { geometry, material } = this; const { map } = material; @@ -680,6 +676,8 @@ Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { } -} ); + + +}; export { Infospot }; \ No newline at end of file diff --git a/src/interface/Reticle.js b/src/interface/Reticle.js index da4fd8ea..1f0f7c4f 100644 --- a/src/interface/Reticle.js +++ b/src/interface/Reticle.js @@ -8,41 +8,34 @@ import * as THREE from 'three'; * @param {boolean} [autoSelect=true] - Auto selection * @param {number} [dwellTime=1500] - Duration for dwelling sequence to complete */ - -function Reticle ( color = 0xffffff, autoSelect = true, dwellTime = 1500 ) { - - this.dpr = window.devicePixelRatio; - - const { canvas, context } = this.createCanvas(); - const material = new THREE.SpriteMaterial( { color, map: this.createCanvasTexture( canvas ) } ); - - THREE.Sprite.call( this, material ); - - this.canvasWidth = canvas.width; - this.canvasHeight = canvas.height; - this.context = context; - this.color = color instanceof THREE.Color ? color : new THREE.Color( color ); - - this.autoSelect = autoSelect; - this.dwellTime = dwellTime; - this.rippleDuration = 500; - this.position.z = -10; - this.center.set( 0.5, 0.5 ); - this.scale.set( 0.5, 0.5, 1 ); - - this.startTimestamp = null; - this.timerId = null; - this.callback = null; - - this.frustumCulled = false; - - this.updateCanvasArcByProgress( 0 ); - -}; - -Reticle.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { - - constructor: Reticle, +class Reticle extends THREE.Sprite { + constructor( color = 0xffffff, autoSelect = true, dwellTime = 1500 ) { + const { canvas, context } = Reticle.createCanvas(window.devicePixelRatio); + const material = new THREE.SpriteMaterial( { color, map: Reticle.createCanvasTexture( canvas ) } ); + super(material); + + this.dpr = window.devicePixelRatio; + + this.canvasWidth = canvas.width; + this.canvasHeight = canvas.height; + this.context = context; + this.color = color instanceof THREE.Color ? color : new THREE.Color( color ); + + this.autoSelect = autoSelect; + this.dwellTime = dwellTime; + this.rippleDuration = 500; + this.position.z = -10; + this.center.set( 0.5, 0.5 ); + this.scale.set( 0.5, 0.5, 1 ); + + this.startTimestamp = null; + this.timerId = null; + this.callback = null; + + this.frustumCulled = false; + + this.updateCanvasArcByProgress( 0 ); + } /** * Set material color @@ -50,12 +43,12 @@ Reticle.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @memberOf Reticle * @instance */ - setColor: function ( color ) { + setColor ( color ) { this.material.color.copy( color instanceof THREE.Color ? color : new THREE.Color( color ) ); - - }, - + + } + /** * Create canvas texture * @param {HTMLCanvasElement} canvas @@ -63,17 +56,17 @@ Reticle.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @instance * @returns {THREE.CanvasTexture} */ - createCanvasTexture: function ( canvas ) { - + static createCanvasTexture ( canvas ) { + const texture = new THREE.CanvasTexture( canvas ); texture.minFilter = THREE.LinearFilter; texture.magFilter = THREE.LinearFilter; texture.generateMipmaps = false; - + return texture; - }, - + } + /** * Create canvas element * @memberOf Reticle @@ -82,33 +75,30 @@ Reticle.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @returns {HTMLCanvasElement} object.canvas * @returns {CanvasRenderingContext2D} object.context */ - createCanvas: function () { - + static createCanvas (dpr) { const width = 32; const height = 32; const canvas = document.createElement( 'canvas' ); const context = canvas.getContext( '2d' ); - const dpr = this.dpr; - + canvas.width = width * dpr; canvas.height = height * dpr; context.scale( dpr, dpr ); - + context.shadowBlur = 5; context.shadowColor = 'rgba(200,200,200,0.9)'; - + return { canvas, context }; - - }, - + } + /** * Update canvas arc by progress * @param {number} progress * @memberOf Reticle * @instance */ - updateCanvasArcByProgress: function ( progress ) { - + updateCanvasArcByProgress ( progress ) { + const context = this.context; const { canvasWidth, canvasHeight, material } = this; const dpr = this.dpr; @@ -117,10 +107,10 @@ Reticle.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { const x = canvasWidth * 0.5 / dpr; const y = canvasHeight * 0.5 / dpr; const lineWidth = 3; - + context.clearRect( 0, 0, canvasWidth, canvasHeight ); context.beginPath(); - + if ( progress === 0 ) { context.arc( x, y, canvasWidth / 16, 0, 2 * Math.PI ); context.fillStyle = color; @@ -131,13 +121,13 @@ Reticle.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { context.lineWidth = lineWidth; context.stroke(); } - + context.closePath(); - + material.map.needsUpdate = true; - - }, - + + } + /** * Ripple effect * @memberOf Reticle @@ -145,8 +135,8 @@ Reticle.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @fires Reticle#reticle-ripple-start * @fires Reticle#reticle-ripple-end */ - ripple: function () { - + ripple() { + const context = this.context; const { canvasWidth, canvasHeight, material } = this; const duration = this.rippleDuration; @@ -155,73 +145,73 @@ Reticle.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { const dpr = this.dpr; const x = canvasWidth * 0.5 / dpr; const y = canvasHeight * 0.5 / dpr; - + const update = () => { - + const timerId = window.requestAnimationFrame( update ); const elapsed = performance.now() - timestamp; const progress = elapsed / duration; const opacity = 1.0 - progress > 0 ? 1.0 - progress : 0; const radius = progress * canvasWidth * 0.5 / dpr; - + context.clearRect( 0, 0, canvasWidth, canvasHeight ); context.beginPath(); context.arc( x, y, radius, 0, Math.PI * 2 ); context.fillStyle = `rgba(${color.r * 255}, ${color.g * 255}, ${color.b * 255}, ${opacity})`; context.fill(); context.closePath(); - + if ( progress >= 1.0 ) { - + window.cancelAnimationFrame( timerId ); this.updateCanvasArcByProgress( 0 ); - + /** * Reticle ripple end event * @type {object} * @event Reticle#reticle-ripple-end */ this.dispatchEvent( { type: 'reticle-ripple-end' } ); - + } - + material.map.needsUpdate = true; - + }; - + /** * Reticle ripple start event * @type {object} * @event Reticle#reticle-ripple-start */ this.dispatchEvent( { type: 'reticle-ripple-start' } ); - + update(); - - }, - + + } + /** * Make reticle visible * @memberOf Reticle * @instance */ - show: function () { - + show () { + this.visible = true; - - }, - + + } + /** * Make reticle invisible * @memberOf Reticle * @instance */ - hide: function () { - + hide () { + this.visible = false; - - }, - + + } + /** * Start dwelling * @param {function} callback @@ -229,86 +219,85 @@ Reticle.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { * @instance * @fires Reticle#reticle-start */ - start: function ( callback ) { - + start ( callback ) { + if ( !this.autoSelect ) { - + return; - + } - + /** * Reticle start event * @type {object} * @event Reticle#reticle-start */ this.dispatchEvent( { type: 'reticle-start' } ); - + this.startTimestamp = performance.now(); this.callback = callback; this.update(); - - }, - + + } + /** * End dwelling * @memberOf Reticle * @instance * @fires Reticle#reticle-end */ - end: function(){ - + end(){ + if ( !this.startTimestamp ) { return; } - + window.cancelAnimationFrame( this.timerId ); - + this.updateCanvasArcByProgress( 0 ); this.callback = null; this.timerId = null; this.startTimestamp = null; - + /** * Reticle end event * @type {object} * @event Reticle#reticle-end */ this.dispatchEvent( { type: 'reticle-end' } ); - - }, - + + } + /** * Update dwelling * @memberOf Reticle * @instance * @fires Reticle#reticle-update */ - update: function () { - + update () { + this.timerId = window.requestAnimationFrame( this.update.bind( this ) ); - + const elapsed = performance.now() - this.startTimestamp; const progress = elapsed / this.dwellTime; - + this.updateCanvasArcByProgress( progress ); - + /** * Reticle update event * @type {object} * @event Reticle#reticle-update */ this.dispatchEvent( { type: 'reticle-update', progress } ); - + if ( progress >= 1.0 ) { - + window.cancelAnimationFrame( this.timerId ); if ( this.callback ) { this.callback(); } this.end(); this.ripple(); - + } - - } - -} ); + + } +}; export { Reticle }; \ No newline at end of file diff --git a/src/panorama/ImagePanorama.js b/src/panorama/ImagePanorama.js index ba20098c..8d2be272 100644 --- a/src/panorama/ImagePanorama.js +++ b/src/panorama/ImagePanorama.js @@ -7,30 +7,18 @@ import * as THREE from 'three'; * @constructor * @param {string} image - Image url or HTMLImageElement */ -function ImagePanorama ( image, _geometry, _material ) { - - const radius = 5000; - const geometry = _geometry || new THREE.SphereBufferGeometry( radius, 60, 40 ); - const material = _material || new THREE.MeshBasicMaterial( { opacity: 0, transparent: true } ); - - Panorama.call( this, geometry, material ); - - this.src = image; - this.radius = radius; - -} - -ImagePanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { +class ImagePanorama extends Panorama { + constructor( image, _geometry, _material ) { + const radius = 5000; + const geometry = _geometry || new THREE.SphereBufferGeometry( radius, 60, 40 ); + const material = _material || new THREE.MeshBasicMaterial( { opacity: 0, transparent: true } ); + super(geometry, material); + + this.src = image; + this.radius = radius; + } - constructor: ImagePanorama, - - /** - * Load image asset - * @param {*} src - Url or image element - * @memberOf ImagePanorama - * @instance - */ - load: function ( src ) { + load ( src ) { src = src || this.src; @@ -50,7 +38,7 @@ ImagePanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { } - }, + } /** * This will be called when image is loaded @@ -58,34 +46,34 @@ ImagePanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { * @memberOf ImagePanorama * @instance */ - onLoad: function ( texture ) { + onLoad( texture ) { texture.minFilter = texture.magFilter = THREE.LinearFilter; texture.needsUpdate = true; - + this.updateTexture( texture ); window.requestAnimationFrame( Panorama.prototype.onLoad.bind( this ) ); - }, + } /** * Reset * @memberOf ImagePanorama * @instance */ - reset: function () { + reset() { Panorama.prototype.reset.call( this ); - }, + } /** * Dispose * @memberOf ImagePanorama * @instance */ - dispose: function () { + dispose() { const { material: { map } } = this; @@ -98,6 +86,8 @@ ImagePanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { } -} ); + + +} export { ImagePanorama }; \ No newline at end of file diff --git a/src/panorama/Panorama.js b/src/panorama/Panorama.js index e3cf1dd9..0fba794e 100644 --- a/src/panorama/Panorama.js +++ b/src/panorama/Panorama.js @@ -10,72 +10,59 @@ import TWEEN from '@tweenjs/tween.js'; * @param {THREE.Geometry} geometry - The geometry for this panorama * @param {THREE.Material} material - The material for this panorama */ -function Panorama ( geometry, material ) { +class Panorama extends THREE.Mesh { + constructor(geometry, material) { + super(geometry, material); - THREE.Mesh.call( this, geometry, material ); + this.type = 'panorama'; - this.type = 'panorama'; + this.ImageQualityLow = 1; + this.ImageQualityFair = 2; + this.ImageQualityMedium = 3; + this.ImageQualityHigh = 4; + this.ImageQualitySuperHigh = 5; - this.ImageQualityLow = 1; - this.ImageQualityFair = 2; - this.ImageQualityMedium = 3; - this.ImageQualityHigh = 4; - this.ImageQualitySuperHigh = 5; + this.animationDuration = 1000; - this.animationDuration = 1000; + this.defaultInfospotSize = 350; - this.defaultInfospotSize = 350; + this.container = undefined; - this.container = undefined; + this.loaded = false; - this.loaded = false; + this.linkedSpots = []; - this.linkedSpots = []; + this.isInfospotVisible = false; - this.isInfospotVisible = false; - - this.linkingImageURL = undefined; - this.linkingImageScale = undefined; + this.linkingImageURL = undefined; + this.linkingImageScale = undefined; - this.material.side = THREE.BackSide; - this.material.opacity = 0; + this.material.side = THREE.BackSide; + this.material.opacity = 0; - this.scale.x *= -1; - this.renderOrder = -1; + this.scale.x *= -1; + this.renderOrder = -1; - this.active = false; - - this.infospotAnimation = new TWEEN.Tween( this ).to( {}, this.animationDuration / 2 ); - - this.addEventListener( 'load', this.fadeIn.bind( this ) ); - this.addEventListener( 'panolens-container', this.setContainer.bind( this ) ); - this.addEventListener( 'click', this.onClick.bind( this ) ); + this.active = false; - this.setupTransitions(); + this.infospotAnimation = new TWEEN.Tween(this).to({}, this.animationDuration / 2); -} + this.addEventListener('load', this.fadeIn.bind(this)); + this.addEventListener('panolens-container', this.setContainer.bind(this)); + this.addEventListener('click', this.onClick.bind(this)); -Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { + this.setupTransitions(); + } - constructor: Panorama, - - /** - * Adding an object - * To counter the scale.x = -1, it will automatically add an - * empty object with inverted scale on x - * @memberOf Panorama - * @instance - * @param {THREE.Object3D} object - The object to be added - */ - add: function ( object ) { + add(object) { let invertedObject; - if ( arguments.length > 1 ) { + if (arguments.length > 1) { - for ( var i = 0; i < arguments.length; i ++ ) { + for (var i = 0; i < arguments.length; i++) { - this.add( arguments[ i ] ); + this.add(arguments[i]); } @@ -84,29 +71,31 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { } // In case of infospots - if ( object instanceof Infospot ) { + if (object instanceof Infospot) { invertedObject = object; - if ( object.dispatchEvent ) { + if (object.dispatchEvent) { const { container } = this; - if ( container ) { object.dispatchEvent( { type: 'panolens-container', container } ); } - - object.dispatchEvent( { type: 'panolens-infospot-focus', method: function ( vector, duration, easing ) { + if (container) { object.dispatchEvent({ type: 'panolens-container', container }); } - /** - * Infospot focus handler event - * @type {object} - * @event Panorama#panolens-viewer-handler - * @property {string} method - Viewer function name - * @property {*} data - The argument to be passed into the method - */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'tweenControlCenter', data: [ vector, duration, easing ] } ); + object.dispatchEvent({ + type: 'panolens-infospot-focus', method: function (vector, duration, easing) { + + /** + * Infospot focus handler event + * @type {object} + * @event Panorama#panolens-viewer-handler + * @property {string} method - Viewer function name + * @property {*} data - The argument to be passed into the method + */ + this.dispatchEvent({ type: 'panolens-viewer-handler', method: 'tweenControlCenter', data: [vector, duration, easing] }); - }.bind( this ) } ); + }.bind(this) + }); } } else { @@ -115,19 +104,19 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { invertedObject = new THREE.Object3D(); invertedObject.scale.x = -1; invertedObject.scalePlaceHolder = true; - invertedObject.add( object ); + invertedObject.add(object); } - THREE.Object3D.prototype.add.call( this, invertedObject ); + THREE.Object3D.prototype.add.call(this, invertedObject); - }, + } - load: function () { + load() { this.onLoad(); - - }, + + } /** * Click event handler @@ -136,24 +125,24 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @instance * @fires Infospot#dismiss */ - onClick: function ( event ) { + onClick(event) { - if ( event.intersects && event.intersects.length === 0 ) { + if (event.intersects && event.intersects.length === 0) { - this.traverse( function ( object ) { + this.traverse(function (object) { /** * Dimiss event * @type {object} * @event Infospot#dismiss */ - object.dispatchEvent( { type: 'dismiss' } ); + object.dispatchEvent({ type: 'dismiss' }); - } ); + }); } - }, + } /** * Set container of this panorama @@ -162,25 +151,25 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @instance * @fires Infospot#panolens-container */ - setContainer: function ( data ) { + setContainer(data) { let container; - if ( data instanceof HTMLElement ) { + if (data instanceof HTMLElement) { container = data; - } else if ( data && data.container ) { + } else if (data && data.container) { container = data.container; } - if ( container ) { + if (container) { - this.children.forEach( function ( child ) { + this.children.forEach(function (child) { - if ( child instanceof Infospot && child.dispatchEvent ) { + if (child instanceof Infospot && child.dispatchEvent) { /** * Set container event @@ -188,17 +177,17 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @event Infospot#panolens-container * @property {HTMLElement} container - The container of this panorama */ - child.dispatchEvent( { type: 'panolens-container', container: container } ); + child.dispatchEvent({ type: 'panolens-container', container: container }); } - } ); + }); this.container = container; } - }, + } /** * This will be called when panorama is loaded @@ -206,7 +195,7 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @instance * @fires Panorama#load */ - onLoad: function () { + onLoad() { this.loaded = true; @@ -215,9 +204,9 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @type {object} * @event Panorama#load */ - this.dispatchEvent( { type: 'load' } ); + this.dispatchEvent({ type: 'load' }); - }, + } /** * This will be called when panorama is in progress @@ -225,7 +214,7 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @instance * @fires Panorama#progress */ - onProgress: function ( progress ) { + onProgress(progress) { /** * Loading panorama progress event @@ -233,26 +222,25 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @event Panorama#progress * @property {object} progress - The progress object containing loaded and total amount */ - this.dispatchEvent( { type: 'progress', progress: progress } ); - - }, + this.dispatchEvent({ type: 'progress', progress: progress }); + } /** * This will be called when panorama loading has error * @memberOf Panorama * @instance * @fires Panorama#error */ - onError: function () { + onError() { /** * Loading panorama error event * @type {object} * @event Panorama#error */ - this.dispatchEvent( { type: 'error' } ); + this.dispatchEvent({ type: 'error' }); - }, + } /** * Get zoom level based on window width @@ -260,23 +248,23 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @instance * @return {number} zoom level indicating image quality */ - getZoomLevel: function () { + getZoomLevel() { let zoomLevel; - if ( window.innerWidth <= 800 ) { + if (window.innerWidth <= 800) { zoomLevel = this.ImageQualityFair; - } else if ( window.innerWidth > 800 && window.innerWidth <= 1280 ) { + } else if (window.innerWidth > 800 && window.innerWidth <= 1280) { zoomLevel = this.ImageQualityMedium; - } else if ( window.innerWidth > 1280 && window.innerWidth <= 1920 ) { + } else if (window.innerWidth > 1280 && window.innerWidth <= 1920) { zoomLevel = this.ImageQualityHigh; - } else if ( window.innerWidth > 1920 ) { + } else if (window.innerWidth > 1920) { zoomLevel = this.ImageQualitySuperHigh; @@ -288,7 +276,7 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { return zoomLevel; - }, + } /** * Update texture of a panorama @@ -296,12 +284,12 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @instance * @param {THREE.Texture} texture - Texture to be updated */ - updateTexture: function ( texture ) { + updateTexture(texture) { this.material.map = texture; this.material.needsUpdate = true; - }, + } /** * Toggle visibility of infospots in this panorama @@ -311,45 +299,45 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @instance * @fires Panorama#infospot-animation-complete */ - toggleInfospotVisibility: function ( isVisible, delay ) { + toggleInfospotVisibility(isVisible, delay) { - delay = ( delay !== undefined ) ? delay : 0; + delay = (delay !== undefined) ? delay : 0; - const visible = ( isVisible !== undefined ) ? isVisible : ( this.isInfospotVisible ? false : true ); + const visible = (isVisible !== undefined) ? isVisible : (this.isInfospotVisible ? false : true); - this.traverse( function ( object ) { + this.traverse(function (object) { - if ( object instanceof Infospot ) { + if (object instanceof Infospot) { - if ( visible ) { + if (visible) { - object.show( delay ); + object.show(delay); } else { - object.hide( delay ); + object.hide(delay); } } - } ); + }); this.isInfospotVisible = visible; // Animation complete event - this.infospotAnimation.onComplete( function () { + this.infospotAnimation.onComplete(function () { /** * Complete toggling infospot visibility * @event Panorama#infospot-animation-complete * @type {object} */ - this.dispatchEvent( { type: 'infospot-animation-complete', visible: visible } ); + this.dispatchEvent({ type: 'infospot-animation-complete', visible: visible }); - }.bind( this ) ).delay( delay ).start(); + }.bind(this)).delay(delay).start(); - }, + } /** * Set image of this panorama's linking infospot @@ -358,12 +346,12 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @param {string} url - Url to the image asset * @param {number} scale - Scale factor of the infospot */ - setLinkingImage: function ( url, scale ) { + setLinkingImage(url, scale) { this.linkingImageURL = url; this.linkingImageScale = scale; - }, + } /** * Link one-way panorama @@ -374,26 +362,26 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @memberOf Panorama * @instance */ - link: function ( pano, position, imageScale, imageSrc ) { + link(pano, position, imageScale, imageSrc) { let scale, img; this.visible = true; - if ( !position ) { + if (!position) { - console.warn( 'Please specify infospot position for linking' ); + console.warn('Please specify infospot position for linking'); return; } // Infospot scale - if ( imageScale !== undefined ) { + if (imageScale !== undefined) { scale = imageScale; - } else if ( pano.linkingImageScale !== undefined ) { + } else if (pano.linkingImageScale !== undefined) { scale = pano.linkingImageScale; @@ -405,11 +393,11 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { // Infospot image - if ( imageSrc ) { + if (imageSrc) { img = imageSrc; - } else if ( pano.linkingImageURL ) { + } else if (pano.linkingImageURL) { img = pano.linkingImageURL; @@ -420,10 +408,10 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { } // Creates a new infospot - const spot = new Infospot( scale, img ); - spot.position.copy( position ); + const spot = new Infospot(scale, img); + spot.position.copy(position); spot.toPanorama = pano; - spot.addEventListener( 'click', function () { + spot.addEventListener('click', function () { /** * Viewer handler event @@ -432,29 +420,29 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @property {string} method - Viewer function name * @property {*} data - The argument to be passed into the method */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'setPanorama', data: pano } ); + this.dispatchEvent({ type: 'panolens-viewer-handler', method: 'setPanorama', data: pano }); - }.bind( this ) ); + }.bind(this)); - this.linkedSpots.push( spot ); + this.linkedSpots.push(spot); - this.add( spot ); + this.add(spot); this.visible = false; - }, + } - reset: function () { + reset() { - this.children.length = 0; + this.children.length = 0; - }, + } - setupTransitions: function () { + setupTransitions() { - this.fadeInAnimation = new TWEEN.Tween( this.material ) - .easing( TWEEN.Easing.Quartic.Out ) - .onStart( function () { + this.fadeInAnimation = new TWEEN.Tween(this.material) + .easing(TWEEN.Easing.Quartic.Out) + .onStart(function () { this.visible = true; // this.material.visible = true; @@ -464,13 +452,13 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @event Panorama#enter-fade-start * @type {object} */ - this.dispatchEvent( { type: 'enter-fade-start' } ); + this.dispatchEvent({ type: 'enter-fade-start' }); - }.bind( this ) ); + }.bind(this)); - this.fadeOutAnimation = new TWEEN.Tween( this.material ) - .easing( TWEEN.Easing.Quartic.Out ) - .onComplete( function () { + this.fadeOutAnimation = new TWEEN.Tween(this.material) + .easing(TWEEN.Easing.Quartic.Out) + .onComplete(function () { this.visible = false; // this.material.visible = true; @@ -480,39 +468,38 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @event Panorama#leave-complete * @type {object} */ - this.dispatchEvent( { type: 'leave-complete' } ); + this.dispatchEvent({ type: 'leave-complete' }); - }.bind( this ) ); + }.bind(this)); - this.enterTransition = new TWEEN.Tween( this ) - .easing( TWEEN.Easing.Quartic.Out ) - .onComplete( function () { + this.enterTransition = new TWEEN.Tween(this) + .easing(TWEEN.Easing.Quartic.Out) + .onComplete(function () { /** * Enter panorama and animation complete event * @event Panorama#enter-complete * @type {object} */ - this.dispatchEvent( { type: 'enter-complete' } ); + this.dispatchEvent({ type: 'enter-complete' }); - }.bind ( this ) ) + }.bind(this)) .start(); - this.leaveTransition = new TWEEN.Tween( this ) - .easing( TWEEN.Easing.Quartic.Out ); - - }, + this.leaveTransition = new TWEEN.Tween(this) + .easing(TWEEN.Easing.Quartic.Out); - onFadeAnimationUpdate: function () { + } + onFadeAnimationUpdate() { const alpha = this.material.opacity; const { uniforms } = this.material; - if ( uniforms && uniforms.opacity ) { + if (uniforms && uniforms.opacity) { uniforms.opacity.value = alpha; } - }, + } /** * Start fading in animation @@ -520,46 +507,46 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @instance * @fires Panorama#enter-fade-complete */ - fadeIn: function ( duration ) { + fadeIn(duration) { duration = duration >= 0 ? duration : this.animationDuration; this.fadeOutAnimation.stop(); this.fadeInAnimation - .to( { opacity: 1 }, duration ) - .onUpdate( this.onFadeAnimationUpdate.bind( this ) ) - .onComplete( function () { + .to({ opacity: 1 }, duration) + .onUpdate(this.onFadeAnimationUpdate.bind(this)) + .onComplete(function () { - this.toggleInfospotVisibility( true, duration / 2 ); + this.toggleInfospotVisibility(true, duration / 2); /** * Enter panorama fade complete event * @event Panorama#enter-fade-complete * @type {object} */ - this.dispatchEvent( { type: 'enter-fade-complete' } ); + this.dispatchEvent({ type: 'enter-fade-complete' }); - }.bind( this ) ) + }.bind(this)) .start(); - }, + } /** * Start fading out animation * @memberOf Panorama * @instance */ - fadeOut: function ( duration ) { + fadeOut(duration) { duration = duration >= 0 ? duration : this.animationDuration; this.fadeInAnimation.stop(); this.fadeOutAnimation - .to( { opacity: 0 }, duration ) - .onUpdate( this.onFadeAnimationUpdate.bind( this ) ) + .to({ opacity: 0 }, duration) + .onUpdate(this.onFadeAnimationUpdate.bind(this)) .start(); - }, + } /** * This will be called when entering a panorama @@ -568,33 +555,33 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @fires Panorama#enter * @fires Panorama#enter-start */ - onEnter: function () { + onEnter() { const duration = this.animationDuration; this.leaveTransition.stop(); this.enterTransition - .to( {}, duration ) - .onStart( function () { + .to({}, duration) + .onStart(function () { /** * Enter panorama and animation starting event * @event Panorama#enter-start * @type {object} */ - this.dispatchEvent( { type: 'enter-start' } ); - - if ( this.loaded ) { + this.dispatchEvent({ type: 'enter-start' }); + + if (this.loaded) { - this.fadeIn( duration ); + this.fadeIn(duration); } else { this.load(); } - - }.bind( this ) ) + + }.bind(this)) .start(); /** @@ -602,17 +589,17 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @event Panorama#enter * @type {object} */ - this.dispatchEvent( { type: 'enter' } ); + this.dispatchEvent({ type: 'enter' }); - this.children.forEach( child => { + this.children.forEach(child => { - child.dispatchEvent( { type: 'panorama-enter' } ); + child.dispatchEvent({ type: 'panorama-enter' }); - } ); + }); this.active = true; - }, + } /** * This will be called when leaving a panorama @@ -620,26 +607,26 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @instance * @fires Panorama#leave */ - onLeave: function () { + onLeave() { const duration = this.animationDuration; this.enterTransition.stop(); this.leaveTransition - .to( {}, duration ) - .onStart( function () { + .to({}, duration) + .onStart(function () { /** * Leave panorama and animation starting event * @event Panorama#leave-start * @type {object} */ - this.dispatchEvent( { type: 'leave-start' } ); + this.dispatchEvent({ type: 'leave-start' }); - this.fadeOut( duration ); - this.toggleInfospotVisibility( false ); + this.fadeOut(duration); + this.toggleInfospotVisibility(false); - }.bind( this ) ) + }.bind(this)) .start(); /** @@ -647,24 +634,24 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @event Panorama#leave * @type {object} */ - this.dispatchEvent( { type: 'leave' } ); + this.dispatchEvent({ type: 'leave' }); - this.children.forEach( child => { + this.children.forEach(child => { - child.dispatchEvent( { type: 'panorama-leave' } ); + child.dispatchEvent({ type: 'panorama-leave' }); - } ); + }); this.active = false; - }, + } /** * Dispose panorama * @memberOf Panorama * @instance */ - dispose: function () { + dispose() { this.infospotAnimation.stop(); this.fadeInAnimation.stop(); @@ -679,41 +666,39 @@ Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { * @property {string} method - Viewer function name * @property {*} data - The argument to be passed into the method */ - this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onPanoramaDispose', data: this } ); + this.dispatchEvent({ type: 'panolens-viewer-handler', method: 'onPanoramaDispose', data: this }); // recursive disposal on 3d objects - function recursiveDispose ( object ) { + function recursiveDispose(object) { const { geometry, material } = object; - for ( var i = object.children.length - 1; i >= 0; i-- ) { + for (var i = object.children.length - 1; i >= 0; i--) { - recursiveDispose( object.children[i] ); - object.remove( object.children[i] ); + recursiveDispose(object.children[i]); + object.remove(object.children[i]); } - if ( object instanceof Infospot ) { + if (object instanceof Infospot) { object.dispose(); } - - if ( geometry ) { geometry.dispose(); object.geometry = null; } - if ( material ) { material.dispose(); object.material = null; } + + if (geometry) { geometry.dispose(); object.geometry = null; } + if (material) { material.dispose(); object.material = null; } } - recursiveDispose( this ); + recursiveDispose(this); - if ( this.parent ) { + if (this.parent) { - this.parent.remove( this ); + this.parent.remove(this); } - } - -} ); +} export { Panorama }; \ No newline at end of file diff --git a/src/widget/Widget.js b/src/widget/Widget.js index 04de0867..59970ac8 100644 --- a/src/widget/Widget.js +++ b/src/widget/Widget.js @@ -7,48 +7,43 @@ import * as THREE from 'three'; * @constructor * @param {HTMLElement} container - A domElement where default control widget will be attached to */ -function Widget ( container ) { +class Widget extends THREE.EventDispatcher { - if ( !container ) { + constructor( container ) { + super(); + if ( !container ) { - console.warn( 'PANOLENS.Widget: No container specified' ); + console.warn( 'PANOLENS.Widget: No container specified' ); - } - - THREE.EventDispatcher.call( this ); - - this.DEFAULT_TRANSITION = 'all 0.27s ease'; - this.TOUCH_ENABLED = !!(( 'ontouchstart' in window ) || window.DocumentTouch && document instanceof DocumentTouch); - this.PREVENT_EVENT_HANDLER = function ( event ) { - event.preventDefault(); - event.stopPropagation(); - }; - - this.container = container; - - this.barElement = null; - this.fullscreenElement = null; - this.videoElement = null; - this.settingElement = null; + } - this.mainMenu = null; + this.DEFAULT_TRANSITION = 'all 0.27s ease'; + this.TOUCH_ENABLED = !!(( 'ontouchstart' in window ) || window.DocumentTouch && document instanceof DocumentTouch); + this.PREVENT_EVENT_HANDLER = function ( event ) { + event.preventDefault(); + event.stopPropagation(); + }; - this.activeMainItem = null; - this.activeSubMenu = null; - this.mask = null; + this.container = container; -} + this.barElement = null; + this.fullscreenElement = null; + this.videoElement = null; + this.settingElement = null; -Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype ), { + this.mainMenu = null; - constructor: Widget, + this.activeMainItem = null; + this.activeSubMenu = null; + this.mask = null; + } /** * Add control bar * @memberOf Widget * @instance */ - addControlBar: function () { + addControlBar () { if ( !this.container ) { @@ -141,14 +136,14 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype this.barElement = bar; - }, + } /** * Create default menu * @memberOf Widget * @instance */ - createDefaultMenu: function () { + createDefaultMenu () { var scope = this, handler; @@ -204,7 +199,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype ]; - }, + } /** * Add buttons on top of control bar @@ -212,7 +207,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype * @memberOf Widget * @instance */ - addControlButton: function ( name ) { + addControlButton ( name ) { let element; @@ -253,14 +248,14 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype this.barElement.appendChild( element ); - }, + } /** * Create modal mask * @memberOf Widget * @instance */ - createMask: function () { + createMask () { const element = document.createElement( 'div' ); element.style.position = 'absolute'; @@ -285,14 +280,14 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype return element; - }, + } /** * Create Setting button to toggle menu * @memberOf Widget * @instance */ - createSettingButton: function () { + createSettingButton () { let scope = this, item; @@ -304,7 +299,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype scope.mainMenu.toggle(); if ( this.activated ) { - + this.deactivate(); } else { @@ -346,7 +341,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype if ( scope.mainMenu && scope.mainMenu.visible ) { scope.mainMenu.hide(); - + } if ( scope.activeSubMenu && scope.activeSubMenu.visible ) { @@ -361,14 +356,14 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype scope.mainMenu.unslideAll(); } - + }; item.activated = false; return item; - }, + } /** * Create Fullscreen button @@ -377,7 +372,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype * @instance * @fires Widget#panolens-viewer-handler */ - createFullscreenButton: function () { + createFullscreenButton () { let scope = this, item, isFullscreen = false, tapSkipped = true, stylesheetId; @@ -387,9 +382,9 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype // Don't create button if no support if ( !document.fullscreenEnabled && - !document.webkitFullscreenEnabled && - !document.mozFullScreenEnabled && - !document.msFullscreenEnabled ) { + !document.webkitFullscreenEnabled && + !document.mozFullScreenEnabled && + !document.msFullscreenEnabled ) { return; } @@ -406,7 +401,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype if ( container.msRequestFullscreen ) { container.msRequestFullscreen(); } if ( container.mozRequestFullScreen ) { container.mozRequestFullScreen(); } if ( container.webkitRequestFullscreen ) { container.webkitRequestFullscreen( Element.ALLOW_KEYBOARD_INPUT ); } - + isFullscreen = true; } else { @@ -474,10 +469,10 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype sheet.innerHTML = ':-webkit-full-screen { width: 100% !important; height: 100% !important }'; document.body.appendChild( sheet ); } - + return item; - }, + } /** * Create video control container @@ -485,7 +480,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype * @instance * @return {HTMLSpanElement} - The dom element icon for video control */ - createVideoControl: function () { + createVideoControl () { const item = document.createElement( 'span' ); item.style.display = 'none'; @@ -505,7 +500,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype item.controlButton = this.createVideoControlButton(); item.seekBar = this.createVideoControlSeekbar(); - + item.appendChild( item.controlButton ); item.appendChild( item.seekBar ); @@ -527,7 +522,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype return item; - }, + } /** * Create video control button @@ -536,7 +531,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype * @return {HTMLSpanElement} - The dom element icon for video control * @fires Widget#panolens-viewer-handler */ - createVideoControlButton: function () { + createVideoControlButton () { const scope = this; @@ -586,7 +581,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype return item; - }, + } /** * Create video seekbar @@ -595,7 +590,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype * @return {HTMLSpanElement} - The dom element icon for video seekbar * @fires Widget#panolens-viewer-handler */ - createVideoControlSeekbar: function () { + createVideoControlSeekbar () { let scope = this, item, progressElement, progressElementControl, isDragging = false, mouseX, percentageNow, percentageNext; @@ -619,9 +614,9 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype function onMouseDown ( event ) { event.stopPropagation(); - + isDragging = true; - + mouseX = event.clientX || ( event.changedTouches && event.changedTouches[0].clientX ); percentageNow = parseInt( progressElement.style.width ) / 100; @@ -634,7 +629,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype if( isDragging ){ const clientX = event.clientX || ( event.changedTouches && event.changedTouches[0].clientX ); - + percentageNext = ( clientX - mouseX ) / item.clientWidth; percentageNext = percentageNow + percentageNext; @@ -754,7 +749,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype return item; - }, + } /** * Create menu item @@ -763,7 +758,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype * @instance * @return {HTMLElement} - An anchor tag element */ - createMenuItem: function ( title ) { + createMenuItem ( title ) { const scope = this; const item = document.createElement( 'a' ); @@ -808,7 +803,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype }; item.addSelection = function ( name ) { - + const selection = document.createElement( 'span' ); selection.style.fontSize = '13px'; selection.style.fontWeight = '300'; @@ -817,13 +812,13 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype this.selection = selection; this.setSelectionTitle( name ); this.appendChild( selection ); - + return this; }; item.addIcon = function ( url = DataImage.ChevronRight, left = false, flip = false ) { - + const element = document.createElement( 'span' ); element.style.float = left ? 'left' : 'right'; element.style.width = '17px'; @@ -854,20 +849,20 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype }; item.addEventListener( 'mouseenter', function () { - + this.style.backgroundColor = '#e0e0e0'; }, false ); item.addEventListener( 'mouseleave', function () { - + this.style.backgroundColor = '#fafafa'; }, false ); return item; - }, + } /** * Create menu item header @@ -876,7 +871,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype * @instance * @return {HTMLElement} - An anchor tag element */ - createMenuItemHeader: function ( title ) { + createMenuItemHeader ( title ) { const header = this.createMenuItem( title ); @@ -885,7 +880,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype return header; - }, + } /** * Create main menu @@ -894,8 +889,8 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype * @instance * @return {HTMLElement} - A span element */ - createMainMenu: function ( menus ) { - + createMainMenu ( menus ) { + let scope = this, menu = this.createMenu(); menu._width = 200; @@ -949,7 +944,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype return menu; - }, + } /** * Create sub menu @@ -959,7 +954,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype * @instance * @return {HTMLElement} - A span element */ - createSubMenu: function ( title, items ) { + createSubMenu ( title, items ) { let scope = this, menu, subMenu = this.createMenu(); @@ -1011,8 +1006,8 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype subMenu.slideAll( true ); return subMenu; - - }, + + } /** * Create general menu @@ -1020,7 +1015,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype * @instance * @return {HTMLElement} - A span element */ - createMenu: function () { + createMenu () { const scope = this; const menu = document.createElement( 'span' ); @@ -1160,7 +1155,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype return menu; - }, + } /** * Create custom item element @@ -1168,7 +1163,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype * @instance * @return {HTMLSpanElement} - The dom element icon */ - createCustomItem: function ( options = {} ) { + createCustomItem ( options = {} ) { const scope = this; const item = options.element || document.createElement( 'span' ); @@ -1182,19 +1177,19 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype item.style.backgroundRepeat = 'no-repeat'; item.style.backgroundPosition = 'center'; item.style.webkitUserSelect = - item.style.MozUserSelect = - item.style.userSelect = 'none'; + item.style.MozUserSelect = + item.style.userSelect = 'none'; item.style.position = 'relative'; item.style.pointerEvents = 'auto'; // White glow on icon item.addEventListener( scope.TOUCH_ENABLED ? 'touchstart' : 'mouseenter', function() { item.style.filter = - item.style.webkitFilter = 'drop-shadow(0 0 5px rgba(255,255,255,1))'; + item.style.webkitFilter = 'drop-shadow(0 0 5px rgba(255,255,255,1))'; }, { passive: true }); item.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'mouseleave', function() { item.style.filter = - item.style.webkitFilter = ''; + item.style.webkitFilter = ''; }, { passive: true }); this.mergeStyleOptions( item, options.style ); @@ -1212,10 +1207,10 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype if ( onDispose ) { options.onDispose(); } }; - + return item; - }, + } /** * Merge item css style @@ -1225,7 +1220,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype * @instance * @return {HTMLElement} - The same element with merged styles */ - mergeStyleOptions: function ( element, options = {} ) { + mergeStyleOptions ( element, options = {} ) { for ( let property in options ){ @@ -1239,14 +1234,14 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype return element; - }, + } /** * Dispose widgets by detaching dom elements from container * @memberOf Widget * @instance */ - dispose: function () { + dispose () { if ( this.barElement ) { this.container.removeChild( this.barElement ); @@ -1256,7 +1251,7 @@ Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype } } - -} ); + +} export { Widget }; \ No newline at end of file From 2b68e8f1ef244c48d560dbfd36878a47a0c3d0c5 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Wed, 2 Mar 2022 16:25:30 +0100 Subject: [PATCH 04/42] Revert "Remove build dir from repo" This reverts commit 5ffdf05a5a82e3914636a53a526bb614a38aed2b. --- .gitignore | 3 +- build/panolens.js | 9595 ++++++++++++++++++++++++++++++++++++++ build/panolens.min.js | 202 + build/panolens.module.js | 9563 +++++++++++++++++++++++++++++++++++++ 4 files changed, 19361 insertions(+), 2 deletions(-) create mode 100644 build/panolens.js create mode 100644 build/panolens.min.js create mode 100644 build/panolens.module.js diff --git a/.gitignore b/.gitignore index b619fbff..590a7c84 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,4 @@ bower_components ./**.DS_Store coverage/ .nyc_output -.vscode/ -build/ +.vscode/ \ No newline at end of file diff --git a/build/panolens.js b/build/panolens.js new file mode 100644 index 00000000..a47d060c --- /dev/null +++ b/build/panolens.js @@ -0,0 +1,9595 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('three')) : + typeof define === 'function' && define.amd ? define(['exports', 'three'], factory) : + (global = global || self, factory(global.PANOLENS = {}, global.THREE)); +}(this, function (exports, THREE) { 'use strict'; + + const version="0.12.1";const dependencies={three:"^0.105.2"}; + + /** + * REVISION + * @module REVISION + * @example PANOLENS.REVISION + * @type {string} revision + */ + const REVISION = version.split( '.' )[ 1 ]; + + /** + * VERSION + * @module VERSION + * @example PANOLENS.VERSION + * @type {string} version + */ + const VERSION = version; + + /** + * THREEJS REVISION + * @module THREE_REVISION + * @example PANOLENS.THREE_REVISION + * @type {string} threejs revision + */ + const THREE_REVISION = dependencies.three.split( '.' )[ 1 ]; + + /** + * THREEJS VERSION + * @module THREE_VERSION + * @example PANOLENS.THREE_VERSION + * @type {string} threejs version + */ + const THREE_VERSION = dependencies.three.replace( /[^0-9.]/g, '' ); + + /** + * CONTROLS + * @module CONTROLS + * @example PANOLENS.CONTROLS.ORBIT + * @property {number} ORBIT 0 + * @property {number} DEVICEORIENTATION 1 + */ + const CONTROLS = { ORBIT: 0, DEVICEORIENTATION: 1 }; + + /** + * MODES + * @module MODES + * @example PANOLENS.MODES.UNKNOWN + * @property {number} UNKNOWN 0 + * @property {number} NORMAL 1 + * @property {number} CARDBOARD 2 + * @property {number} STEREO 3 + */ + const MODES = { UNKNOWN: 0, NORMAL: 1, CARDBOARD: 2, STEREO: 3 }; + + /** + * Data URI Images + * @module DataImage + * @example PANOLENS.DataImage.Info + * @property {string} Info Information Icon + * @property {string} Arrow Arrow Icon + * @property {string} FullscreenEnter Fullscreen Enter Icon + * @property {string} FullscreenLeave Fullscreen Leave Icon + * @property {string} VideoPlay Video Play Icon + * @property {string} VideoPause Video Pause Icon + * @property {string} WhiteTile White Tile Icon + * @property {string} Setting Settings Icon + * @property {string} ChevronRight Chevron Right Icon + * @property {string} Check Check Icon + * @property {string} ViewIndicator View Indicator Icon + */ + const DataImage = { + Info: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAABAAAAAQADq8/hgAAADBklEQVR42u2bP08UQRiHnzFaSYCI/xoksdBIqGwIiYWRUBISExpCQ0ej38FWOmlIKKhoMPEbaCxsrrHiYrQgOSlQEaICrT+LHSPZzNzt3s3c3Hn7lHvLzvv82L2dm30XKioqKgYY062BJF0HpoA7wARwBbhsPz4DjoEG8AnYNcZ8Sx1Op8IXJM1KWpdUV3nq9m9nJV1I7VNGfEzSM0mNNqR9NOwxx1L7NRMflbQm6SSgeJ4TO8Zoat+8/LKkg4jieQ4kLaf2RtKwpJ0uiufZkTScSn5S0l5C+b/sSZrstvyMpKPU5uc4kjTTjkvpeYCkaeA1/+7hvcIZMGuMqUULQNIU8Aa4ltrWwyHwyBizGzwASSPAe+B2assW7AH3jTE/i+xcZoa12Qfy2Bo3i+5cKABl99zF1GYlWFTBeULLS0DZrOsDcDNggTXgc27bLWA64BhfgHvGmB8dHUXZ1DM0S45xliKMs9bKr+klIOkqsBrwv9JtVq1DewEAT4Ch1BYdMGQdygeg7Df4SmqDAKyoyXpCszPgITCeuvoAjFuX0gE8jljUdv7bCtiOOJ7XpdUZ8L/gdXHOA5QtYH5NXXVgbrgWWn1nwFTqaiPgdPIFcDd1tRFwOl307DwRuZgXwLvctgfA04hjOp18AcReZ6sZY16e3yDpUuQxnU6+S2AkcjEpcDr1zxOXSPgCKLSa0mc4nXwB/EpdbQScTr4AGqmrjYDTyRfAx9TVRsDp5Aug8LJyH+F0cgZg58z11BUHpO5ruGh2G3ybuuqAeF2aBfAqddUB8bq0OgP2U1cegH3aOQOMMb+BrdTVB2DLupQLwLIOnKY26IBT6+ClaQDGmO/ARmqLDtiwDn7HVkcY+EdjNoTlCI+tYhO2iUppm6HKslPUq2qQKHpUe8AFsjaUXuUQWCgqXyoAG8IuME/WkNRrnAHzZfqDSgdgQ6gBc2Td3b3CMTBXtkOsIzTIjZLnQhjcVtlcEIPZLJ0LoVvt8s/Va+3yuSAG84UJRxB98cpM9dJURUVFxSDzBxKde4Lk3/h2AAAAAElFTkSuQmCC', + Arrow: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAABAAAAAQADq8/hgAAADPklEQVR42u2bMUscQRiG30/SRaJEI1ZKUiRErNIELRUbQYSAnX8hpVUgkDYp0wgWVjYW+QcJaQzYpLojJIXhtDDEKBpj65ti58ixmdmb2ZvZ7+T2AUHudmfmeXf2bnb3O6CmpqZmgJGqOiI5AWAWwEMA0wDuArht3r4CcAagBeAbgIaI/NQOp1fhIZKLJN+SbDKcptl3keSQtk+I+BjJVyRbJaRdtEybY9p+ReKjJN+QvIwonufS9DGq7ZuXXyd5nFA8zzHJdW1vkLxDcrdC8Ty7JO9oyc+QPFCUb3NAcqZq+TmSp9rmHZySnCvjErwOIPkUwHv8+w7vF64ALIrIfrIASM4C+ADgnratgxMACyLSiB4AyREAnwE80LbswgGAJyJy4bNxyApr6wbIw4xxy3djrwCYfeeuaZsFsEbPdULXU4DZqusLgMkEA21P05EEbf8A8FhEzos28pkBLxLKL5s/r/M1kEkz9vKQHGeatf05yfmOfubNa7G5JDle5NhtBjwHMBz5yFwAWBaRT+0XzP8pZsKwcQiH2fX8Ycojb+kzxUw4ZJn7CSQXqpRPHMKCq7+iZJ71Mvdy/DftXSQ6HcJdSDaqPPKW/mPOBO+lcbvzCU35RCFM2PpwnQKzZQfdgfe0dxH5dLA6uQJ4pC2fIASrkyuA6X6QjxyC1ckVQNn7bNHlI4ZgdXIFUObiJJl8pBCsTjGfuIwA2Cv4FN7xbYjkjqsRAHuIePXoCiDF1Zk2VidXAL+1R5sAq5MrgJb2aBNgdXIF8FV7tAmwOrkCCFs73wysTtYATHFCU3vEEWm6Ci6KvgY/ao86Ik6XogDeaY86Ik6XbjPgSHvkEThCwQy45XpDRK5JbgN4GWkgUyR9H65MRQxgW0SunZ5FezK7pfwd8e8MV8UfAPdF5Jdrg8JrAbPjprZFD2wWyQP6j8ZSEufRmGlgQ9umBBvd5IOgbjFUKLu+XnWBhG+rpsFVZGUo/coJgFVf+aAATAgNACvICpL6jSsAKyH1QcEBmBD2ASwhq+7uF84ALIVWiPUEB7lQsiOEwS2VzQUxmMXSuRCqKpd/zX4rl88FMZg/mLAEcSN+MlP/aKqmpqZmkPkL0hSjwOpNKxwAAAAASUVORK5CYII=', + FullscreenEnter: 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4KICAgIDxwYXRoIGQ9Ik03IDE0SDV2NWg1di0ySDd2LTN6bS0yLTRoMlY3aDNWNUg1djV6bTEyIDdoLTN2Mmg1di01aC0ydjN6TTE0IDV2MmgzdjNoMlY1aC01eiIvPgo8L3N2Zz4=', + FullscreenLeave: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggc3R5bGU9ImZpbGw6I2ZmZiIgZD0iTTE0LDE0SDE5VjE2SDE2VjE5SDE0VjE0TTUsMTRIMTBWMTlIOFYxNkg1VjE0TTgsNUgxMFYxMEg1VjhIOFY1TTE5LDhWMTBIMTRWNUgxNlY4SDE5WiIgLz48L3N2Zz4=', + VideoPlay: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggc3R5bGU9ImZpbGw6I2ZmZiIgZD0iTTgsNS4xNFYxOS4xNEwxOSwxMi4xNEw4LDUuMTRaIiAvPjwvc3ZnPg==', + VideoPause: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggc3R5bGU9ImZpbGw6I2ZmZiIgZD0iTTE0LDE5LjE0SDE4VjUuMTRIMTRNNiwxOS4xNEgxMFY1LjE0SDZWMTkuMTRaIiAvPjwvc3ZnPg==', + WhiteTile: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIABAMAAAAGVsnJAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAB1WlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOkNvbXByZXNzaW9uPjE8L3RpZmY6Q29tcHJlc3Npb24+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlBob3RvbWV0cmljSW50ZXJwcmV0YXRpb24+MjwvdGlmZjpQaG90b21ldHJpY0ludGVycHJldGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KAtiABQAAACRQTFRFAAAAAAAABgYGBwcHHh4eKysrx8fHy8vLzMzM7OzsAAAABgYG+q7SZgAAAAp0Uk5TAP7+/v7+/v7+/iJx/a8AAAOwSURBVHja7d0hbsNAEAVQo6SFI6XEcALDcgNLvUBvEBQVhpkWVYWlhSsVFS7t5QIshRt695lEASZP+8c7a1kzDL1fz+/zyuvzp6FbvoddrL6uDd1yGZ5eXldeb18N3fIx7A+58prmhm65DfvDcd0952lu6JabFbD/zVprZj1lzcys+fj9z8xTZtbT8rv8yWlu6BYAIgAAAAAAAAAAAABAM6QXEAEAAAAAAAAAgJ2gnaAIiIA3Q2qAGgAAAAAAAAAAAAAAAAAAAAAAAAAAQJsADkVFAAAAAAA8Bj0GRUAEREAEREAEREAEREAEAAAAAAAAAAB2gnaCIiACPplRA9QANUAERAAAAEVQERQBERCBVlfAcZ3aeZobusUKMGBhV6KUElHGKBERJR6/fxExRkQZl9/lT8S1oVsuhqyYMmPKjCkzvfcCpsxohrwY0Q06EAEAAAAAAAAAAACgGdILiAAAAAAAAAAAwE7QTlAERMCbITVADQAAAAAAAAAAAAAAAAAAAAAAAAAAwKmwQ1ERAAAAAACPQY9BERABERABERABERABERABAAAAAAAAAICdoJ2gCIiAT2bUADVADRABEQAAQBFUBEVABERgEyvAlJm+V4ApM6bMmDJjyowpM6bMdN0LmDKjGfJiRDfoQAQAAAAAAAAAAACAZkgvIAIAAAAAAAAAADtBO0EREAFvhtQANQAAAAAAAAAAAAAAAAAAAAAAAAAAAKfCDkVFAAAAAAA8Bj0GRUAEREAEREAEREAEREAEAAAAAAAAAAB2gnaCIiACPplRA9QANUAERAAAAEVQERQBERCBTawAU2b6XgGmzJgyY8qMKTOmzJgy03UvYMqMZsiLEd2gAxEAAAAAAAAAAAAAmiG9gAgAAAAAAAAAAOwE7QRFQAS8GVID1AAAAAAAAAAAAAAAAAAAAAAAAAAAAJwKOxQVAQAAAADwGPQYFAEREAEREAEREAEREAERAAAAAAAAAADYCdoJioAI+GRGDVAD1AAREAEAABRBRVAEREAENrECTJnpewWYMmPKjCkzpsyYMmPKTNe9gCkzmiEvRnSDDkQAAAAAAAAAAAAAaIb0AiIAAAAAAAAAALATtBMUARHwZkgNUAMAAAAAAAAAAAAAAAAAAAAAAAAAAHAq7FBUBAAAAADAY9BjUAREQAREQAREQAREQAREAAAAAAAAAABgJ2gnKAIi4JMZNUANUANEQAQAAFAEFUEREAER2MQKMGWm7xVgyowpM50PWen9ugNGXz1XaocAFgAAAABJRU5ErkJggg==', + Setting: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAABAAAAAQADq8/hgAAADn0lEQVR42u2bzUsVURjGnyO6CPzAMnTjppAo3LTwH1CqTfaxbeOiRS37A0wXtROFVi1aRBs3LWohSIGbQAQXViBGRhG0UIRKUCpK7q/FnOB2uc6cOXNmRnGe3eW+H8/7zLln3vNxpQoVKlQ4wjBFJAFOSRqX1O7osivpvjHmU1nChBZglvSYLYJbS0EanCvIJzWK+gnsyH34/8OuMaYjb265jwCgz6N4SWq3vodbAEmnS/KtBDgoAgyU5BteAOAkMAPcBroc7PskDWfgN+wyDwBdltMMcDI3tYBnde/pHeARMNTErgd4APzweP834oeN1dMkz5DlsFNn/yyv4kdiSK4At4AO4CqwGaDwRmza2B0210qM7YhrXU59ANAq6bWkwQTTn5KO5fIE0uVYlXTeGLOXFMx1DrjlULwKKN41x6DlnIjEEQCckPRe0okCiguJr5LOGGO+xhm5jICJQ1i8LOeJJKPYEQAMKvrtt5ZdjSf2FM0Fq/sZJI2A6UNcvCz36TiDfUcAcE1SPu/U6Mm8k/TFfu6XdFb5iX3dGPM8lQfwNod3+TowBnQ3yddtv1vPIe+b1JIBiwEJ1IAJ208k5W21trWA+V/5CHAcmAtU/A2P/DcCiTAHHE8tgCVhgLvAXgYCk17Jo/yTGfLuWe7Zd72AC8CWB4n3OAz7mLytNkZabAEXMhfeQKYfWEpJZCxA3rGUOZeA/qDF15FpAz47EvlNk9neI2e3jeWCz0BbmvipNkSMMX8kuSZYM8Z8zyqAjbHmaN5mOeYjgIXrU93MWrxHrNQjrqiDkQMLHwG+OdqF3NN3jeXKzU8AoF1SzdH8XKhJUO7HZDXLMbwAwICkJUULFxe0SbqSVQAbw3Xi7Ze0ZLmGAzAKbHs0JGU1QtvAaIjCW4B7ZOvJy2qFa5a730RPtBiaz0CgnkiZi6F5fBZDVMvho7EhcuS3xJJ2hV9IupgTqaLw0hhzab8vq23xOG/r+LDsKjLgYVzxUnU0ltwK2wDezUyJmEwqXgp/PL4rvxthaeCSI+zxuA10J8ZkWdJNSb2SLkvayKHwDRu71+ZajrG941J8agALDQ3GU/a/IvMkYCPzmCbtLNEVmacNtgs5iP9fYVNEV1Q6Hez7yNZSL+J2SarTcpqiyV2iUkG0IvPFvbz5FbEn+KEk3wMjwMeSfCsBXFBdly9CAPk9ydyffpECuB5tZfVJjaKWueOSfinln6YK4lahQoUKRxd/AcRPGTcQCAUQAAAAAElFTkSuQmCC', + ChevronRight: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTguNTksMTYuNThMMTMuMTcsMTJMOC41OSw3LjQxTDEwLDZMMTYsMTJMMTAsMThMOC41OSwxNi41OFoiIC8+PC9zdmc+', + Check: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTIxLDdMOSwxOUwzLjUsMTMuNUw0LjkxLDEyLjA5TDksMTYuMTdMMTkuNTksNS41OUwyMSw3WiIgLz48L3N2Zz4=', + ViewIndicator: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBpZD0idmlldy1pbmRpY2F0b3IiIGhlaWdodD0iMzAiIHdpZHRoPSIzMCIgdmlld0JveD0iLTIuNSAtMSAzMCAzMCI+Cgk8c3R5bGUgdHlwZT0idGV4dC9jc3MiPi5zdDB7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLW1pdGVybGltaXQ6MTA7ZmlsbDpub25lO30uc3Qxe3N0cm9rZS13aWR0aDo2O3N0cm9rZS1taXRlcmxpbWl0OjEwO30KCTwvc3R5bGU+Cgk8Zz4KCQk8cGF0aCBjbGFzcz0ic3QwIiBkPSJNIDEyLjUgMCBBIDEyLjUgMTIuNSAwIDAgMCAtMTIuNSAwIEEgMTIuNSAxMi41IDAgMCAwIDEyLjUgMCIgdHJhbnNmb3JtPSJtYXRyaXgoMSwwLDAsMSwxMywxNS41KSI+PC9wYXRoPgoJCTxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0gMTMgMCBMIDEwIDIgTCAxNiAyIFoiPjwvcGF0aD4KCQk8cGF0aCBjbGFzcz0ic3QyIiBkPSJNIDIgMCBBIDIgMiAwIDAgMCAtMiAwIEEgMiAyIDAgMCAwIDIgMCIgdHJhbnNmb3JtPSJtYXRyaXgoMSwwLDAsMSwxMywxNS41KSI+PC9wYXRoPgoJCTxwYXRoIGNsYXNzPSJzdDEiIGlkPSJpbmRpY2F0b3IiIHRyYW5zZm9ybT0ibWF0cml4KDEsMCwwLDEsMTMsMTUuNSkiPjwvcGF0aD4KCTwvZz4KPC9zdmc+' + }; + + /** + * @module ImageLoader + * @description Image loader with progress based on {@link https://github.com/mrdoob/three.js/blob/master/src/loaders/ImageLoader.js} + */ + const ImageLoader = { + + /** + * Load image + * @example PANOLENS.ImageLoader.load( IMAGE_URL ) + * @method load + * @param {string} url - An image url + * @param {function} onLoad - On load callback + * @param {function} onProgress - In progress callback + * @param {function} onError - On error callback + */ + load: function ( url, onLoad = () => {}, onProgress = () => {}, onError = () => {} ) { + + // Enable cache + THREE.Cache.enabled = true; + + let cached, request, arrayBufferView, blob, urlCreator, image, reference; + + // Reference key + for (let iconName in DataImage) { + + if (DataImage.hasOwnProperty(iconName) && url === DataImage[iconName]) { + + reference = iconName; + + } + + } + + // Cached + cached = THREE.Cache.get(reference ? reference : url); + + if (cached !== undefined) { + + if (onLoad) { + + setTimeout(function () { + + onProgress({loaded: 1, total: 1}); + onLoad(cached); + + }, 0); + + } + + return cached; + + } + + // Construct a new XMLHttpRequest + urlCreator = window.URL || window.webkitURL; + image = document.createElementNS('http://www.w3.org/1999/xhtml', 'img'); + + // Add to cache + THREE.Cache.add(reference ? reference : url, image); + + const onImageLoaded = () => { + + urlCreator.revokeObjectURL(image.src); + onLoad(image); + + }; + + if (url.indexOf('data:') === 0) { + + image.addEventListener('load', onImageLoaded, false); + image.src = url; + return image; + } + + image.crossOrigin = this.crossOrigin !== undefined ? this.crossOrigin : ''; + + request = new window.XMLHttpRequest(); + request.open('GET', url, true); + if (process.env.npm_lifecycle_event !== 'test') { + request.onreadystatechange = function () { + if (this.readyState === 4 && this.status >= 400) { + onError(); + } + }; + } + request.responseType = 'arraybuffer'; + request.addEventListener( 'error', onError ); + request.addEventListener( 'progress', event => { + + if ( !event ) return; + + const { loaded, total, lengthComputable } = event; + + if ( lengthComputable ) { + + onProgress( { loaded, total } ); + + } + + } ); + + request.addEventListener( 'loadend', event => { + + if ( !event ) return; + const { currentTarget: { response } } = event; + + arrayBufferView = new Uint8Array( response ); + blob = new window.Blob( [ arrayBufferView ] ); + + image.addEventListener( 'load', onImageLoaded, false ); + image.src = urlCreator.createObjectURL( blob ); + + } ); + + request.send(null); + + } + + }; + + /** + * @module TextureLoader + * @description Texture loader based on {@link https://github.com/mrdoob/three.js/blob/master/src/loaders/TextureLoader.js} + */ + const TextureLoader = { + + /** + * Load image texture + * @example PANOLENS.TextureLoader.load( IMAGE_URL ) + * @method load + * @param {string} url - An image url + * @param {function} onLoad - On load callback + * @param {function} onProgress - In progress callback + * @param {function} onError - On error callback + * @return {THREE.Texture} - Image texture + */ + load: function ( url, onLoad = () => {}, onProgress, onError ) { + + var texture = new THREE.Texture(); + + ImageLoader.load( url, function ( image ) { + + texture.image = image; + + // JPEGs can't have an alpha channel, so memory can be saved by storing them as RGB. + const isJPEG = url.search( /\.(jpg|jpeg)$/ ) > 0 || url.search( /^data\:image\/jpeg/ ) === 0; + + texture.format = isJPEG ? THREE.RGBFormat : THREE.RGBAFormat; + texture.needsUpdate = true; + + onLoad( texture ); + + }, onProgress, onError ); + + return texture; + + } + + }; + + /** + * @module CubeTextureLoader + * @description Cube Texture Loader based on {@link https://github.com/mrdoob/three.js/blob/master/src/loaders/CubeTextureLoader.js} + */ + const CubeTextureLoader = { + + /** + * Load 6 images as a cube texture + * @example PANOLENS.CubeTextureLoader.load( [ 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' ] ) + * @method load + * @param {array} urls - array of 6 urls to images, one for each side of the CubeTexture. The urls should be specified in the following order: pos-x, neg-x, pos-y, neg-y, pos-z, neg-z + * @param {function} onLoad - On load callback + * @param {function} onProgress - In progress callback + * @param {function} onError - On error callback + * @return {THREE.CubeTexture} - Cube texture + */ + load: function ( urls, onLoad = () => {}, onProgress = () => {}, onError ) { + + var texture, loaded, progress, all, loadings; + + texture = new THREE.CubeTexture( [] ); + + loaded = 0; + progress = {}; + all = {}; + + urls.map( function ( url, index ) { + + ImageLoader.load( url, function ( image ) { + + texture.images[ index ] = image; + + loaded++; + + if ( loaded === 6 ) { + + texture.needsUpdate = true; + + onLoad( texture ); + + } + + }, function ( event ) { + + progress[ index ] = { loaded: event.loaded, total: event.total }; + + all.loaded = 0; + all.total = 0; + loadings = 0; + + for ( var i in progress ) { + + loadings++; + all.loaded += progress[ i ].loaded; + all.total += progress[ i ].total; + + } + + if ( loadings < 6 ) { + + all.total = all.total / loadings * 6; + + } + + onProgress( all ); + + }, onError ); + + } ); + + return texture; + + } + + }; + + /** + * @classdesc User Media + * @constructor + * @param {object} [constraints={ video: { width: { ideal: 1920 }, height: { ideal: 1080 }, facingMode: { exact: 'environment' } }, audio: false }] + */ + function Media ( constraints ) { + + const defaultConstraints = { video: { width: { ideal: 1920 }, height: { ideal: 1080 }, facingMode: { exact: 'environment' } }, audio: false }; + + this.constraints = Object.assign( defaultConstraints, constraints ); + + this.container = null; + this.scene = null; + this.element = null; + this.devices = []; + this.stream = null; + this.ratioScalar = 1; + this.videoDeviceIndex = 0; + + } + Media.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype ), { + + setContainer: function ( container ) { + + this.container = container; + + }, + + setScene: function ( scene ) { + + this.scene = scene; + + }, + + /** + * Enumerate devices + * @memberOf Media + * @instance + * @returns {Promise} + */ + enumerateDevices: function () { + + const devices = this.devices; + const resolvedPromise = new Promise( resolve => { resolve( devices ); } ); + + return devices.length > 0 ? resolvedPromise : window.navigator.mediaDevices.enumerateDevices(); + + }, + + /** + * Switch to next available video device + * @memberOf Media + * @instance + */ + switchNextVideoDevice: function () { + + const stop = this.stop.bind( this ); + const start = this.start.bind( this ); + const setVideDeviceIndex = this.setVideDeviceIndex.bind( this ); + + let index = this.videoDeviceIndex; + + this.getDevices( 'video' ) + .then( devices => { + stop(); + index++; + if ( index >= devices.length ) { + setVideDeviceIndex( 0 ); + index--; + } else { + setVideDeviceIndex( index ); + } + + start( devices[ index ] ); + + + } ); + + }, + + /** + * Get devices + * @param {string} type - type keyword to match device.kind + * @memberOf Media + * @instance + */ + getDevices: function ( type = 'video' ) { + + const devices = this.devices; + const validate = _devices => { + + return _devices.map( device => { + + if ( !devices.includes( device ) ) { devices.push( device ); } + return device; + + } ); + + }; + const filter = _devices => { + + const reg = new RegExp( type, 'i' ); + return _devices.filter( device => reg.test( device.kind ) ); + + }; + + return this.enumerateDevices() + .then( validate ) + .then( filter ); + + }, + + /** + * Get user media + * @param {MediaStreamConstraints} constraints + * @memberOf Media + * @instance + */ + getUserMedia: function ( constraints ) { + + const setMediaStream = this.setMediaStream.bind( this ); + const playVideo = this.playVideo.bind( this ); + const onCatchError = error => { console.warn( `PANOLENS.Media: ${error}` ); }; + + return window.navigator.mediaDevices.getUserMedia( constraints ) + .then( setMediaStream ) + .then( playVideo ) + .catch( onCatchError ); + + }, + + /** + * Set video device index + * @param {number} index + * @memberOf Media + * @instance + */ + setVideDeviceIndex: function ( index ) { + + this.videoDeviceIndex = index; + + }, + + /** + * Start streaming + * @param {MediaDeviceInfo} [targetDevice] + * @memberOf Media + * @instance + */ + start: function( targetDevice ) { + + const constraints = this.constraints; + const getUserMedia = this.getUserMedia.bind( this ); + const onVideoDevices = devices => { + + if ( !devices || devices.length === 0 ) { + + throw Error( 'no video device found' ); + + } + + const device = targetDevice || devices[ 0 ]; + constraints.video.deviceId = device.deviceId; + + return getUserMedia( constraints ); + + }; + + this.element = this.createVideoElement(); + + return this.getDevices().then( onVideoDevices ); + + }, + + /** + * Stop streaming + * @memberOf Media + * @instance + */ + stop: function () { + + const stream = this.stream; + + if ( stream && stream.active ) { + + const track = stream.getTracks()[ 0 ]; + + track.stop(); + + window.removeEventListener( 'resize', this.onWindowResize.bind( this ) ); + + this.element = null; + this.stream = null; + + } + + }, + + /** + * Set media stream + * @param {MediaStream} stream + * @memberOf Media + * @instance + */ + setMediaStream: function ( stream ) { + + this.stream = stream; + this.element.srcObject = stream; + + if ( this.scene ) { + + this.scene.background = this.createVideoTexture(); + + } + + window.addEventListener( 'resize', this.onWindowResize.bind( this ) ); + + }, + + /** + * Play video element + * @memberOf Media + * @instance + */ + playVideo: function () { + + const { element } = this; + + if ( element ) { + + element.play(); + this.dispatchEvent( { type: 'play' } ); + + } + + }, + + /** + * Pause video element + * @memberOf Media + * @instance + */ + pauseVideo: function () { + + const { element } = this; + + if ( element ) { + + element.pause(); + this.dispatchEvent( { type: 'pause' } ); + + } + + }, + + /** + * Create video texture + * @memberOf Media + * @instance + * @returns {THREE.VideoTexture} + */ + createVideoTexture: function () { + + const video = this.element; + const texture = new THREE.VideoTexture( video ); + + texture.generateMipmaps = false; + texture.minFilter = THREE.LinearFilter; + texture.magFilter = THREE.LinearFilter; + texture.format = THREE.RGBFormat; + texture.center.set( 0.5, 0.5 ); + + video.addEventListener( 'canplay', this.onWindowResize.bind( this ) ); + + return texture; + + }, + + /** + * Create video element + * @memberOf Media + * @instance + * @returns {HTMLVideoElement} + * @fires Media#canplay + */ + createVideoElement: function() { + + const dispatchEvent = this.dispatchEvent.bind( this ); + const video = document.createElement( 'video' ); + + /** + * Video can play event + * @type {object} + * @event Media#canplay + */ + const canPlay = () => dispatchEvent( { type: 'canplay' } ); + + video.setAttribute( 'autoplay', '' ); + video.setAttribute( 'muted', '' ); + video.setAttribute( 'playsinline', '' ); + + video.style.position = 'absolute'; + video.style.top = '0'; + video.style.left = '0'; + video.style.width = '100%'; + video.style.height = '100%'; + video.style.objectPosition = 'center'; + video.style.objectFit = 'cover'; + video.style.display = this.scene ? 'none' : ''; + + video.addEventListener( 'canplay', canPlay ); + + return video; + + }, + + /** + * On window resize event + * @param {Event} event + * @memberOf Media + * @instance + */ + onWindowResize: function () { + + if ( this.element && this.element.videoWidth && this.element.videoHeight && this.scene ) { + + const { clientWidth: width, clientHeight: height } = this.container; + const texture = this.scene.background; + const { videoWidth, videoHeight } = this.element; + const cameraRatio = videoHeight / videoWidth; + const viewportRatio = this.container ? width / height : 1.0; + const ratio = cameraRatio * viewportRatio * this.ratioScalar; + + if ( width > height ) { + texture.repeat.set( ratio, 1 ); + } else { + texture.repeat.set( 1, 1 / ratio ); + } + + } + + } + + } ); + + /** + * @classdesc Reticle 3D Sprite + * @constructor + * @param {THREE.Color} [color=0xffffff] - Color of the reticle sprite + * @param {boolean} [autoSelect=true] - Auto selection + * @param {number} [dwellTime=1500] - Duration for dwelling sequence to complete + */ + + function Reticle ( color = 0xffffff, autoSelect = true, dwellTime = 1500 ) { + + this.dpr = window.devicePixelRatio; + + const { canvas, context } = this.createCanvas(); + const material = new THREE.SpriteMaterial( { color, map: this.createCanvasTexture( canvas ) } ); + + THREE.Sprite.call( this, material ); + + this.canvasWidth = canvas.width; + this.canvasHeight = canvas.height; + this.context = context; + this.color = color instanceof THREE.Color ? color : new THREE.Color( color ); + + this.autoSelect = autoSelect; + this.dwellTime = dwellTime; + this.rippleDuration = 500; + this.position.z = -10; + this.center.set( 0.5, 0.5 ); + this.scale.set( 0.5, 0.5, 1 ); + + this.startTimestamp = null; + this.timerId = null; + this.callback = null; + + this.frustumCulled = false; + + this.updateCanvasArcByProgress( 0 ); + + } + Reticle.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { + + constructor: Reticle, + + /** + * Set material color + * @param {THREE.Color} color + * @memberOf Reticle + * @instance + */ + setColor: function ( color ) { + + this.material.color.copy( color instanceof THREE.Color ? color : new THREE.Color( color ) ); + + }, + + /** + * Create canvas texture + * @param {HTMLCanvasElement} canvas + * @memberOf Reticle + * @instance + * @returns {THREE.CanvasTexture} + */ + createCanvasTexture: function ( canvas ) { + + const texture = new THREE.CanvasTexture( canvas ); + texture.minFilter = THREE.LinearFilter; + texture.magFilter = THREE.LinearFilter; + texture.generateMipmaps = false; + + return texture; + + }, + + /** + * Create canvas element + * @memberOf Reticle + * @instance + * @returns {object} object + * @returns {HTMLCanvasElement} object.canvas + * @returns {CanvasRenderingContext2D} object.context + */ + createCanvas: function () { + + const width = 32; + const height = 32; + const canvas = document.createElement( 'canvas' ); + const context = canvas.getContext( '2d' ); + const dpr = this.dpr; + + canvas.width = width * dpr; + canvas.height = height * dpr; + context.scale( dpr, dpr ); + + context.shadowBlur = 5; + context.shadowColor = 'rgba(200,200,200,0.9)'; + + return { canvas, context }; + + }, + + /** + * Update canvas arc by progress + * @param {number} progress + * @memberOf Reticle + * @instance + */ + updateCanvasArcByProgress: function ( progress ) { + + const context = this.context; + const { canvasWidth, canvasHeight, material } = this; + const dpr = this.dpr; + const degree = progress * Math.PI * 2; + const color = this.color.getStyle(); + const x = canvasWidth * 0.5 / dpr; + const y = canvasHeight * 0.5 / dpr; + const lineWidth = 3; + + context.clearRect( 0, 0, canvasWidth, canvasHeight ); + context.beginPath(); + + if ( progress === 0 ) { + context.arc( x, y, canvasWidth / 16, 0, 2 * Math.PI ); + context.fillStyle = color; + context.fill(); + } else { + context.arc( x, y, canvasWidth / 4 - lineWidth, -Math.PI / 2, -Math.PI / 2 + degree ); + context.strokeStyle = color; + context.lineWidth = lineWidth; + context.stroke(); + } + + context.closePath(); + + material.map.needsUpdate = true; + + }, + + /** + * Ripple effect + * @memberOf Reticle + * @instance + * @fires Reticle#reticle-ripple-start + * @fires Reticle#reticle-ripple-end + */ + ripple: function () { + + const context = this.context; + const { canvasWidth, canvasHeight, material } = this; + const duration = this.rippleDuration; + const timestamp = performance.now(); + const color = this.color; + const dpr = this.dpr; + const x = canvasWidth * 0.5 / dpr; + const y = canvasHeight * 0.5 / dpr; + + const update = () => { + + const timerId = window.requestAnimationFrame( update ); + const elapsed = performance.now() - timestamp; + const progress = elapsed / duration; + const opacity = 1.0 - progress > 0 ? 1.0 - progress : 0; + const radius = progress * canvasWidth * 0.5 / dpr; + + context.clearRect( 0, 0, canvasWidth, canvasHeight ); + context.beginPath(); + context.arc( x, y, radius, 0, Math.PI * 2 ); + context.fillStyle = `rgba(${color.r * 255}, ${color.g * 255}, ${color.b * 255}, ${opacity})`; + context.fill(); + context.closePath(); + + if ( progress >= 1.0 ) { + + window.cancelAnimationFrame( timerId ); + this.updateCanvasArcByProgress( 0 ); + + /** + * Reticle ripple end event + * @type {object} + * @event Reticle#reticle-ripple-end + */ + this.dispatchEvent( { type: 'reticle-ripple-end' } ); + + } + + material.map.needsUpdate = true; + + }; + + /** + * Reticle ripple start event + * @type {object} + * @event Reticle#reticle-ripple-start + */ + this.dispatchEvent( { type: 'reticle-ripple-start' } ); + + update(); + + }, + + /** + * Make reticle visible + * @memberOf Reticle + * @instance + */ + show: function () { + + this.visible = true; + + }, + + /** + * Make reticle invisible + * @memberOf Reticle + * @instance + */ + hide: function () { + + this.visible = false; + + }, + + /** + * Start dwelling + * @param {function} callback + * @memberOf Reticle + * @instance + * @fires Reticle#reticle-start + */ + start: function ( callback ) { + + if ( !this.autoSelect ) { + + return; + + } + + /** + * Reticle start event + * @type {object} + * @event Reticle#reticle-start + */ + this.dispatchEvent( { type: 'reticle-start' } ); + + this.startTimestamp = performance.now(); + this.callback = callback; + this.update(); + + }, + + /** + * End dwelling + * @memberOf Reticle + * @instance + * @fires Reticle#reticle-end + */ + end: function(){ + + if ( !this.startTimestamp ) { return; } + + window.cancelAnimationFrame( this.timerId ); + + this.updateCanvasArcByProgress( 0 ); + this.callback = null; + this.timerId = null; + this.startTimestamp = null; + + /** + * Reticle end event + * @type {object} + * @event Reticle#reticle-end + */ + this.dispatchEvent( { type: 'reticle-end' } ); + + }, + + /** + * Update dwelling + * @memberOf Reticle + * @instance + * @fires Reticle#reticle-update + */ + update: function () { + + this.timerId = window.requestAnimationFrame( this.update.bind( this ) ); + + const elapsed = performance.now() - this.startTimestamp; + const progress = elapsed / this.dwellTime; + + this.updateCanvasArcByProgress( progress ); + + /** + * Reticle update event + * @type {object} + * @event Reticle#reticle-update + */ + this.dispatchEvent( { type: 'reticle-update', progress } ); + + if ( progress >= 1.0 ) { + + window.cancelAnimationFrame( this.timerId ); + if ( this.callback ) { this.callback(); } + this.end(); + this.ripple(); + + } + + } + + } ); + + function createCommonjsModule(fn, module) { + return module = { exports: {} }, fn(module, module.exports), module.exports; + } + + var Tween = createCommonjsModule(function (module, exports) { + /** + * Tween.js - Licensed under the MIT license + * https://github.com/tweenjs/tween.js + * ---------------------------------------------- + * + * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors. + * Thank you all, you're awesome! + */ + + + var _Group = function () { + this._tweens = {}; + this._tweensAddedDuringUpdate = {}; + }; + + _Group.prototype = { + getAll: function () { + + return Object.keys(this._tweens).map(function (tweenId) { + return this._tweens[tweenId]; + }.bind(this)); + + }, + + removeAll: function () { + + this._tweens = {}; + + }, + + add: function (tween) { + + this._tweens[tween.getId()] = tween; + this._tweensAddedDuringUpdate[tween.getId()] = tween; + + }, + + remove: function (tween) { + + delete this._tweens[tween.getId()]; + delete this._tweensAddedDuringUpdate[tween.getId()]; + + }, + + update: function (time, preserve) { + + var tweenIds = Object.keys(this._tweens); + + if (tweenIds.length === 0) { + return false; + } + + time = time !== undefined ? time : TWEEN.now(); + + // Tweens are updated in "batches". If you add a new tween during an update, then the + // new tween will be updated in the next batch. + // If you remove a tween during an update, it may or may not be updated. However, + // if the removed tween was added during the current batch, then it will not be updated. + while (tweenIds.length > 0) { + this._tweensAddedDuringUpdate = {}; + + for (var i = 0; i < tweenIds.length; i++) { + + var tween = this._tweens[tweenIds[i]]; + + if (tween && tween.update(time) === false) { + tween._isPlaying = false; + + if (!preserve) { + delete this._tweens[tweenIds[i]]; + } + } + } + + tweenIds = Object.keys(this._tweensAddedDuringUpdate); + } + + return true; + + } + }; + + var TWEEN = new _Group(); + + TWEEN.Group = _Group; + TWEEN._nextId = 0; + TWEEN.nextId = function () { + return TWEEN._nextId++; + }; + + + // Include a performance.now polyfill. + // In node.js, use process.hrtime. + if (typeof (self) === 'undefined' && typeof (process) !== 'undefined' && process.hrtime) { + TWEEN.now = function () { + var time = process.hrtime(); + + // Convert [seconds, nanoseconds] to milliseconds. + return time[0] * 1000 + time[1] / 1000000; + }; + } + // In a browser, use self.performance.now if it is available. + else if (typeof (self) !== 'undefined' && + self.performance !== undefined && + self.performance.now !== undefined) { + // This must be bound, because directly assigning this function + // leads to an invocation exception in Chrome. + TWEEN.now = self.performance.now.bind(self.performance); + } + // Use Date.now if it is available. + else if (Date.now !== undefined) { + TWEEN.now = Date.now; + } + // Otherwise, use 'new Date().getTime()'. + else { + TWEEN.now = function () { + return new Date().getTime(); + }; + } + + + TWEEN.Tween = function (object, group) { + this._object = object; + this._valuesStart = {}; + this._valuesEnd = {}; + this._valuesStartRepeat = {}; + this._duration = 1000; + this._repeat = 0; + this._repeatDelayTime = undefined; + this._yoyo = false; + this._isPlaying = false; + this._reversed = false; + this._delayTime = 0; + this._startTime = null; + this._easingFunction = TWEEN.Easing.Linear.None; + this._interpolationFunction = TWEEN.Interpolation.Linear; + this._chainedTweens = []; + this._onStartCallback = null; + this._onStartCallbackFired = false; + this._onUpdateCallback = null; + this._onRepeatCallback = null; + this._onCompleteCallback = null; + this._onStopCallback = null; + this._group = group || TWEEN; + this._id = TWEEN.nextId(); + + }; + + TWEEN.Tween.prototype = { + getId: function () { + return this._id; + }, + + isPlaying: function () { + return this._isPlaying; + }, + + to: function (properties, duration) { + + this._valuesEnd = Object.create(properties); + + if (duration !== undefined) { + this._duration = duration; + } + + return this; + + }, + + duration: function duration(d) { + this._duration = d; + return this; + }, + + start: function (time) { + + this._group.add(this); + + this._isPlaying = true; + + this._onStartCallbackFired = false; + + this._startTime = time !== undefined ? typeof time === 'string' ? TWEEN.now() + parseFloat(time) : time : TWEEN.now(); + this._startTime += this._delayTime; + + for (var property in this._valuesEnd) { + + // Check if an Array was provided as property value + if (this._valuesEnd[property] instanceof Array) { + + if (this._valuesEnd[property].length === 0) { + continue; + } + + // Create a local copy of the Array with the start value at the front + this._valuesEnd[property] = [this._object[property]].concat(this._valuesEnd[property]); + + } + + // If `to()` specifies a property that doesn't exist in the source object, + // we should not set that property in the object + if (this._object[property] === undefined) { + continue; + } + + // Save the starting value. + this._valuesStart[property] = this._object[property]; + + if ((this._valuesStart[property] instanceof Array) === false) { + this._valuesStart[property] *= 1.0; // Ensures we're using numbers, not strings + } + + this._valuesStartRepeat[property] = this._valuesStart[property] || 0; + + } + + return this; + + }, + + stop: function () { + + if (!this._isPlaying) { + return this; + } + + this._group.remove(this); + this._isPlaying = false; + + if (this._onStopCallback !== null) { + this._onStopCallback(this._object); + } + + this.stopChainedTweens(); + return this; + + }, + + end: function () { + + this.update(Infinity); + return this; + + }, + + stopChainedTweens: function () { + + for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { + this._chainedTweens[i].stop(); + } + + }, + + group: function (group) { + this._group = group; + return this; + }, + + delay: function (amount) { + + this._delayTime = amount; + return this; + + }, + + repeat: function (times) { + + this._repeat = times; + return this; + + }, + + repeatDelay: function (amount) { + + this._repeatDelayTime = amount; + return this; + + }, + + yoyo: function (yoyo) { + + this._yoyo = yoyo; + return this; + + }, + + easing: function (easingFunction) { + + this._easingFunction = easingFunction; + return this; + + }, + + interpolation: function (interpolationFunction) { + + this._interpolationFunction = interpolationFunction; + return this; + + }, + + chain: function () { + + this._chainedTweens = arguments; + return this; + + }, + + onStart: function (callback) { + + this._onStartCallback = callback; + return this; + + }, + + onUpdate: function (callback) { + + this._onUpdateCallback = callback; + return this; + + }, + + onRepeat: function onRepeat(callback) { + + this._onRepeatCallback = callback; + return this; + + }, + + onComplete: function (callback) { + + this._onCompleteCallback = callback; + return this; + + }, + + onStop: function (callback) { + + this._onStopCallback = callback; + return this; + + }, + + update: function (time) { + + var property; + var elapsed; + var value; + + if (time < this._startTime) { + return true; + } + + if (this._onStartCallbackFired === false) { + + if (this._onStartCallback !== null) { + this._onStartCallback(this._object); + } + + this._onStartCallbackFired = true; + } + + elapsed = (time - this._startTime) / this._duration; + elapsed = (this._duration === 0 || elapsed > 1) ? 1 : elapsed; + + value = this._easingFunction(elapsed); + + for (property in this._valuesEnd) { + + // Don't update properties that do not exist in the source object + if (this._valuesStart[property] === undefined) { + continue; + } + + var start = this._valuesStart[property] || 0; + var end = this._valuesEnd[property]; + + if (end instanceof Array) { + + this._object[property] = this._interpolationFunction(end, value); + + } else { + + // Parses relative end values with start as base (e.g.: +10, -3) + if (typeof (end) === 'string') { + + if (end.charAt(0) === '+' || end.charAt(0) === '-') { + end = start + parseFloat(end); + } else { + end = parseFloat(end); + } + } + + // Protect against non numeric properties. + if (typeof (end) === 'number') { + this._object[property] = start + (end - start) * value; + } + + } + + } + + if (this._onUpdateCallback !== null) { + this._onUpdateCallback(this._object, elapsed); + } + + if (elapsed === 1) { + + if (this._repeat > 0) { + + if (isFinite(this._repeat)) { + this._repeat--; + } + + // Reassign starting values, restart by making startTime = now + for (property in this._valuesStartRepeat) { + + if (typeof (this._valuesEnd[property]) === 'string') { + this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]); + } + + if (this._yoyo) { + var tmp = this._valuesStartRepeat[property]; + + this._valuesStartRepeat[property] = this._valuesEnd[property]; + this._valuesEnd[property] = tmp; + } + + this._valuesStart[property] = this._valuesStartRepeat[property]; + + } + + if (this._yoyo) { + this._reversed = !this._reversed; + } + + if (this._repeatDelayTime !== undefined) { + this._startTime = time + this._repeatDelayTime; + } else { + this._startTime = time + this._delayTime; + } + + if (this._onRepeatCallback !== null) { + this._onRepeatCallback(this._object); + } + + return true; + + } else { + + if (this._onCompleteCallback !== null) { + + this._onCompleteCallback(this._object); + } + + for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { + // Make the chained tweens start exactly at the time they should, + // even if the `update()` method was called way past the duration of the tween + this._chainedTweens[i].start(this._startTime + this._duration); + } + + return false; + + } + + } + + return true; + + } + }; + + + TWEEN.Easing = { + + Linear: { + + None: function (k) { + + return k; + + } + + }, + + Quadratic: { + + In: function (k) { + + return k * k; + + }, + + Out: function (k) { + + return k * (2 - k); + + }, + + InOut: function (k) { + + if ((k *= 2) < 1) { + return 0.5 * k * k; + } + + return - 0.5 * (--k * (k - 2) - 1); + + } + + }, + + Cubic: { + + In: function (k) { + + return k * k * k; + + }, + + Out: function (k) { + + return --k * k * k + 1; + + }, + + InOut: function (k) { + + if ((k *= 2) < 1) { + return 0.5 * k * k * k; + } + + return 0.5 * ((k -= 2) * k * k + 2); + + } + + }, + + Quartic: { + + In: function (k) { + + return k * k * k * k; + + }, + + Out: function (k) { + + return 1 - (--k * k * k * k); + + }, + + InOut: function (k) { + + if ((k *= 2) < 1) { + return 0.5 * k * k * k * k; + } + + return - 0.5 * ((k -= 2) * k * k * k - 2); + + } + + }, + + Quintic: { + + In: function (k) { + + return k * k * k * k * k; + + }, + + Out: function (k) { + + return --k * k * k * k * k + 1; + + }, + + InOut: function (k) { + + if ((k *= 2) < 1) { + return 0.5 * k * k * k * k * k; + } + + return 0.5 * ((k -= 2) * k * k * k * k + 2); + + } + + }, + + Sinusoidal: { + + In: function (k) { + + return 1 - Math.cos(k * Math.PI / 2); + + }, + + Out: function (k) { + + return Math.sin(k * Math.PI / 2); + + }, + + InOut: function (k) { + + return 0.5 * (1 - Math.cos(Math.PI * k)); + + } + + }, + + Exponential: { + + In: function (k) { + + return k === 0 ? 0 : Math.pow(1024, k - 1); + + }, + + Out: function (k) { + + return k === 1 ? 1 : 1 - Math.pow(2, - 10 * k); + + }, + + InOut: function (k) { + + if (k === 0) { + return 0; + } + + if (k === 1) { + return 1; + } + + if ((k *= 2) < 1) { + return 0.5 * Math.pow(1024, k - 1); + } + + return 0.5 * (- Math.pow(2, - 10 * (k - 1)) + 2); + + } + + }, + + Circular: { + + In: function (k) { + + return 1 - Math.sqrt(1 - k * k); + + }, + + Out: function (k) { + + return Math.sqrt(1 - (--k * k)); + + }, + + InOut: function (k) { + + if ((k *= 2) < 1) { + return - 0.5 * (Math.sqrt(1 - k * k) - 1); + } + + return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1); + + } + + }, + + Elastic: { + + In: function (k) { + + if (k === 0) { + return 0; + } + + if (k === 1) { + return 1; + } + + return -Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI); + + }, + + Out: function (k) { + + if (k === 0) { + return 0; + } + + if (k === 1) { + return 1; + } + + return Math.pow(2, -10 * k) * Math.sin((k - 0.1) * 5 * Math.PI) + 1; + + }, + + InOut: function (k) { + + if (k === 0) { + return 0; + } + + if (k === 1) { + return 1; + } + + k *= 2; + + if (k < 1) { + return -0.5 * Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI); + } + + return 0.5 * Math.pow(2, -10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI) + 1; + + } + + }, + + Back: { + + In: function (k) { + + var s = 1.70158; + + return k * k * ((s + 1) * k - s); + + }, + + Out: function (k) { + + var s = 1.70158; + + return --k * k * ((s + 1) * k + s) + 1; + + }, + + InOut: function (k) { + + var s = 1.70158 * 1.525; + + if ((k *= 2) < 1) { + return 0.5 * (k * k * ((s + 1) * k - s)); + } + + return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2); + + } + + }, + + Bounce: { + + In: function (k) { + + return 1 - TWEEN.Easing.Bounce.Out(1 - k); + + }, + + Out: function (k) { + + if (k < (1 / 2.75)) { + return 7.5625 * k * k; + } else if (k < (2 / 2.75)) { + return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75; + } else if (k < (2.5 / 2.75)) { + return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375; + } else { + return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375; + } + + }, + + InOut: function (k) { + + if (k < 0.5) { + return TWEEN.Easing.Bounce.In(k * 2) * 0.5; + } + + return TWEEN.Easing.Bounce.Out(k * 2 - 1) * 0.5 + 0.5; + + } + + } + + }; + + TWEEN.Interpolation = { + + Linear: function (v, k) { + + var m = v.length - 1; + var f = m * k; + var i = Math.floor(f); + var fn = TWEEN.Interpolation.Utils.Linear; + + if (k < 0) { + return fn(v[0], v[1], f); + } + + if (k > 1) { + return fn(v[m], v[m - 1], m - f); + } + + return fn(v[i], v[i + 1 > m ? m : i + 1], f - i); + + }, + + Bezier: function (v, k) { + + var b = 0; + var n = v.length - 1; + var pw = Math.pow; + var bn = TWEEN.Interpolation.Utils.Bernstein; + + for (var i = 0; i <= n; i++) { + b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i); + } + + return b; + + }, + + CatmullRom: function (v, k) { + + var m = v.length - 1; + var f = m * k; + var i = Math.floor(f); + var fn = TWEEN.Interpolation.Utils.CatmullRom; + + if (v[0] === v[m]) { + + if (k < 0) { + i = Math.floor(f = m * (1 + k)); + } + + return fn(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i); + + } else { + + if (k < 0) { + return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]); + } + + if (k > 1) { + return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]); + } + + return fn(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i); + + } + + }, + + Utils: { + + Linear: function (p0, p1, t) { + + return (p1 - p0) * t + p0; + + }, + + Bernstein: function (n, i) { + + var fc = TWEEN.Interpolation.Utils.Factorial; + + return fc(n) / fc(i) / fc(n - i); + + }, + + Factorial: (function () { + + var a = [1]; + + return function (n) { + + var s = 1; + + if (a[n]) { + return a[n]; + } + + for (var i = n; i > 1; i--) { + s *= i; + } + + a[n] = s; + return s; + + }; + + })(), + + CatmullRom: function (p0, p1, p2, p3, t) { + + var v0 = (p2 - p0) * 0.5; + var v1 = (p3 - p1) * 0.5; + var t2 = t * t; + var t3 = t * t2; + + return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (- 3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1; + + } + + } + + }; + + // UMD (Universal Module Definition) + (function (root) { + + { + + // Node.js + module.exports = TWEEN; + + } + + })(); + }); + + /** + * @classdesc Information spot attached to panorama + * @constructor + * @param {number} [scale=300] - Default scale + * @param {string} [imageSrc=PANOLENS.DataImage.Info] - Image overlay info + * @param {boolean} [animated=true] - Enable default hover animation + */ + function Infospot ( scale = 300, imageSrc, animated ) { + + const duration = 500, scaleFactor = 1.3; + + imageSrc = imageSrc || DataImage.Info; + + THREE.Sprite.call( this ); + + this.type = 'infospot'; + + this.animated = animated !== undefined ? animated : true; + this.isHovering = false; + + /* + * TODO: Three.js bug hotfix for sprite raycasting r104 + * https://github.com/mrdoob/three.js/issues/14624 + */ + this.frustumCulled = false; + + this.element = null; + this.toPanorama = null; + this.cursorStyle = null; + + this.mode = MODES.NORMAL; + + this.scale.set( scale, scale, 1 ); + this.rotation.y = Math.PI; + + this.container = null; + + this.originalRaycast = this.raycast; + + // Event Handler + this.HANDLER_FOCUS = null; + + this.material.side = THREE.DoubleSide; + this.material.depthTest = false; + this.material.transparent = true; + this.material.opacity = 0; + + this.scaleUpAnimation = new Tween.Tween(); + this.scaleDownAnimation = new Tween.Tween(); + + + const postLoad = function ( texture ) { + + if ( !this.material ) { return; } + + const ratio = texture.image.width / texture.image.height; + const textureScale = new THREE.Vector3(); + + texture.image.width = texture.image.naturalWidth || 64; + texture.image.height = texture.image.naturalHeight || 64; + + this.scale.set( ratio * scale, scale, 1 ); + + textureScale.copy( this.scale ); + + this.scaleUpAnimation = new Tween.Tween( this.scale ) + .to( { x: textureScale.x * scaleFactor, y: textureScale.y * scaleFactor }, duration ) + .easing( Tween.Easing.Elastic.Out ); + + this.scaleDownAnimation = new Tween.Tween( this.scale ) + .to( { x: textureScale.x, y: textureScale.y }, duration ) + .easing( Tween.Easing.Elastic.Out ); + + this.material.map = texture; + this.material.needsUpdate = true; + + }.bind( this ); + + // Add show and hide animations + this.showAnimation = new Tween.Tween( this.material ) + .to( { opacity: 1 }, duration ) + .onStart( this.enableRaycast.bind( this, true ) ) + .easing( Tween.Easing.Quartic.Out ); + + this.hideAnimation = new Tween.Tween( this.material ) + .to( { opacity: 0 }, duration ) + .onStart( this.enableRaycast.bind( this, false ) ) + .easing( Tween.Easing.Quartic.Out ); + + // Attach event listeners + this.addEventListener( 'click', this.onClick ); + this.addEventListener( 'hover', this.onHover ); + this.addEventListener( 'hoverenter', this.onHoverStart ); + this.addEventListener( 'hoverleave', this.onHoverEnd ); + this.addEventListener( 'panolens-dual-eye-effect', this.onDualEyeEffect ); + this.addEventListener( 'panolens-container', this.setContainer.bind( this ) ); + this.addEventListener( 'dismiss', this.onDismiss ); + this.addEventListener( 'panolens-infospot-focus', this.setFocusMethod ); + + TextureLoader.load( imageSrc, postLoad ); + + } + Infospot.prototype = Object.assign( Object.create( THREE.Sprite.prototype ), { + + constructor: Infospot, + + /** + * Set infospot container + * @param {HTMLElement|object} data - Data with container information + * @memberOf Infospot + * @instance + */ + setContainer: function ( data ) { + + let container; + + if ( data instanceof HTMLElement ) { + + container = data; + + } else if ( data && data.container ) { + + container = data.container; + + } + + // Append element if exists + if ( container && this.element ) { + + container.appendChild( this.element ); + + } + + this.container = container; + + }, + + /** + * Get container + * @memberOf Infospot + * @instance + * @return {HTMLElement} - The container of this infospot + */ + getContainer: function () { + + return this.container; + + }, + + /** + * This will be called by a click event + * Translate and lock the hovering element if any + * @param {object} event - Event containing mouseEvent with clientX and clientY + * @memberOf Infospot + * @instance + */ + onClick: function ( event ) { + + if ( this.element && this.getContainer() ) { + + this.onHoverStart( event ); + + // Lock element + this.lockHoverElement(); + + } + + }, + + /** + * Dismiss current element if any + * @param {object} event - Dismiss event + * @memberOf Infospot + * @instance + */ + onDismiss: function () { + + if ( this.element ) { + + this.unlockHoverElement(); + this.onHoverEnd(); + + } + + }, + + /** + * This will be called by a mouse hover event + * Translate the hovering element if any + * @param {object} event - Event containing mouseEvent with clientX and clientY + * @memberOf Infospot + * @instance + */ + onHover: function () {}, + + /** + * This will be called on a mouse hover start + * Sets cursor style to 'pointer', display the element and scale up the infospot + * @param {object} event + * @memberOf Infospot + * @instance + */ + onHoverStart: function ( event ) { + + if ( !this.getContainer() ) { return; } + + const cursorStyle = this.cursorStyle || ( this.mode === MODES.NORMAL ? 'pointer' : 'default' ); + const { scaleDownAnimation, scaleUpAnimation, element } = this; + + this.isHovering = true; + this.container.style.cursor = cursorStyle; + + if ( this.animated ) { + + scaleDownAnimation.stop(); + scaleUpAnimation.start(); + + } + + if ( element && event.mouseEvent.clientX >= 0 && event.mouseEvent.clientY >= 0 ) { + + const { left, right, style } = element; + + if ( this.mode === MODES.CARDBOARD || this.mode === MODES.STEREO ) { + + style.display = 'none'; + left.style.display = 'block'; + right.style.display = 'block'; + + // Store element width for reference + element._width = left.clientWidth; + element._height = left.clientHeight; + + } else { + + style.display = 'block'; + if ( left ) { left.style.display = 'none'; } + if ( right ) { right.style.display = 'none'; } + + // Store element width for reference + element._width = element.clientWidth; + element._height = element.clientHeight; + + } + + } + + }, + + /** + * This will be called on a mouse hover end + * Sets cursor style to 'default', hide the element and scale down the infospot + * @memberOf Infospot + * @instance + */ + onHoverEnd: function () { + + if ( !this.getContainer() ) { return; } + + const { scaleDownAnimation, scaleUpAnimation, element } = this; + + this.isHovering = false; + this.container.style.cursor = 'default'; + + if ( this.animated ) { + + scaleUpAnimation.stop(); + scaleDownAnimation.start(); + + } + + if ( element && !this.element.locked ) { + + const { left, right, style } = element; + + style.display = 'none'; + if ( left ) { left.style.display = 'none'; } + if ( right ) { right.style.display = 'none'; } + + this.unlockHoverElement(); + + } + + }, + + /** + * On dual eye effect handler + * Creates duplicate left and right element + * @param {object} event - panolens-dual-eye-effect event + * @memberOf Infospot + * @instance + */ + onDualEyeEffect: function ( event ) { + + if ( !this.getContainer() ) { return; } + + let element, halfWidth, halfHeight; + + this.mode = event.mode; + + element = this.element; + + halfWidth = this.container.clientWidth / 2; + halfHeight = this.container.clientHeight / 2; + + if ( !element ) { + + return; + + } + + if ( !element.left && !element.right ) { + + element.left = element.cloneNode( true ); + element.right = element.cloneNode( true ); + + } + + if ( this.mode === MODES.CARDBOARD || this.mode === MODES.STEREO ) { + + element.left.style.display = element.style.display; + element.right.style.display = element.style.display; + element.style.display = 'none'; + + } else { + + element.style.display = element.left.style.display; + element.left.style.display = 'none'; + element.right.style.display = 'none'; + + } + + // Update elements translation + this.translateElement( halfWidth, halfHeight ); + + this.container.appendChild( element.left ); + this.container.appendChild( element.right ); + + }, + + /** + * Translate the hovering element by css transform + * @param {number} x - X position on the window screen + * @param {number} y - Y position on the window screen + * @memberOf Infospot + * @instance + */ + translateElement: function ( x, y ) { + + if ( !this.element._width || !this.element._height || !this.getContainer() ) { + + return; + + } + + let left, top, element, width, height, delta, container; + + container = this.container; + element = this.element; + width = element._width / 2; + height = element._height / 2; + delta = element.verticalDelta !== undefined ? element.verticalDelta : 40; + + left = x - width; + top = y - height - delta; + + if ( ( this.mode === MODES.CARDBOARD || this.mode === MODES.STEREO ) + && element.left && element.right + && !( x === container.clientWidth / 2 && y === container.clientHeight / 2 ) ) { + + left = container.clientWidth / 4 - width + ( x - container.clientWidth / 2 ); + top = container.clientHeight / 2 - height - delta + ( y - container.clientHeight / 2 ); + + this.setElementStyle( 'transform', element.left, 'translate(' + left + 'px, ' + top + 'px)' ); + + left += container.clientWidth / 2; + + this.setElementStyle( 'transform', element.right, 'translate(' + left + 'px, ' + top + 'px)' ); + + } else { + + this.setElementStyle( 'transform', element, 'translate(' + left + 'px, ' + top + 'px)' ); + + } + + }, + + /** + * Set vendor specific css + * @param {string} type - CSS style name + * @param {HTMLElement} element - The element to be modified + * @param {string} value - Style value + * @memberOf Infospot + * @instance + */ + setElementStyle: function ( type, element, value ) { + + const style = element.style; + + if ( type === 'transform' ) { + + style.webkitTransform = style.msTransform = style.transform = value; + + } + + }, + + /** + * Set hovering text content + * @param {string} text - Text to be displayed + * @memberOf Infospot + * @instance + */ + setText: function ( text ) { + + if ( this.element ) { + + this.element.textContent = text; + + } + + }, + + /** + * Set cursor css style on hover + * @memberOf Infospot + * @instance + */ + setCursorHoverStyle: function ( style ) { + + this.cursorStyle = style; + + }, + + /** + * Add hovering text element + * @param {string} text - Text to be displayed + * @param {number} [delta=40] - Vertical delta to the infospot + * @memberOf Infospot + * @instance + */ + addHoverText: function ( text, delta = 40 ) { + + if ( !this.element ) { + + this.element = document.createElement( 'div' ); + this.element.style.display = 'none'; + this.element.style.color = '#fff'; + this.element.style.top = 0; + this.element.style.maxWidth = '50%'; + this.element.style.maxHeight = '50%'; + this.element.style.textShadow = '0 0 3px #000000'; + this.element.style.fontFamily = '"Trebuchet MS", Helvetica, sans-serif'; + this.element.style.position = 'absolute'; + this.element.classList.add( 'panolens-infospot' ); + this.element.verticalDelta = delta; + + } + + this.setText( text ); + + }, + + /** + * Add hovering element by cloning an element + * @param {HTMLDOMElement} el - Element to be cloned and displayed + * @param {number} [delta=40] - Vertical delta to the infospot + * @memberOf Infospot + * @instance + */ + addHoverElement: function ( el, delta = 40 ) { + + if ( !this.element ) { + + this.element = el.cloneNode( true ); + this.element.style.display = 'none'; + this.element.style.top = 0; + this.element.style.position = 'absolute'; + this.element.classList.add( 'panolens-infospot' ); + this.element.verticalDelta = delta; + + } + + }, + + /** + * Remove hovering element + * @memberOf Infospot + * @instance + */ + removeHoverElement: function () { + + if ( this.element ) { + + if ( this.element.left ) { + + this.container.removeChild( this.element.left ); + this.element.left = null; + + } + + if ( this.element.right ) { + + this.container.removeChild( this.element.right ); + this.element.right = null; + + } + + this.container.removeChild( this.element ); + this.element = null; + + } + + }, + + /** + * Lock hovering element + * @memberOf Infospot + * @instance + */ + lockHoverElement: function () { + + if ( this.element ) { + + this.element.locked = true; + + } + + }, + + /** + * Unlock hovering element + * @memberOf Infospot + * @instance + */ + unlockHoverElement: function () { + + if ( this.element ) { + + this.element.locked = false; + + } + + }, + + /** + * Enable raycasting + * @param {boolean} [enabled=true] + * @memberOf Infospot + * @instance + */ + enableRaycast: function ( enabled = true ) { + + if ( enabled ) { + + this.raycast = this.originalRaycast; + + } else { + + this.raycast = () => {}; + + } + + }, + + /** + * Show infospot + * @param {number} [delay=0] - Delay time to show + * @memberOf Infospot + * @instance + */ + show: function ( delay = 0 ) { + + const { animated, hideAnimation, showAnimation, material } = this; + + if ( animated ) { + + hideAnimation.stop(); + showAnimation.delay( delay ).start(); + + } else { + + this.enableRaycast( true ); + material.opacity = 1; + + } + + }, + + /** + * Hide infospot + * @param {number} [delay=0] - Delay time to hide + * @memberOf Infospot + * @instance + */ + hide: function ( delay = 0 ) { + + const { animated, hideAnimation, showAnimation, material, element } = this; + + if ( element ) { + const { style } = element; + style.display = 'none'; + } + + if ( animated ) { + + showAnimation.stop(); + hideAnimation.delay( delay ).start(); + + } else { + + this.enableRaycast( false ); + material.opacity = 0; + + } + + }, + + /** + * Set focus event handler + * @memberOf Infospot + * @instance + */ + setFocusMethod: function ( event ) { + + if ( event ) { + + this.HANDLER_FOCUS = event.method; + + } + + }, + + /** + * Focus camera center to this infospot + * @param {number} [duration=1000] - Duration to tween + * @param {function} [easing=TWEEN.Easing.Exponential.Out] - Easing function + * @memberOf Infospot + * @instance + */ + focus: function ( duration, easing ) { + + if ( this.HANDLER_FOCUS ) { + + this.HANDLER_FOCUS( this.position, duration, easing ); + this.onDismiss(); + + } + + }, + + /** + * Dispose + * @memberOf Infospot + * @instance + */ + dispose: function () { + + const { geometry, material } = this; + const { map } = material; + + this.removeHoverElement(); + + if ( this.parent ) { + + this.parent.remove( this ); + + } + + if ( map ) { map.dispose(); material.map = null; } + if ( geometry ) { geometry.dispose(); this.geometry = null; } + if ( material ) { material.dispose(); this.material = null; } + + } + + } ); + + /** + * @classdesc Widget for controls + * @constructor + * @param {HTMLElement} container - A domElement where default control widget will be attached to + */ + function Widget ( container ) { + + if ( !container ) { + + console.warn( 'PANOLENS.Widget: No container specified' ); + + } + + THREE.EventDispatcher.call( this ); + + this.DEFAULT_TRANSITION = 'all 0.27s ease'; + this.TOUCH_ENABLED = !!(( 'ontouchstart' in window ) || window.DocumentTouch && document instanceof DocumentTouch); + this.PREVENT_EVENT_HANDLER = function ( event ) { + event.preventDefault(); + event.stopPropagation(); + }; + + this.container = container; + + this.barElement = null; + this.fullscreenElement = null; + this.videoElement = null; + this.settingElement = null; + + this.mainMenu = null; + + this.activeMainItem = null; + this.activeSubMenu = null; + this.mask = null; + + } + + Widget.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype ), { + + constructor: Widget, + + /** + * Add control bar + * @memberOf Widget + * @instance + */ + addControlBar: function () { + + if ( !this.container ) { + + console.warn( 'Widget container not set' ); + return; + } + + var scope = this, bar, styleTranslate, styleOpacity, gradientStyle; + + gradientStyle = 'linear-gradient(bottom, rgba(0,0,0,0.2), rgba(0,0,0,0))'; + + bar = document.createElement( 'div' ); + bar.style.width = '100%'; + bar.style.height = '44px'; + bar.style.float = 'left'; + bar.style.transform = bar.style.webkitTransform = bar.style.msTransform = 'translateY(-100%)'; + bar.style.background = '-webkit-' + gradientStyle; + bar.style.background = '-moz-' + gradientStyle; + bar.style.background = '-o-' + gradientStyle; + bar.style.background = '-ms-' + gradientStyle; + bar.style.background = gradientStyle; + bar.style.transition = this.DEFAULT_TRANSITION; + bar.style.pointerEvents = 'none'; + bar.isHidden = false; + bar.toggle = function () { + bar.isHidden = !bar.isHidden; + styleTranslate = bar.isHidden ? 'translateY(0)' : 'translateY(-100%)'; + styleOpacity = bar.isHidden ? 0 : 1; + bar.style.transform = bar.style.webkitTransform = bar.style.msTransform = styleTranslate; + bar.style.opacity = styleOpacity; + }; + + // Menu + var menu = this.createDefaultMenu(); + this.mainMenu = this.createMainMenu( menu ); + bar.appendChild( this.mainMenu ); + + // Mask + var mask = this.createMask(); + this.mask = mask; + this.container.appendChild( mask ); + + // Dispose + bar.dispose = function () { + + if ( scope.fullscreenElement ) { + + bar.removeChild( scope.fullscreenElement ); + scope.fullscreenElement.dispose(); + scope.fullscreenElement = null; + + } + + if ( scope.settingElement ) { + + bar.removeChild( scope.settingElement ); + scope.settingElement.dispose(); + scope.settingElement = null; + + } + + if ( scope.videoElement ) { + + bar.removeChild( scope.videoElement ); + scope.videoElement.dispose(); + scope.videoElement = null; + + } + + }; + + this.container.appendChild( bar ); + + // Mask events + this.mask.addEventListener( 'mousemove', this.PREVENT_EVENT_HANDLER, true ); + this.mask.addEventListener( 'mouseup', this.PREVENT_EVENT_HANDLER, true ); + this.mask.addEventListener( 'mousedown', this.PREVENT_EVENT_HANDLER, true ); + this.mask.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', function ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + scope.mask.hide(); + scope.settingElement.deactivate(); + + }, false ); + + // Event listener + this.addEventListener( 'control-bar-toggle', bar.toggle ); + + this.barElement = bar; + + }, + + /** + * Create default menu + * @memberOf Widget + * @instance + */ + createDefaultMenu: function () { + + var scope = this, handler; + + handler = function ( method, data ) { + + return function () { + + scope.dispatchEvent( { + + type: 'panolens-viewer-handler', + method: method, + data: data + + } ); + + }; + + }; + + return [ + + { + title: 'Control', + subMenu: [ + { + title: this.TOUCH_ENABLED ? 'Touch' : 'Mouse', + handler: handler( 'enableControl', CONTROLS.ORBIT ) + }, + { + title: 'Sensor', + handler: handler( 'enableControl', CONTROLS.DEVICEORIENTATION ) + } + ] + }, + + { + title: 'Mode', + subMenu: [ + { + title: 'Normal', + handler: handler( 'disableEffect' ) + }, + { + title: 'Cardboard', + handler: handler( 'enableEffect', MODES.CARDBOARD ) + }, + { + title: 'Stereoscopic', + handler: handler( 'enableEffect', MODES.STEREO ) + } + ] + } + + ]; + + }, + + /** + * Add buttons on top of control bar + * @param {string} name - The control button name to be created + * @memberOf Widget + * @instance + */ + addControlButton: function ( name ) { + + let element; + + switch( name ) { + + case 'fullscreen': + + element = this.createFullscreenButton(); + this.fullscreenElement = element; + + break; + + case 'setting': + + element = this.createSettingButton(); + this.settingElement = element; + + break; + + case 'video': + + element = this.createVideoControl(); + this.videoElement = element; + + break; + + default: + + return; + + } + + if ( !element ) { + + return; + + } + + this.barElement.appendChild( element ); + + }, + + /** + * Create modal mask + * @memberOf Widget + * @instance + */ + createMask: function () { + + const element = document.createElement( 'div' ); + element.style.position = 'absolute'; + element.style.top = 0; + element.style.left = 0; + element.style.width = '100%'; + element.style.height = '100%'; + element.style.background = 'transparent'; + element.style.display = 'none'; + + element.show = function () { + + this.style.display = 'block'; + + }; + + element.hide = function () { + + this.style.display = 'none'; + + }; + + return element; + + }, + + /** + * Create Setting button to toggle menu + * @memberOf Widget + * @instance + */ + createSettingButton: function () { + + let scope = this, item; + + function onTap ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + scope.mainMenu.toggle(); + + if ( this.activated ) { + + this.deactivate(); + + } else { + + this.activate(); + + } + + } + + item = this.createCustomItem( { + + style: { + + backgroundImage: 'url("' + DataImage.Setting + '")', + webkitTransition: this.DEFAULT_TRANSITION, + transition: this.DEFAULT_TRANSITION + + }, + + onTap: onTap + + } ); + + item.activate = function () { + + this.style.transform = 'rotate3d(0,0,1,90deg)'; + this.activated = true; + scope.mask.show(); + + }; + + item.deactivate = function () { + + this.style.transform = 'rotate3d(0,0,0,0)'; + this.activated = false; + scope.mask.hide(); + + if ( scope.mainMenu && scope.mainMenu.visible ) { + + scope.mainMenu.hide(); + + } + + if ( scope.activeSubMenu && scope.activeSubMenu.visible ) { + + scope.activeSubMenu.hide(); + + } + + if ( scope.mainMenu && scope.mainMenu._width ) { + + scope.mainMenu.changeSize( scope.mainMenu._width ); + scope.mainMenu.unslideAll(); + + } + + }; + + item.activated = false; + + return item; + + }, + + /** + * Create Fullscreen button + * @return {HTMLSpanElement} - The dom element icon for fullscreen + * @memberOf Widget + * @instance + * @fires Widget#panolens-viewer-handler + */ + createFullscreenButton: function () { + + let scope = this, item, isFullscreen = false, tapSkipped = true, stylesheetId; + + const { container } = this; + + stylesheetId = 'panolens-style-addon'; + + // Don't create button if no support + if ( !document.fullscreenEnabled && + !document.webkitFullscreenEnabled && + !document.mozFullScreenEnabled && + !document.msFullscreenEnabled ) { + return; + } + + function onTap ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + tapSkipped = false; + + if ( !isFullscreen ) { + + if ( container.requestFullscreen ) { container.requestFullscreen(); } + if ( container.msRequestFullscreen ) { container.msRequestFullscreen(); } + if ( container.mozRequestFullScreen ) { container.mozRequestFullScreen(); } + if ( container.webkitRequestFullscreen ) { container.webkitRequestFullscreen( Element.ALLOW_KEYBOARD_INPUT ); } + + isFullscreen = true; + + } else { + + if ( document.exitFullscreen ) { document.exitFullscreen(); } + if ( document.msExitFullscreen ) { document.msExitFullscreen(); } + if ( document.mozCancelFullScreen ) { document.mozCancelFullScreen(); } + if ( document.webkitExitFullscreen ) { document.webkitExitFullscreen( ); } + + isFullscreen = false; + + } + + this.style.backgroundImage = ( isFullscreen ) + ? 'url("' + DataImage.FullscreenLeave + '")' + : 'url("' + DataImage.FullscreenEnter + '")'; + + } + + function onFullScreenChange () { + + if ( tapSkipped ) { + + isFullscreen = !isFullscreen; + + item.style.backgroundImage = ( isFullscreen ) + ? 'url("' + DataImage.FullscreenLeave + '")' + : 'url("' + DataImage.FullscreenEnter + '")'; + + } + + /** + * Viewer handler event + * @type {object} + * @event Widget#panolens-viewer-handler + * @property {string} method - 'onWindowResize' function call on Viewer + */ + scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onWindowResize' } ); + + tapSkipped = true; + + } + + document.addEventListener( 'fullscreenchange', onFullScreenChange, false ); + document.addEventListener( 'webkitfullscreenchange', onFullScreenChange, false ); + document.addEventListener( 'mozfullscreenchange', onFullScreenChange, false ); + document.addEventListener( 'MSFullscreenChange', onFullScreenChange, false ); + + item = this.createCustomItem( { + + style: { + + backgroundImage: 'url("' + DataImage.FullscreenEnter + '")' + + }, + + onTap: onTap + + } ); + + // Add fullscreen stlye if not exists + if ( !document.querySelector( stylesheetId ) ) { + const sheet = document.createElement( 'style' ); + sheet.id = stylesheetId; + sheet.innerHTML = ':-webkit-full-screen { width: 100% !important; height: 100% !important }'; + document.body.appendChild( sheet ); + } + + return item; + + }, + + /** + * Create video control container + * @memberOf Widget + * @instance + * @return {HTMLSpanElement} - The dom element icon for video control + */ + createVideoControl: function () { + + const item = document.createElement( 'span' ); + item.style.display = 'none'; + item.show = function () { + + item.style.display = ''; + + }; + + item.hide = function () { + + item.style.display = 'none'; + item.controlButton.paused = true; + item.controlButton.update(); + + }; + + item.controlButton = this.createVideoControlButton(); + item.seekBar = this.createVideoControlSeekbar(); + + item.appendChild( item.controlButton ); + item.appendChild( item.seekBar ); + + item.dispose = function () { + + item.removeChild( item.controlButton ); + item.removeChild( item.seekBar ); + + item.controlButton.dispose(); + item.controlButton = null; + + item.seekBar.dispose(); + item.seekBar = null; + + }; + + this.addEventListener( 'video-control-show', item.show ); + this.addEventListener( 'video-control-hide', item.hide ); + + return item; + + }, + + /** + * Create video control button + * @memberOf Widget + * @instance + * @return {HTMLSpanElement} - The dom element icon for video control + * @fires Widget#panolens-viewer-handler + */ + createVideoControlButton: function () { + + const scope = this; + + function onTap ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + /** + * Viewer handler event + * @type {object} + * @event Widget#panolens-viewer-handler + * @property {string} method - 'toggleVideoPlay' function call on Viewer + */ + scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'toggleVideoPlay', data: !this.paused } ); + + this.paused = !this.paused; + + item.update(); + + } + const item = this.createCustomItem( { + + style: { + + float: 'left', + backgroundImage: 'url("' + DataImage.VideoPlay + '")' + + }, + + onTap: onTap + + } ); + + item.paused = true; + + item.update = function ( paused ) { + + this.paused = paused !== undefined ? paused : this.paused; + + this.style.backgroundImage = 'url("' + ( this.paused + ? DataImage.VideoPlay + : DataImage.VideoPause ) + '")'; + + }; + + return item; + + }, + + /** + * Create video seekbar + * @memberOf Widget + * @instance + * @return {HTMLSpanElement} - The dom element icon for video seekbar + * @fires Widget#panolens-viewer-handler + */ + createVideoControlSeekbar: function () { + + let scope = this, item, progressElement, progressElementControl, + isDragging = false, mouseX, percentageNow, percentageNext; + + progressElement = document.createElement( 'div' ); + progressElement.style.width = '0%'; + progressElement.style.height = '100%'; + progressElement.style.backgroundColor = '#fff'; + + progressElementControl = document.createElement( 'div' ); + progressElementControl.style.float = 'right'; + progressElementControl.style.width = '14px'; + progressElementControl.style.height = '14px'; + progressElementControl.style.transform = 'translate(7px, -5px)'; + progressElementControl.style.borderRadius = '50%'; + progressElementControl.style.backgroundColor = '#ddd'; + + progressElementControl.addEventListener( 'mousedown', onMouseDown, { passive: true } ); + progressElementControl.addEventListener( 'touchstart', onMouseDown, { passive: true } ); + + function onMouseDown ( event ) { + + event.stopPropagation(); + + isDragging = true; + + mouseX = event.clientX || ( event.changedTouches && event.changedTouches[0].clientX ); + + percentageNow = parseInt( progressElement.style.width ) / 100; + + addControlListeners(); + } + + function onVideoControlDrag ( event ) { + + if( isDragging ){ + + const clientX = event.clientX || ( event.changedTouches && event.changedTouches[0].clientX ); + + percentageNext = ( clientX - mouseX ) / item.clientWidth; + + percentageNext = percentageNow + percentageNext; + + percentageNext = percentageNext > 1 ? 1 : ( ( percentageNext < 0 ) ? 0 : percentageNext ); + + item.setProgress ( percentageNext ); + + /** + * Viewer handler event + * @type {object} + * @event Widget#panolens-viewer-handler + * @property {string} method - 'setVideoCurrentTime' function call on Viewer + * @property {number} data - Percentage of current video. Range from 0.0 to 1.0 + */ + scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'setVideoCurrentTime', data: percentageNext } ); + + } + + } + + function onVideoControlStop ( event ) { + + event.stopPropagation(); + + isDragging = false; + + removeControlListeners(); + + } + + function addControlListeners () { + + scope.container.addEventListener( 'mousemove', onVideoControlDrag, { passive: true } ); + scope.container.addEventListener( 'mouseup', onVideoControlStop, { passive: true } ); + scope.container.addEventListener( 'touchmove', onVideoControlDrag, { passive: true } ); + scope.container.addEventListener( 'touchend', onVideoControlStop, { passive: true } ); + + + } + + function removeControlListeners () { + + scope.container.removeEventListener( 'mousemove', onVideoControlDrag, false ); + scope.container.removeEventListener( 'mouseup', onVideoControlStop, false ); + scope.container.removeEventListener( 'touchmove', onVideoControlDrag, false ); + scope.container.removeEventListener( 'touchend', onVideoControlStop, false ); + + } + + function onTap ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + if ( event.target === progressElementControl ) { return; } + + const percentage = ( event.changedTouches && event.changedTouches.length > 0 ) + ? ( event.changedTouches[0].pageX - event.target.getBoundingClientRect().left ) / this.clientWidth + : event.offsetX / this.clientWidth; + + /** + * Viewer handler event + * @type {object} + * @property {string} method - 'setVideoCurrentTime' function call on Viewer + * @property {number} data - Percentage of current video. Range from 0.0 to 1.0 + */ + scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'setVideoCurrentTime', data: percentage } ); + + item.setProgress( event.offsetX / this.clientWidth ); + + } + function onDispose () { + + removeControlListeners(); + progressElement = null; + progressElementControl = null; + + } + + progressElement.appendChild( progressElementControl ); + + item = this.createCustomItem( { + + style: { + + float: 'left', + width: '30%', + height: '4px', + marginTop: '20px', + backgroundColor: 'rgba(188,188,188,0.8)' + + }, + + onTap: onTap, + onDispose: onDispose + + } ); + + item.appendChild( progressElement ); + + item.setProgress = function( percentage ) { + + progressElement.style.width = percentage * 100 + '%'; + + }; + + this.addEventListener( 'video-update', function ( event ) { + + item.setProgress( event.percentage ); + + } ); + + item.progressElement = progressElement; + item.progressElementControl = progressElementControl; + + return item; + + }, + + /** + * Create menu item + * @param {string} title - Title to display + * @memberOf Widget + * @instance + * @return {HTMLElement} - An anchor tag element + */ + createMenuItem: function ( title ) { + + const scope = this; + const item = document.createElement( 'a' ); + item.textContent = title; + item.style.display = 'block'; + item.style.padding = '10px'; + item.style.textDecoration = 'none'; + item.style.cursor = 'pointer'; + item.style.pointerEvents = 'auto'; + item.style.transition = this.DEFAULT_TRANSITION; + + item.slide = function ( right ) { + + this.style.transform = 'translateX(' + ( right ? '' : '-' ) + '100%)'; + + }; + + item.unslide = function () { + + this.style.transform = 'translateX(0)'; + + }; + + item.setIcon = function ( url ) { + + if ( this.icon ) { + + this.icon.style.backgroundImage = 'url(' + url + ')'; + + } + + }; + + item.setSelectionTitle = function ( title ) { + + if ( this.selection ) { + + this.selection.textContent = title; + + } + + }; + + item.addSelection = function ( name ) { + + const selection = document.createElement( 'span' ); + selection.style.fontSize = '13px'; + selection.style.fontWeight = '300'; + selection.style.float = 'right'; + + this.selection = selection; + this.setSelectionTitle( name ); + this.appendChild( selection ); + + return this; + + }; + + item.addIcon = function ( url = DataImage.ChevronRight, left = false, flip = false ) { + + const element = document.createElement( 'span' ); + element.style.float = left ? 'left' : 'right'; + element.style.width = '17px'; + element.style.height = '17px'; + element.style[ 'margin' + ( left ? 'Right' : 'Left' ) ] = '12px'; + element.style.backgroundSize = 'cover'; + + if ( flip ) { + + element.style.transform = 'rotateZ(180deg)'; + + } + + this.icon = element; + this.setIcon( url ); + this.appendChild( element ); + + return this; + + }; + + item.addSubMenu = function ( title, items ) { + + this.subMenu = scope.createSubMenu( title, items ); + + return this; + + }; + + item.addEventListener( 'mouseenter', function () { + + this.style.backgroundColor = '#e0e0e0'; + + }, false ); + + item.addEventListener( 'mouseleave', function () { + + this.style.backgroundColor = '#fafafa'; + + }, false ); + + return item; + + }, + + /** + * Create menu item header + * @param {string} title - Title to display + * @memberOf Widget + * @instance + * @return {HTMLElement} - An anchor tag element + */ + createMenuItemHeader: function ( title ) { + + const header = this.createMenuItem( title ); + + header.style.borderBottom = '1px solid #333'; + header.style.paddingBottom = '15px'; + + return header; + + }, + + /** + * Create main menu + * @param {array} menus - Menu array list + * @memberOf Widget + * @instance + * @return {HTMLElement} - A span element + */ + createMainMenu: function ( menus ) { + + let scope = this, menu = this.createMenu(); + + menu._width = 200; + menu.changeSize( menu._width ); + + function onTap ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + let mainMenu = scope.mainMenu, subMenu = this.subMenu; + + function onNextTick () { + + mainMenu.changeSize( subMenu.clientWidth ); + subMenu.show(); + subMenu.unslideAll(); + + } + + mainMenu.hide(); + mainMenu.slideAll(); + mainMenu.parentElement.appendChild( subMenu ); + + scope.activeMainItem = this; + scope.activeSubMenu = subMenu; + + window.requestAnimationFrame( onNextTick ); + + } + for ( var i = 0; i < menus.length; i++ ) { + + var item = menu.addItem( menus[ i ].title ); + + item.style.paddingLeft = '20px'; + + item.addIcon() + .addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', onTap, false ); + + if ( menus[ i ].subMenu && menus[ i ].subMenu.length > 0 ) { + + var title = menus[ i ].subMenu[ 0 ].title; + + item.addSelection( title ) + .addSubMenu( menus[ i ].title, menus[ i ].subMenu ); + + } + + } + + return menu; + + }, + + /** + * Create sub menu + * @param {string} title - Sub menu title + * @param {array} items - Item array list + * @memberOf Widget + * @instance + * @return {HTMLElement} - A span element + */ + createSubMenu: function ( title, items ) { + + let scope = this, menu, subMenu = this.createMenu(); + + subMenu.items = items; + subMenu.activeItem = null; + + function onTap ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + menu = scope.mainMenu; + menu.changeSize( menu._width ); + menu.unslideAll(); + menu.show(); + subMenu.slideAll( true ); + subMenu.hide(); + + if ( this.type !== 'header' ) { + + subMenu.setActiveItem( this ); + scope.activeMainItem.setSelectionTitle( this.textContent ); + + if ( this.handler ) { this.handler(); } + + } + + } + + subMenu.addHeader( title ).addIcon( undefined, true, true ).addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', onTap, false ); + + for ( let i = 0; i < items.length; i++ ) { + + const item = subMenu.addItem( items[ i ].title ); + + item.style.fontWeight = 300; + item.handler = items[ i ].handler; + item.addIcon( ' ', true ); + item.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', onTap, false ); + + if ( !subMenu.activeItem ) { + + subMenu.setActiveItem( item ); + + } + + } + + subMenu.slideAll( true ); + + return subMenu; + + }, + + /** + * Create general menu + * @memberOf Widget + * @instance + * @return {HTMLElement} - A span element + */ + createMenu: function () { + + const scope = this; + const menu = document.createElement( 'span' ); + const style = menu.style; + + style.padding = '5px 0'; + style.position = 'fixed'; + style.bottom = '100%'; + style.right = '14px'; + style.backgroundColor = '#fafafa'; + style.fontFamily = 'Helvetica Neue'; + style.fontSize = '14px'; + style.visibility = 'hidden'; + style.opacity = 0; + style.boxShadow = '0 0 12pt rgba(0,0,0,0.25)'; + style.borderRadius = '2px'; + style.overflow = 'hidden'; + style.willChange = 'width, height, opacity'; + style.pointerEvents = 'auto'; + style.transition = this.DEFAULT_TRANSITION; + + menu.visible = false; + + menu.changeSize = function ( width, height ) { + + if ( width ) { + + this.style.width = width + 'px'; + + } + + if ( height ) { + + this.style.height = height + 'px'; + + } + + }; + + menu.show = function () { + + this.style.opacity = 1; + this.style.visibility = 'visible'; + this.visible = true; + + }; + + menu.hide = function () { + + this.style.opacity = 0; + this.style.visibility = 'hidden'; + this.visible = false; + + }; + + menu.toggle = function () { + + if ( this.visible ) { + + this.hide(); + + } else { + + this.show(); + + } + + }; + + menu.slideAll = function ( right ) { + + for ( let i = 0; i < menu.children.length; i++ ){ + + if ( menu.children[ i ].slide ) { + + menu.children[ i ].slide( right ); + + } + + } + + }; + + menu.unslideAll = function () { + + for ( let i = 0; i < menu.children.length; i++ ){ + + if ( menu.children[ i ].unslide ) { + + menu.children[ i ].unslide(); + + } + + } + + }; + + menu.addHeader = function ( title ) { + + const header = scope.createMenuItemHeader( title ); + header.type = 'header'; + + this.appendChild( header ); + + return header; + + }; + + menu.addItem = function ( title ) { + + const item = scope.createMenuItem( title ); + item.type = 'item'; + + this.appendChild( item ); + + return item; + + }; + + menu.setActiveItem = function ( item ) { + + if ( this.activeItem ) { + + this.activeItem.setIcon( ' ' ); + + } + + item.setIcon( DataImage.Check ); + + this.activeItem = item; + + }; + + menu.addEventListener( 'mousemove', this.PREVENT_EVENT_HANDLER, true ); + menu.addEventListener( 'mouseup', this.PREVENT_EVENT_HANDLER, true ); + menu.addEventListener( 'mousedown', this.PREVENT_EVENT_HANDLER, true ); + + return menu; + + }, + + /** + * Create custom item element + * @memberOf Widget + * @instance + * @return {HTMLSpanElement} - The dom element icon + */ + createCustomItem: function ( options = {} ) { + + const scope = this; + const item = options.element || document.createElement( 'span' ); + const { onDispose } = options; + + item.style.cursor = 'pointer'; + item.style.float = 'right'; + item.style.width = '44px'; + item.style.height = '100%'; + item.style.backgroundSize = '60%'; + item.style.backgroundRepeat = 'no-repeat'; + item.style.backgroundPosition = 'center'; + item.style.webkitUserSelect = + item.style.MozUserSelect = + item.style.userSelect = 'none'; + item.style.position = 'relative'; + item.style.pointerEvents = 'auto'; + + // White glow on icon + item.addEventListener( scope.TOUCH_ENABLED ? 'touchstart' : 'mouseenter', function() { + item.style.filter = + item.style.webkitFilter = 'drop-shadow(0 0 5px rgba(255,255,255,1))'; + }, { passive: true }); + item.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'mouseleave', function() { + item.style.filter = + item.style.webkitFilter = ''; + }, { passive: true }); + + this.mergeStyleOptions( item, options.style ); + + if ( options.onTap ) { + + item.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', options.onTap, false ); + + } + + item.dispose = function () { + + item.removeEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', options.onTap, false ); + + if ( onDispose ) { options.onDispose(); } + + }; + + return item; + + }, + + /** + * Merge item css style + * @param {HTMLElement} element - The element to be merged with style + * @param {object} options - The style options + * @memberOf Widget + * @instance + * @return {HTMLElement} - The same element with merged styles + */ + mergeStyleOptions: function ( element, options = {} ) { + + for ( let property in options ){ + + if ( options.hasOwnProperty( property ) ) { + + element.style[ property ] = options[ property ]; + + } + + } + + return element; + + }, + + /** + * Dispose widgets by detaching dom elements from container + * @memberOf Widget + * @instance + */ + dispose: function () { + + if ( this.barElement ) { + this.container.removeChild( this.barElement ); + this.barElement.dispose(); + this.barElement = null; + + } + + } + + } ); + + /** + * @classdesc Base Panorama + * @constructor + * @param {THREE.Geometry} geometry - The geometry for this panorama + * @param {THREE.Material} material - The material for this panorama + */ + function Panorama ( geometry, material ) { + + THREE.Mesh.call( this, geometry, material ); + + this.type = 'panorama'; + + this.ImageQualityLow = 1; + this.ImageQualityFair = 2; + this.ImageQualityMedium = 3; + this.ImageQualityHigh = 4; + this.ImageQualitySuperHigh = 5; + + this.animationDuration = 1000; + + this.defaultInfospotSize = 350; + + this.container = undefined; + + this.loaded = false; + + this.linkedSpots = []; + + this.isInfospotVisible = false; + + this.linkingImageURL = undefined; + this.linkingImageScale = undefined; + + this.material.side = THREE.BackSide; + this.material.opacity = 0; + + this.scale.x *= -1; + this.renderOrder = -1; + + this.active = false; + + this.infospotAnimation = new Tween.Tween( this ).to( {}, this.animationDuration / 2 ); + + this.addEventListener( 'load', this.fadeIn.bind( this ) ); + this.addEventListener( 'panolens-container', this.setContainer.bind( this ) ); + this.addEventListener( 'click', this.onClick.bind( this ) ); + + this.setupTransitions(); + + } + + Panorama.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { + + constructor: Panorama, + + /** + * Adding an object + * To counter the scale.x = -1, it will automatically add an + * empty object with inverted scale on x + * @memberOf Panorama + * @instance + * @param {THREE.Object3D} object - The object to be added + */ + add: function ( object ) { + + let invertedObject; + + if ( arguments.length > 1 ) { + + for ( var i = 0; i < arguments.length; i ++ ) { + + this.add( arguments[ i ] ); + + } + + return this; + + } + + // In case of infospots + if ( object instanceof Infospot ) { + + invertedObject = object; + + if ( object.dispatchEvent ) { + + const { container } = this; + + if ( container ) { object.dispatchEvent( { type: 'panolens-container', container } ); } + + object.dispatchEvent( { type: 'panolens-infospot-focus', method: function ( vector, duration, easing ) { + + /** + * Infospot focus handler event + * @type {object} + * @event Panorama#panolens-viewer-handler + * @property {string} method - Viewer function name + * @property {*} data - The argument to be passed into the method + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'tweenControlCenter', data: [ vector, duration, easing ] } ); + + + }.bind( this ) } ); + } + + } else { + + // Counter scale.x = -1 effect + invertedObject = new THREE.Object3D(); + invertedObject.scale.x = -1; + invertedObject.scalePlaceHolder = true; + invertedObject.add( object ); + + } + + THREE.Object3D.prototype.add.call( this, invertedObject ); + + }, + + load: function () { + + this.onLoad(); + + }, + + /** + * Click event handler + * @param {object} event - Click event + * @memberOf Panorama + * @instance + * @fires Infospot#dismiss + */ + onClick: function ( event ) { + + if ( event.intersects && event.intersects.length === 0 ) { + + this.traverse( function ( object ) { + + /** + * Dimiss event + * @type {object} + * @event Infospot#dismiss + */ + object.dispatchEvent( { type: 'dismiss' } ); + + } ); + + } + + }, + + /** + * Set container of this panorama + * @param {HTMLElement|object} data - Data with container information + * @memberOf Panorama + * @instance + * @fires Infospot#panolens-container + */ + setContainer: function ( data ) { + + let container; + + if ( data instanceof HTMLElement ) { + + container = data; + + } else if ( data && data.container ) { + + container = data.container; + + } + + if ( container ) { + + this.children.forEach( function ( child ) { + + if ( child instanceof Infospot && child.dispatchEvent ) { + + /** + * Set container event + * @type {object} + * @event Infospot#panolens-container + * @property {HTMLElement} container - The container of this panorama + */ + child.dispatchEvent( { type: 'panolens-container', container: container } ); + + } + + } ); + + this.container = container; + + } + + }, + + /** + * This will be called when panorama is loaded + * @memberOf Panorama + * @instance + * @fires Panorama#load + */ + onLoad: function () { + + this.loaded = true; + + /** + * Load panorama event + * @type {object} + * @event Panorama#load + */ + this.dispatchEvent( { type: 'load' } ); + + }, + + /** + * This will be called when panorama is in progress + * @memberOf Panorama + * @instance + * @fires Panorama#progress + */ + onProgress: function ( progress ) { + + /** + * Loading panorama progress event + * @type {object} + * @event Panorama#progress + * @property {object} progress - The progress object containing loaded and total amount + */ + this.dispatchEvent( { type: 'progress', progress: progress } ); + + }, + + /** + * This will be called when panorama loading has error + * @memberOf Panorama + * @instance + * @fires Panorama#error + */ + onError: function () { + + /** + * Loading panorama error event + * @type {object} + * @event Panorama#error + */ + this.dispatchEvent( { type: 'error' } ); + + }, + + /** + * Get zoom level based on window width + * @memberOf Panorama + * @instance + * @return {number} zoom level indicating image quality + */ + getZoomLevel: function () { + + let zoomLevel; + + if ( window.innerWidth <= 800 ) { + + zoomLevel = this.ImageQualityFair; + + } else if ( window.innerWidth > 800 && window.innerWidth <= 1280 ) { + + zoomLevel = this.ImageQualityMedium; + + } else if ( window.innerWidth > 1280 && window.innerWidth <= 1920 ) { + + zoomLevel = this.ImageQualityHigh; + + } else if ( window.innerWidth > 1920 ) { + + zoomLevel = this.ImageQualitySuperHigh; + + } else { + + zoomLevel = this.ImageQualityLow; + + } + + return zoomLevel; + + }, + + /** + * Update texture of a panorama + * @memberOf Panorama + * @instance + * @param {THREE.Texture} texture - Texture to be updated + */ + updateTexture: function ( texture ) { + + this.material.map = texture; + this.material.needsUpdate = true; + + }, + + /** + * Toggle visibility of infospots in this panorama + * @param {boolean} isVisible - Visibility of infospots + * @param {number} delay - Delay in milliseconds to change visibility + * @memberOf Panorama + * @instance + * @fires Panorama#infospot-animation-complete + */ + toggleInfospotVisibility: function ( isVisible, delay ) { + + delay = ( delay !== undefined ) ? delay : 0; + + const visible = ( isVisible !== undefined ) ? isVisible : ( this.isInfospotVisible ? false : true ); + + this.traverse( function ( object ) { + + if ( object instanceof Infospot ) { + + if ( visible ) { + + object.show( delay ); + + } else { + + object.hide( delay ); + + } + + } + + } ); + + this.isInfospotVisible = visible; + + // Animation complete event + this.infospotAnimation.onComplete( function () { + + /** + * Complete toggling infospot visibility + * @event Panorama#infospot-animation-complete + * @type {object} + */ + this.dispatchEvent( { type: 'infospot-animation-complete', visible: visible } ); + + }.bind( this ) ).delay( delay ).start(); + + }, + + /** + * Set image of this panorama's linking infospot + * @memberOf Panorama + * @instance + * @param {string} url - Url to the image asset + * @param {number} scale - Scale factor of the infospot + */ + setLinkingImage: function ( url, scale ) { + + this.linkingImageURL = url; + this.linkingImageScale = scale; + + }, + + /** + * Link one-way panorama + * @param {Panorama} pano - The panorama to be linked to + * @param {THREE.Vector3} position - The position of infospot which navigates to the pano + * @param {number} [imageScale=300] - Image scale of linked infospot + * @param {string} [imageSrc=DataImage.Arrow] - The image source of linked infospot + * @memberOf Panorama + * @instance + */ + link: function ( pano, position, imageScale, imageSrc ) { + + let scale, img; + + this.visible = true; + + if ( !position ) { + + console.warn( 'Please specify infospot position for linking' ); + + return; + + } + + // Infospot scale + if ( imageScale !== undefined ) { + + scale = imageScale; + + } else if ( pano.linkingImageScale !== undefined ) { + + scale = pano.linkingImageScale; + + } else { + + scale = 300; + + } + + + // Infospot image + if ( imageSrc ) { + + img = imageSrc; + + } else if ( pano.linkingImageURL ) { + + img = pano.linkingImageURL; + + } else { + + img = DataImage.Arrow; + + } + + // Creates a new infospot + const spot = new Infospot( scale, img ); + spot.position.copy( position ); + spot.toPanorama = pano; + spot.addEventListener( 'click', function () { + + /** + * Viewer handler event + * @type {object} + * @event Panorama#panolens-viewer-handler + * @property {string} method - Viewer function name + * @property {*} data - The argument to be passed into the method + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'setPanorama', data: pano } ); + + }.bind( this ) ); + + this.linkedSpots.push( spot ); + + this.add( spot ); + + this.visible = false; + + }, + + reset: function () { + + this.children.length = 0; + + }, + + setupTransitions: function () { + + this.fadeInAnimation = new Tween.Tween( this.material ) + .easing( Tween.Easing.Quartic.Out ) + .onStart( function () { + + this.visible = true; + // this.material.visible = true; + + /** + * Enter panorama fade in start event + * @event Panorama#enter-fade-start + * @type {object} + */ + this.dispatchEvent( { type: 'enter-fade-start' } ); + + }.bind( this ) ); + + this.fadeOutAnimation = new Tween.Tween( this.material ) + .easing( Tween.Easing.Quartic.Out ) + .onComplete( function () { + + this.visible = false; + // this.material.visible = true; + + /** + * Leave panorama complete event + * @event Panorama#leave-complete + * @type {object} + */ + this.dispatchEvent( { type: 'leave-complete' } ); + + }.bind( this ) ); + + this.enterTransition = new Tween.Tween( this ) + .easing( Tween.Easing.Quartic.Out ) + .onComplete( function () { + + /** + * Enter panorama and animation complete event + * @event Panorama#enter-complete + * @type {object} + */ + this.dispatchEvent( { type: 'enter-complete' } ); + + }.bind ( this ) ) + .start(); + + this.leaveTransition = new Tween.Tween( this ) + .easing( Tween.Easing.Quartic.Out ); + + }, + + onFadeAnimationUpdate: function () { + + const alpha = this.material.opacity; + const { uniforms } = this.material; + + if ( uniforms && uniforms.opacity ) { + uniforms.opacity.value = alpha; + } + + }, + + /** + * Start fading in animation + * @memberOf Panorama + * @instance + * @fires Panorama#enter-fade-complete + */ + fadeIn: function ( duration ) { + + duration = duration >= 0 ? duration : this.animationDuration; + + this.fadeOutAnimation.stop(); + this.fadeInAnimation + .to( { opacity: 1 }, duration ) + .onUpdate( this.onFadeAnimationUpdate.bind( this ) ) + .onComplete( function () { + + this.toggleInfospotVisibility( true, duration / 2 ); + + /** + * Enter panorama fade complete event + * @event Panorama#enter-fade-complete + * @type {object} + */ + this.dispatchEvent( { type: 'enter-fade-complete' } ); + + }.bind( this ) ) + .start(); + + }, + + /** + * Start fading out animation + * @memberOf Panorama + * @instance + */ + fadeOut: function ( duration ) { + + duration = duration >= 0 ? duration : this.animationDuration; + + this.fadeInAnimation.stop(); + this.fadeOutAnimation + .to( { opacity: 0 }, duration ) + .onUpdate( this.onFadeAnimationUpdate.bind( this ) ) + .start(); + + }, + + /** + * This will be called when entering a panorama + * @memberOf Panorama + * @instance + * @fires Panorama#enter + * @fires Panorama#enter-start + */ + onEnter: function () { + + const duration = this.animationDuration; + + this.leaveTransition.stop(); + this.enterTransition + .to( {}, duration ) + .onStart( function () { + + /** + * Enter panorama and animation starting event + * @event Panorama#enter-start + * @type {object} + */ + this.dispatchEvent( { type: 'enter-start' } ); + + if ( this.loaded ) { + + this.fadeIn( duration ); + + } else { + + this.load(); + + } + + }.bind( this ) ) + .start(); + + /** + * Enter panorama event + * @event Panorama#enter + * @type {object} + */ + this.dispatchEvent( { type: 'enter' } ); + + this.children.forEach( child => { + + child.dispatchEvent( { type: 'panorama-enter' } ); + + } ); + + this.active = true; + + }, + + /** + * This will be called when leaving a panorama + * @memberOf Panorama + * @instance + * @fires Panorama#leave + */ + onLeave: function () { + + const duration = this.animationDuration; + + this.enterTransition.stop(); + this.leaveTransition + .to( {}, duration ) + .onStart( function () { + + /** + * Leave panorama and animation starting event + * @event Panorama#leave-start + * @type {object} + */ + this.dispatchEvent( { type: 'leave-start' } ); + + this.fadeOut( duration ); + this.toggleInfospotVisibility( false ); + + }.bind( this ) ) + .start(); + + /** + * Leave panorama event + * @event Panorama#leave + * @type {object} + */ + this.dispatchEvent( { type: 'leave' } ); + + this.children.forEach( child => { + + child.dispatchEvent( { type: 'panorama-leave' } ); + + } ); + + this.active = false; + + }, + + /** + * Dispose panorama + * @memberOf Panorama + * @instance + */ + dispose: function () { + + this.infospotAnimation.stop(); + this.fadeInAnimation.stop(); + this.fadeOutAnimation.stop(); + this.enterTransition.stop(); + this.leaveTransition.stop(); + + /** + * On panorama dispose handler + * @type {object} + * @event Panorama#panolens-viewer-handler + * @property {string} method - Viewer function name + * @property {*} data - The argument to be passed into the method + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onPanoramaDispose', data: this } ); + + // recursive disposal on 3d objects + function recursiveDispose ( object ) { + + const { geometry, material } = object; + + for ( var i = object.children.length - 1; i >= 0; i-- ) { + + recursiveDispose( object.children[i] ); + object.remove( object.children[i] ); + + } + + if ( object instanceof Infospot ) { + + object.dispose(); + + } + + if ( geometry ) { geometry.dispose(); object.geometry = null; } + if ( material ) { material.dispose(); object.material = null; } + + } + + recursiveDispose( this ); + + if ( this.parent ) { + + this.parent.remove( this ); + + } + + } + + } ); + + /** + * @classdesc Equirectangular based image panorama + * @constructor + * @param {string} image - Image url or HTMLImageElement + */ + function ImagePanorama ( image, _geometry, _material ) { + + const radius = 5000; + const geometry = _geometry || new THREE.SphereBufferGeometry( radius, 60, 40 ); + const material = _material || new THREE.MeshBasicMaterial( { opacity: 0, transparent: true } ); + + Panorama.call( this, geometry, material ); + + this.src = image; + this.radius = radius; + + } + + ImagePanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { + + constructor: ImagePanorama, + + /** + * Load image asset + * @param {*} src - Url or image element + * @memberOf ImagePanorama + * @instance + */ + load: function ( src ) { + + src = src || this.src; + + if ( !src ) { + + console.warn( 'Image source undefined' ); + + return; + + } else if ( typeof src === 'string' ) { + + TextureLoader.load( src, this.onLoad.bind( this ), this.onProgress.bind( this ), this.onError.bind( this ) ); + + } else if ( src instanceof HTMLImageElement ) { + + this.onLoad( new THREE.Texture( src ) ); + + } + + }, + + /** + * This will be called when image is loaded + * @param {THREE.Texture} texture - Texture to be updated + * @memberOf ImagePanorama + * @instance + */ + onLoad: function ( texture ) { + + texture.minFilter = texture.magFilter = THREE.LinearFilter; + texture.needsUpdate = true; + + this.updateTexture( texture ); + + window.requestAnimationFrame( Panorama.prototype.onLoad.bind( this ) ); + + }, + + /** + * Reset + * @memberOf ImagePanorama + * @instance + */ + reset: function () { + + Panorama.prototype.reset.call( this ); + + }, + + /** + * Dispose + * @memberOf ImagePanorama + * @instance + */ + dispose: function () { + + const { material: { map } } = this; + + // Release cached image + THREE.Cache.remove( this.src ); + + if ( map ) { map.dispose(); } + + Panorama.prototype.dispose.call( this ); + + } + + } ); + + /** + * @classdesc Empty panorama + * @constructor + */ + function EmptyPanorama () { + + const geometry = new THREE.BufferGeometry(); + const material = new THREE.MeshBasicMaterial( { color: 0x000000, opacity: 0, transparent: true } ); + + geometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array(), 1 ) ); + + Panorama.call( this, geometry, material ); + + } + + EmptyPanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { + + constructor: EmptyPanorama + + } ); + + /** + * @classdesc Cubemap-based panorama + * @constructor + * @param {array} images - Array of 6 urls to images, one for each side of the CubeTexture. The urls should be specified in the following order: pos-x, neg-x, pos-y, neg-y, pos-z, neg-z + */ + function CubePanorama ( images = [] ){ + + const edgeLength = 10000; + const shader = Object.assign( {}, THREE.ShaderLib[ 'cube' ] ); + const geometry = new THREE.BoxBufferGeometry( edgeLength, edgeLength, edgeLength ); + const material = new THREE.ShaderMaterial( { + + fragmentShader: shader.fragmentShader, + vertexShader: shader.vertexShader, + uniforms: shader.uniforms, + side: THREE.BackSide, + transparent: true + + } ); + + Panorama.call( this, geometry, material ); + + this.images = images; + this.edgeLength = edgeLength; + this.material.uniforms.opacity.value = 0; + + } + + CubePanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { + + constructor: CubePanorama, + + /** + * Load 6 images and bind listeners + * @memberOf CubePanorama + * @instance + */ + load: function () { + + CubeTextureLoader.load( + + this.images, + + this.onLoad.bind( this ), + this.onProgress.bind( this ), + this.onError.bind( this ) + + ); + + }, + + /** + * This will be called when 6 textures are ready + * @param {THREE.CubeTexture} texture - Cube texture + * @memberOf CubePanorama + * @instance + */ + onLoad: function ( texture ) { + + this.material.uniforms[ 'tCube' ].value = texture; + + Panorama.prototype.onLoad.call( this ); + + }, + + /** + * Dispose + * @memberOf CubePanorama + * @instance + */ + dispose: function () { + + const { value } = this.material.uniforms.tCube; + + this.images.forEach( ( image ) => { THREE.Cache.remove( image ); } ); + + if ( value instanceof THREE.CubeTexture ) { + + value.dispose(); + + } + + Panorama.prototype.dispose.call( this ); + + } + + } ); + + /** + * @classdesc Basic panorama with 6 pre-defined grid images + * @constructor + */ + function BasicPanorama () { + + const images = []; + + for ( let i = 0; i < 6; i++ ) { + + images.push( DataImage.WhiteTile ); + + } + + CubePanorama.call( this, images ); + + } + + BasicPanorama.prototype = Object.assign( Object.create( CubePanorama.prototype ), { + + constructor: BasicPanorama + + } ); + + /** + * @classdesc Video Panorama + * @constructor + * @param {string} src - Equirectangular video url + * @param {object} [options] - Option for video settings + * @param {HTMLElement} [options.videoElement] - HTML5 video element contains the video + * @param {boolean} [options.loop=true] - Specify if the video should loop in the end + * @param {boolean} [options.muted=true] - Mute the video or not. Need to be true in order to autoplay on some browsers + * @param {boolean} [options.autoplay=false] - Specify if the video should auto play + * @param {boolean} [options.playsinline=true] - Specify if video should play inline for iOS. If you want it to auto play inline, set both autoplay and muted options to true + * @param {string} [options.crossOrigin="anonymous"] - Sets the cross-origin attribute for the video, which allows for cross-origin videos in some browsers (Firefox, Chrome). Set to either "anonymous" or "use-credentials". + * @param {number} [radius=5000] - The minimum radius for this panoram + */ + function VideoPanorama ( src, options = {} ) { + + const radius = 5000; + const geometry = new THREE.SphereBufferGeometry( radius, 60, 40 ); + const material = new THREE.MeshBasicMaterial( { opacity: 0, transparent: true } ); + + Panorama.call( this, geometry, material ); + + this.src = src; + + this.options = { + + videoElement: document.createElement( 'video' ), + loop: true, + muted: true, + autoplay: false, + playsinline: true, + crossOrigin: 'anonymous' + + }; + + Object.assign( this.options, options ); + + this.videoElement = this.options.videoElement; + this.videoProgress = 0; + this.radius = radius; + + this.addEventListener( 'leave', this.pauseVideo.bind( this ) ); + this.addEventListener( 'enter-fade-start', this.resumeVideoProgress.bind( this ) ); + this.addEventListener( 'video-toggle', this.toggleVideo.bind( this ) ); + this.addEventListener( 'video-time', this.setVideoCurrentTime.bind( this ) ); + + } + VideoPanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { + + constructor: VideoPanorama, + + isMobile: function () { + + let check = false; + (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})( window.navigator.userAgent || window.navigator.vendor || window.opera ); + return check; + + }, + + /** + * Load video panorama + * @memberOf VideoPanorama + * @instance + * @fires Panorama#panolens-viewer-handler + */ + load: function () { + + const { muted, loop, autoplay, playsinline, crossOrigin } = this.options; + const video = this.videoElement; + const material = this.material; + const onProgress = this.onProgress.bind( this ); + const onLoad = this.onLoad.bind( this ); + + video.loop = loop; + video.autoplay = autoplay; + video.playsinline = playsinline; + video.crossOrigin = crossOrigin; + video.muted = muted; + + if ( playsinline ) { + + video.setAttribute( 'playsinline', '' ); + video.setAttribute( 'webkit-playsinline', '' ); + + } + + const onloadeddata = function() { + + this.setVideoTexture( video ); + + if ( autoplay ) { + + /** + * Viewer handler event + * @type {object} + * @property {string} method - 'updateVideoPlayButton' + * @property {boolean} data - Pause video or not + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: false } ); + + } + + // For mobile silent autoplay + if ( this.isMobile() ) { + + video.pause(); + + if ( autoplay && muted ) { + + /** + * Viewer handler event + * @type {object} + * @property {string} method - 'updateVideoPlayButton' + * @property {boolean} data - Pause video or not + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: false } ); + + } else { + + /** + * Viewer handler event + * @type {object} + * @property {string} method - 'updateVideoPlayButton' + * @property {boolean} data - Pause video or not + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: true } ); + + } + + } + + const loaded = () => { + + // Fix for threejs r89 delayed update + material.map.needsUpdate = true; + + onProgress( { loaded: 1, total: 1 } ); + onLoad(); + + }; + + window.requestAnimationFrame( loaded ); + + }; + + /** + * Ready state of the audio/video element + * 0 = HAVE_NOTHING - no information whether or not the audio/video is ready + * 1 = HAVE_METADATA - metadata for the audio/video is ready + * 2 = HAVE_CURRENT_DATA - data for the current playback position is available, but not enough data to play next frame/millisecond + * 3 = HAVE_FUTURE_DATA - data for the current and at least the next frame is available + * 4 = HAVE_ENOUGH_DATA - enough data available to start playing + */ + if ( video.readyState > 2 ) { + + onloadeddata.call( this ); + + } else { + + if ( video.querySelectorAll( 'source' ).length === 0 ) { + + const source = document.createElement( 'source' ); + source.src = this.src; + video.appendChild( source ); + + } + + video.load(); + } + + video.addEventListener( 'loadeddata', onloadeddata.bind( this ) ); + + video.addEventListener( 'timeupdate', function () { + + this.videoProgress = video.duration >= 0 ? video.currentTime / video.duration : 0; + + /** + * Viewer handler event + * @type {object} + * @property {string} method - 'onVideoUpdate' + * @property {number} data - The percentage of video progress. Range from 0.0 to 1.0 + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onVideoUpdate', data: this.videoProgress } ); + + }.bind( this ) ); + + video.addEventListener( 'ended', function () { + + if ( !loop ) { + + this.resetVideo(); + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: true } ); + + } + + }.bind( this ), false ); + + }, + + /** + * Set video texture + * @memberOf VideoPanorama + * @instance + * @param {HTMLVideoElement} video - The html5 video element + * @fires Panorama#panolens-viewer-handler + */ + setVideoTexture: function ( video ) { + + if ( !video ) return; + + const videoTexture = new THREE.VideoTexture( video ); + videoTexture.minFilter = THREE.LinearFilter; + videoTexture.magFilter = THREE.LinearFilter; + videoTexture.format = THREE.RGBFormat; + + this.updateTexture( videoTexture ); + + }, + + /** + * Reset + * @memberOf VideoPanorama + * @instance + */ + reset: function () { + + this.videoElement = undefined; + + Panorama.prototype.reset.call( this ); + + }, + + /** + * Check if video is paused + * @memberOf VideoPanorama + * @instance + * @return {boolean} - is video paused or not + */ + isVideoPaused: function () { + + return this.videoElement.paused; + + }, + + /** + * Toggle video to play or pause + * @memberOf VideoPanorama + * @instance + */ + toggleVideo: function () { + + const video = this.videoElement; + + if ( !video ) { return; } + + video[ video.paused ? 'play' : 'pause' ](); + + }, + + /** + * Set video currentTime + * @memberOf VideoPanorama + * @instance + * @param {object} event - Event contains percentage. Range from 0.0 to 1.0 + */ + setVideoCurrentTime: function ( { percentage } ) { + + const video = this.videoElement; + + if ( video && !Number.isNaN( percentage ) && percentage !== 1 ) { + + video.currentTime = video.duration * percentage; + + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onVideoUpdate', data: percentage } ); + + } + + }, + + /** + * Play video + * @memberOf VideoPanorama + * @instance + * @fires VideoPanorama#play + * @fires VideoPanorama#play-error + */ + playVideo: function () { + + const video = this.videoElement; + const playVideo = this.playVideo.bind( this ); + const dispatchEvent = this.dispatchEvent.bind( this ); + const onSuccess = () => { + + /** + * Play event + * @type {object} + * @event VideoPanorama#play + * + */ + dispatchEvent( { type: 'play' } ); + + }; + const onError = ( error ) => { + + // Error playing video. Retry next frame. Possibly Waiting for user interaction + window.requestAnimationFrame( playVideo ); + + /** + * Play event + * @type {object} + * @event VideoPanorama#play-error + * + */ + dispatchEvent( { type: 'play-error', error } ); + + }; + + if ( video && video.paused ) { + + video.play().then( onSuccess ).catch( onError ); + + } + + }, + + /** + * Pause video + * @memberOf VideoPanorama + * @instance + * @fires VideoPanorama#pause + */ + pauseVideo: function () { + + const video = this.videoElement; + + if ( video && !video.paused ) { + + video.pause(); + + } + + /** + * Pause event + * @type {object} + * @event VideoPanorama#pause + * + */ + this.dispatchEvent( { type: 'pause' } ); + + }, + + /** + * Resume video + * @memberOf VideoPanorama + * @instance + */ + resumeVideoProgress: function () { + + const video = this.videoElement; + + if ( video.readyState >= 4 && video.autoplay && !this.isMobile() ) { + + this.playVideo(); + + /** + * Viewer handler event + * @type {object} + * @property {string} method - 'updateVideoPlayButton' + * @property {boolean} data - Pause video or not + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: false } ); + + } else { + + this.pauseVideo(); + + /** + * Viewer handler event + * @type {object} + * @property {string} method - 'updateVideoPlayButton' + * @property {boolean} data - Pause video or not + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: true } ); + + } + + this.setVideoCurrentTime( { percentage: this.videoProgress } ); + + }, + + /** + * Reset video at stating point + * @memberOf VideoPanorama + * @instance + */ + resetVideo: function () { + + const video = this.videoElement; + + if ( video ) { + + this.setVideoCurrentTime( { percentage: 0 } ); + + } + + }, + + /** + * Check if video is muted + * @memberOf VideoPanorama + * @instance + * @return {boolean} - is video muted or not + */ + isVideoMuted: function () { + + return this.videoElement.muted; + + }, + + /** + * Mute video + * @memberOf VideoPanorama + * @instance + */ + muteVideo: function () { + + const video = this.videoElement; + + if ( video && !video.muted ) { + + video.muted = true; + + } + + this.dispatchEvent( { type: 'volumechange' } ); + + }, + + /** + * Unmute video + * @memberOf VideoPanorama + * @instance + */ + unmuteVideo: function () { + + const video = this.videoElement; + + if ( video && this.isVideoMuted() ) { + + video.muted = false; + + } + + this.dispatchEvent( { type: 'volumechange' } ); + + }, + + /** + * Returns the video element + * @memberOf VideoPanorama + * @instance + * @returns {HTMLElement} + */ + getVideoElement: function () { + + return this.videoElement; + + }, + + /** + * Dispose video panorama + * @memberOf VideoPanorama + * @instance + */ + dispose: function () { + + const { material: { map } } = this; + + this.pauseVideo(); + + this.removeEventListener( 'leave', this.pauseVideo.bind( this ) ); + this.removeEventListener( 'enter-fade-start', this.resumeVideoProgress.bind( this ) ); + this.removeEventListener( 'video-toggle', this.toggleVideo.bind( this ) ); + this.removeEventListener( 'video-time', this.setVideoCurrentTime.bind( this ) ); + + if ( map ) { map.dispose(); } + + Panorama.prototype.dispose.call( this ); + + } + + } ); + + /** + * @classdesc Google Street View Loader + * @constructor + * @param {object} parameters + */ + function GoogleStreetviewLoader ( parameters = {} ) { + + this._parameters = parameters; + this._zoom = null; + this._panoId = null; + this._panoClient = new google.maps.StreetViewService(); + this._count = 0; + this._total = 0; + this._canvas = []; + this._ctx = []; + this._wc = 0; + this._hc = 0; + this.result = null; + this.rotation = 0; + this.copyright = ''; + this.onSizeChange = null; + this.onPanoramaLoad = null; + + this.levelsW = [ 1, 2, 4, 7, 13, 26 ]; + this.levelsH = [ 1, 1, 2, 4, 7, 13 ]; + + this.widths = [ 416, 832, 1664, 3328, 6656, 13312 ]; + this.heights = [ 416, 416, 832, 1664, 3328, 6656 ]; + + this.maxW = 6656; + this.maxH = 6656; + + let gl; + + try { + + const canvas = document.createElement( 'canvas' ); + + gl = canvas.getContext( 'experimental-webgl' ); + + if( !gl ) { + + gl = canvas.getContext( 'webgl' ); + + } + + } + catch ( error ) { + + } + + this.maxW = Math.max( gl.getParameter( gl.MAX_TEXTURE_SIZE ), this.maxW ); + this.maxH = Math.max( gl.getParameter( gl.MAX_TEXTURE_SIZE ), this.maxH ); + + } + + Object.assign( GoogleStreetviewLoader.prototype, { + + constructor: GoogleStreetviewLoader, + + /** + * Set progress + * @param {number} loaded + * @param {number} total + * @memberOf GoogleStreetviewLoader + * @instance + */ + setProgress: function ( loaded, total ) { + + if ( this.onProgress ) { + + this.onProgress( { loaded: loaded, total: total } ); + + } + + }, + + /** + * Adapt texture to zoom + * @memberOf GoogleStreetviewLoader + * @instance + */ + adaptTextureToZoom: function () { + + const w = this.widths [ this._zoom ]; + const h = this.heights[ this._zoom ]; + + const maxW = this.maxW; + const maxH = this.maxH; + + this._wc = Math.ceil( w / maxW ); + this._hc = Math.ceil( h / maxH ); + + for( let y = 0; y < this._hc; y++ ) { + for( let x = 0; x < this._wc; x++ ) { + const c = document.createElement( 'canvas' ); + if( x < ( this._wc - 1 ) ) c.width = maxW; else c.width = w - ( maxW * x ); + if( y < ( this._hc - 1 ) ) c.height = maxH; else c.height = h - ( maxH * y ); + this._canvas.push( c ); + this._ctx.push( c.getContext( '2d' ) ); + } + } + + }, + + /** + * Compose from tile + * @param {number} x + * @param {number} y + * @param {*} texture + * @memberOf GoogleStreetviewLoader + * @instance + */ + composeFromTile: function ( x, y, texture ) { + + const maxW = this.maxW; + const maxH = this.maxH; + + x *= 512; + y *= 512; + + const px = Math.floor( x / maxW ); + const py = Math.floor( y / maxH ); + + x -= px * maxW; + y -= py * maxH; + + this._ctx[ py * this._wc + px ].drawImage( texture, 0, 0, texture.width, texture.height, x, y, 512, 512 ); + + this.progress(); + + }, + + /** + * Progress + * @memberOf GoogleStreetviewLoader + * @instance + */ + progress: function() { + + this._count++; + + this.setProgress( this._count, this._total ); + + if ( this._count === this._total) { + + this.canvas = this._canvas; + this.panoId = this._panoId; + this.zoom = this._zoom; + + if ( this.onPanoramaLoad ) { + + this.onPanoramaLoad( this._canvas[ 0 ] ); + + } + + } + }, + + /** + * Compose panorama + * @memberOf GoogleStreetviewLoader + * @instance + */ + composePanorama: function () { + + this.setProgress( 0, 1 ); + + const w = this.levelsW[ this._zoom ]; + const h = this.levelsH[ this._zoom ]; + const self = this; + + this._count = 0; + this._total = w * h; + + const { useWebGL } = this._parameters; + + for( let y = 0; y < h; y++ ) { + for( let x = 0; x < w; x++ ) { + const url = 'https://geo0.ggpht.com/cbk?cb_client=maps_sv.tactile&authuser=0&hl=en&output=tile&zoom=' + this._zoom + '&x=' + x + '&y=' + y + '&panoid=' + this._panoId + '&nbt&fover=2'; + ( function( x, y ) { + if( useWebGL ) { + const texture = TextureLoader.load( url, null, function() { + self.composeFromTile( x, y, texture ); + } ); + } else { + const img = new Image(); + img.addEventListener( 'load', function() { + self.composeFromTile( x, y, this ); + } ); + img.crossOrigin = ''; + img.src = url; + } + } )( x, y ); + } + } + + }, + + /** + * Load + * @param {string} panoid + * @memberOf GoogleStreetviewLoader + * @instance + */ + load: function ( panoid ) { + + this.loadPano( panoid ); + + }, + + /** + * Load panorama + * @param {string} id + * @memberOf GoogleStreetviewLoader + * @instance + */ + loadPano: function( id ) { + + const self = this; + this._panoClient.getPanoramaById( id, function (result, status) { + if (status === google.maps.StreetViewStatus.OK) { + self.result = result; + self.copyright = result.copyright; + self._panoId = result.location.pano; + self.composePanorama(); + } + }); + + }, + + /** + * Set zoom level + * @param {number} z + * @memberOf GoogleStreetviewLoader + * @instance + */ + setZoom: function( z ) { + + this._zoom = z; + this.adaptTextureToZoom(); + } + + } ); + + /** + * @classdesc Google streetview panorama + * @description [How to get Panorama ID]{@link http://stackoverflow.com/questions/29916149/google-maps-streetview-how-to-get-panorama-id} + * @constructor + * @param {string} panoId - Panorama id from Google Streetview + * @param {string} [apiKey] - Google Street View API Key + */ + function GoogleStreetviewPanorama ( panoId, apiKey ) { + + ImagePanorama.call( this ); + + this.panoId = panoId; + + this.gsvLoader = null; + + this.loadRequested = false; + + this.setupGoogleMapAPI( apiKey ); + + } + + GoogleStreetviewPanorama.prototype = Object.assign( Object.create( ImagePanorama.prototype ), { + + constructor: GoogleStreetviewPanorama, + + /** + * Load Google Street View by panorama id + * @param {string} panoId - Gogogle Street View panorama id + * @memberOf GoogleStreetviewPanorama + * @instance + */ + load: function ( panoId ) { + + this.loadRequested = true; + + panoId = ( panoId || this.panoId ) || {}; + + if ( panoId && this.gsvLoader ) { + + this.loadGSVLoader( panoId ); + + } + + }, + + /** + * Setup Google Map API + * @param {string} apiKey + * @memberOf GoogleStreetviewPanorama + * @instance + */ + setupGoogleMapAPI: function ( apiKey ) { + + const script = document.createElement( 'script' ); + script.src = 'https://maps.googleapis.com/maps/api/js?'; + script.src += apiKey ? 'key=' + apiKey : ''; + script.onreadystatechange = this.setGSVLoader.bind( this ); + script.onload = this.setGSVLoader.bind( this ); + + document.querySelector( 'head' ).appendChild( script ); + + }, + + /** + * Set GSV Loader + * @memberOf GoogleStreetviewPanorama + * @instance + */ + setGSVLoader: function () { + + this.gsvLoader = new GoogleStreetviewLoader(); + + if ( this.loadRequested ) { + + this.load(); + + } + + }, + + /** + * Get GSV Loader + * @memberOf GoogleStreetviewPanorama + * @instance + * @return {GoogleStreetviewLoader} GSV Loader instance + */ + getGSVLoader: function () { + + return this.gsvLoader; + + }, + + /** + * Load GSV Loader + * @param {string} panoId - Gogogle Street View panorama id + * @memberOf GoogleStreetviewPanorama + * @instance + */ + loadGSVLoader: function ( panoId ) { + + this.loadRequested = false; + + this.gsvLoader.onProgress = this.onProgress.bind( this ); + + this.gsvLoader.onPanoramaLoad = this.onLoad.bind( this ); + + this.gsvLoader.setZoom( this.getZoomLevel() ); + + this.gsvLoader.load( panoId ); + + this.gsvLoader.loaded = true; + }, + + /** + * This will be called when panorama is loaded + * @param {HTMLCanvasElement} canvas - Canvas where the tiles have been drawn + * @memberOf GoogleStreetviewPanorama + * @instance + */ + onLoad: function ( canvas ) { + + ImagePanorama.prototype.onLoad.call( this, new THREE.Texture( canvas ) ); + + }, + + /** + * Reset + * @memberOf GoogleStreetviewPanorama + * @instance + */ + reset: function () { + + this.gsvLoader = undefined; + + ImagePanorama.prototype.reset.call( this ); + + } + + } ); + + /** + * Stereographic projection shader + * based on http://notlion.github.io/streetview-stereographic + * @author pchen66 + */ + + /** + * @description Stereograhpic Shader + * @module StereographicShader + * @property {object} uniforms + * @property {THREE.Texture} uniforms.tDiffuse diffuse map + * @property {number} uniforms.resolution image resolution + * @property {THREE.Matrix4} uniforms.transform transformation matrix + * @property {number} uniforms.zoom image zoom factor + * @property {number} uniforms.opacity image opacity + * @property {string} vertexShader vertex shader + * @property {string} fragmentShader fragment shader + */ + const StereographicShader = { + + uniforms: { + + 'tDiffuse': { value: new THREE.Texture() }, + 'resolution': { value: 1.0 }, + 'transform': { value: new THREE.Matrix4() }, + 'zoom': { value: 1.0 }, + 'opacity': { value: 1.0 } + + }, + + vertexShader: [ + + 'varying vec2 vUv;', + + 'void main() {', + + 'vUv = uv;', + 'gl_Position = vec4( position, 1.0 );', + + '}' + + ].join( '\n' ), + + fragmentShader: [ + + 'uniform sampler2D tDiffuse;', + 'uniform float resolution;', + 'uniform mat4 transform;', + 'uniform float zoom;', + 'uniform float opacity;', + + 'varying vec2 vUv;', + + 'const float PI = 3.141592653589793;', + + 'void main(){', + + 'vec2 position = -1.0 + 2.0 * vUv;', + + 'position *= vec2( zoom * resolution, zoom * 0.5 );', + + 'float x2y2 = position.x * position.x + position.y * position.y;', + 'vec3 sphere_pnt = vec3( 2. * position, x2y2 - 1. ) / ( x2y2 + 1. );', + + 'sphere_pnt = vec3( transform * vec4( sphere_pnt, 1.0 ) );', + + 'vec2 sampleUV = vec2(', + '(atan(sphere_pnt.y, sphere_pnt.x) / PI + 1.0) * 0.5,', + '(asin(sphere_pnt.z) / PI + 0.5)', + ');', + + 'gl_FragColor = texture2D( tDiffuse, sampleUV );', + + 'gl_FragColor.a *= opacity;', + + '}' + + ].join( '\n' ) + + }; + + /** + * @classdesc Little Planet + * @constructor + * @param {string} type - Type of little planet basic class + * @param {string} source - URL for the image source + * @param {number} [size=10000] - Size of plane geometry + * @param {number} [ratio=0.5] - Ratio of plane geometry's height against width + */ + function LittlePlanet ( type = 'image', source, size = 10000, ratio = 0.5 ) { + + if ( type === 'image' ) { + + ImagePanorama.call( this, source, this.createGeometry( size, ratio ), this.createMaterial( size ) ); + + } + + this.size = size; + this.ratio = ratio; + this.EPS = 0.000001; + this.frameId = null; + + this.dragging = false; + this.userMouse = new THREE.Vector2(); + + this.quatA = new THREE.Quaternion(); + this.quatB = new THREE.Quaternion(); + this.quatCur = new THREE.Quaternion(); + this.quatSlerp = new THREE.Quaternion(); + + this.vectorX = new THREE.Vector3( 1, 0, 0 ); + this.vectorY = new THREE.Vector3( 0, 1, 0 ); + + this.addEventListener( 'window-resize', this.onWindowResize ); + + } + + LittlePlanet.prototype = Object.assign( Object.create( ImagePanorama.prototype ), { + + constructor: LittlePlanet, + + add: function ( object ) { + + if ( arguments.length > 1 ) { + + for ( let i = 0; i < arguments.length; i ++ ) { + + this.add( arguments[ i ] ); + + } + + return this; + + } + + if ( object instanceof Infospot ) { + + object.material.depthTest = false; + + } + + ImagePanorama.prototype.add.call( this, object ); + + }, + + createGeometry: function ( size, ratio ) { + + return new THREE.PlaneBufferGeometry( size, size * ratio ); + + }, + + createMaterial: function ( size ) { + + const shader = Object.assign( {}, StereographicShader ), uniforms = shader.uniforms; + + uniforms.zoom.value = size; + uniforms.opacity.value = 0.0; + + return new THREE.ShaderMaterial( { + + uniforms: uniforms, + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader, + side: THREE.BackSide, + transparent: true + + } ); + + }, + + registerMouseEvents: function () { + + this.container.addEventListener( 'mousedown', this.onMouseDown.bind( this ), { passive: true } ); + this.container.addEventListener( 'mousemove', this.onMouseMove.bind( this ), { passive: true } ); + this.container.addEventListener( 'mouseup', this.onMouseUp.bind( this ), { passive: true } ); + this.container.addEventListener( 'touchstart', this.onMouseDown.bind( this ), { passive: true } ); + this.container.addEventListener( 'touchmove', this.onMouseMove.bind( this ), { passive: true } ); + this.container.addEventListener( 'touchend', this.onMouseUp.bind( this ), { passive: true } ); + this.container.addEventListener( 'mousewheel', this.onMouseWheel.bind( this ), { passive: false } ); + this.container.addEventListener( 'DOMMouseScroll', this.onMouseWheel.bind( this ), { passive: false } ); + this.container.addEventListener( 'contextmenu', this.onContextMenu.bind( this ), { passive: true } ); + + }, + + unregisterMouseEvents: function () { + + this.container.removeEventListener( 'mousedown', this.onMouseDown.bind( this ), false ); + this.container.removeEventListener( 'mousemove', this.onMouseMove.bind( this ), false ); + this.container.removeEventListener( 'mouseup', this.onMouseUp.bind( this ), false ); + this.container.removeEventListener( 'touchstart', this.onMouseDown.bind( this ), false ); + this.container.removeEventListener( 'touchmove', this.onMouseMove.bind( this ), false ); + this.container.removeEventListener( 'touchend', this.onMouseUp.bind( this ), false ); + this.container.removeEventListener( 'mousewheel', this.onMouseWheel.bind( this ), false ); + this.container.removeEventListener( 'DOMMouseScroll', this.onMouseWheel.bind( this ), false ); + this.container.removeEventListener( 'contextmenu', this.onContextMenu.bind( this ), false ); + + }, + + onMouseDown: function ( event ) { + + const inputCount = ( event.touches && event.touches.length ) || 1 ; + + switch ( inputCount ) { + + case 1: + + const x = ( event.clientX >= 0 ) ? event.clientX : event.touches[ 0 ].clientX; + const y = ( event.clientY >= 0 ) ? event.clientY : event.touches[ 0 ].clientY; + + this.dragging = true; + this.userMouse.set( x, y ); + + break; + + case 2: + + const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + const distance = Math.sqrt( dx * dx + dy * dy ); + this.userMouse.pinchDistance = distance; + + break; + + default: + + break; + + } + + this.onUpdateCallback(); + + }, + + onMouseMove: function ( event ) { + + const inputCount = ( event.touches && event.touches.length ) || 1 ; + + switch ( inputCount ) { + + case 1: + + const x = ( event.clientX >= 0 ) ? event.clientX : event.touches[ 0 ].clientX; + const y = ( event.clientY >= 0 ) ? event.clientY : event.touches[ 0 ].clientY; + + const angleX = THREE.Math.degToRad( x - this.userMouse.x ) * 0.4; + const angleY = THREE.Math.degToRad( y - this.userMouse.y ) * 0.4; + + if ( this.dragging ) { + this.quatA.setFromAxisAngle( this.vectorY, angleX ); + this.quatB.setFromAxisAngle( this.vectorX, angleY ); + this.quatCur.multiply( this.quatA ).multiply( this.quatB ); + this.userMouse.set( x, y ); + } + + break; + + case 2: + + const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + const distance = Math.sqrt( dx * dx + dy * dy ); + + this.addZoomDelta( this.userMouse.pinchDistance - distance ); + + break; + + default: + + break; + + } + + }, + + onMouseUp: function () { + + this.dragging = false; + + }, + + onMouseWheel: function ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + let delta = 0; + + if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 + + delta = event.wheelDelta; + + } else if ( event.detail !== undefined ) { // Firefox + + delta = - event.detail; + + } + + this.addZoomDelta( delta ); + this.onUpdateCallback(); + + }, + + addZoomDelta: function ( delta ) { + + const uniforms = this.material.uniforms; + const lowerBound = this.size * 0.1; + const upperBound = this.size * 10; + + uniforms.zoom.value += delta; + + if ( uniforms.zoom.value <= lowerBound ) { + + uniforms.zoom.value = lowerBound; + + } else if ( uniforms.zoom.value >= upperBound ) { + + uniforms.zoom.value = upperBound; + + } + + }, + + onUpdateCallback: function () { + + this.frameId = window.requestAnimationFrame( this.onUpdateCallback.bind( this ) ); + + this.quatSlerp.slerp( this.quatCur, 0.1 ); + + if ( this.material ) { + + this.material.uniforms.transform.value.makeRotationFromQuaternion( this.quatSlerp ); + + } + + if ( !this.dragging && 1.0 - this.quatSlerp.clone().dot( this.quatCur ) < this.EPS ) { + + window.cancelAnimationFrame( this.frameId ); + + } + + }, + + reset: function () { + + this.quatCur.set( 0, 0, 0, 1 ); + this.quatSlerp.set( 0, 0, 0, 1 ); + this.onUpdateCallback(); + + }, + + onLoad: function ( texture ) { + + this.material.uniforms.resolution.value = this.container.clientWidth / this.container.clientHeight; + + this.registerMouseEvents(); + this.onUpdateCallback(); + + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'disableControl' } ); + + ImagePanorama.prototype.onLoad.call( this, texture ); + + }, + + onLeave: function () { + + this.unregisterMouseEvents(); + + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'enableControl', data: CONTROLS.ORBIT } ); + + window.cancelAnimationFrame( this.frameId ); + + ImagePanorama.prototype.onLeave.call( this ); + + }, + + onWindowResize: function () { + + this.material.uniforms.resolution.value = this.container.clientWidth / this.container.clientHeight; + + }, + + onContextMenu: function () { + + this.dragging = false; + + }, + + dispose: function () { + + this.unregisterMouseEvents(); + + ImagePanorama.prototype.dispose.call( this ); + + } + + }); + + /** + * @classdesc Image Little Planet + * @constructor + * @param {string} source - URL for the image source + * @param {number} [size=10000] - Size of plane geometry + * @param {number} [ratio=0.5] - Ratio of plane geometry's height against width + */ + function ImageLittlePlanet ( source, size, ratio ) { + + LittlePlanet.call( this, 'image', source, size, ratio ); + + } + + ImageLittlePlanet.prototype = Object.assign( Object.create( LittlePlanet.prototype ), { + + constructor: ImageLittlePlanet, + + /** + * On loaded with texture + * @param {THREE.Texture} texture + * @memberOf ImageLittlePlanet + * @instance + */ + onLoad: function ( texture ) { + + this.updateTexture( texture ); + + LittlePlanet.prototype.onLoad.call( this, texture ); + + }, + + /** + * Update texture + * @param {THREE.Texture} texture + * @memberOf ImageLittlePlanet + * @instance + */ + updateTexture: function ( texture ) { + + texture.minFilter = texture.magFilter = THREE.LinearFilter; + + this.material.uniforms[ 'tDiffuse' ].value = texture; + + }, + + /** + * Dispose + * @memberOf ImageLittlePlanet + * @instance + */ + dispose: function () { + + const tDiffuse = this.material.uniforms[ 'tDiffuse' ]; + + if ( tDiffuse && tDiffuse.value ) { + + tDiffuse.value.dispose(); + + } + + LittlePlanet.prototype.dispose.call( this ); + + } + + } ); + + /** + * @classdesc Camera panorama + * @description See {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints|MediaStreamConstraints} for constraints + * @param {object} - camera constraints + * @constructor + */ + function CameraPanorama ( constraints ) { + + const radius = 5000; + const geometry = new THREE.SphereBufferGeometry( radius, 60, 40 ); + const material = new THREE.MeshBasicMaterial( { visible: false }); + + Panorama.call( this, geometry, material ); + + this.media = new Media( constraints ); + this.radius = radius; + + this.addEventListener( 'enter', this.start.bind( this ) ); + this.addEventListener( 'leave', this.stop.bind( this ) ); + this.addEventListener( 'panolens-container', this.onPanolensContainer.bind( this ) ); + this.addEventListener( 'panolens-scene', this.onPanolensScene.bind( this ) ); + + } + + CameraPanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { + + constructor: CameraPanorama, + + /** + * On container event + * @param {object} event + * @memberOf CameraPanorama + * @instance + */ + onPanolensContainer: function ( { container } ) { + + this.media.setContainer( container ); + + }, + + /** + * On scene event + * @param {object} event + * @memberOf CameraPanorama + * @instance + */ + onPanolensScene: function ( { scene } ) { + + this.media.setScene( scene ); + + }, + + /** + * Start camera streaming + * @memberOf CameraPanorama + * @instance + * @returns {Promise} + */ + start: function () { + + return this.media.start(); + + }, + + /** + * Stop camera streaming + * @memberOf CameraPanorama + * @instance + */ + stop: function () { + + this.media.stop(); + + }, + + } ); + + /** + * @classdesc Orbit Controls + * @constructor + * @external OrbitControls + * @param {THREE.Object} object + * @param {HTMLElement} domElement + */ + function OrbitControls ( object, domElement ) { + + this.object = object; + this.domElement = ( domElement !== undefined ) ? domElement : document; + this.frameId = null; + + // API + + // Set to false to disable this control + this.enabled = true; + + /* + * "target" sets the location of focus, where the control orbits around + * and where it pans with respect to. + */ + this.target = new THREE.Vector3(); + + // center is old, deprecated; use "target" instead + this.center = this.target; + + /* + * This option actually enables dollying in and out; left as "zoom" for + * backwards compatibility + */ + this.noZoom = false; + this.zoomSpeed = 1.0; + + // Limits to how far you can dolly in and out ( PerspectiveCamera only ) + this.minDistance = 0; + this.maxDistance = Infinity; + + // Limits to how far you can zoom in and out ( OrthographicCamera only ) + this.minZoom = 0; + this.maxZoom = Infinity; + + // Set to true to disable this control + this.noRotate = false; + this.rotateSpeed = -0.15; + + // Set to true to disable this control + this.noPan = true; + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + // Set to true to automatically rotate around the target + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 + + /* + * How far you can orbit vertically, upper and lower limits. + * Range is 0 to Math.PI radians. + */ + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + // Momentum + this.momentumDampingFactor = 0.90; + this.momentumScalingFactor = -0.005; + this.momentumKeydownFactor = 20; + + // Fov + this.minFov = 30; + this.maxFov = 120; + + /* + * How far you can orbit horizontally, upper and lower limits. + * If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. + */ + this.minAzimuthAngle = - Infinity; // radians + this.maxAzimuthAngle = Infinity; // radians + + // Set to true to disable use of the keys + this.noKeys = false; + + // The four arrow keys + this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; + + // Mouse buttons + this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; + + /* + * ////////// + * internals + */ + + var scope = this; + + var EPS = 10e-8; + var MEPS = 10e-5; + + var rotateStart = new THREE.Vector2(); + var rotateEnd = new THREE.Vector2(); + var rotateDelta = new THREE.Vector2(); + + var panStart = new THREE.Vector2(); + var panEnd = new THREE.Vector2(); + var panDelta = new THREE.Vector2(); + var panOffset = new THREE.Vector3(); + + var offset = new THREE.Vector3(); + + var dollyStart = new THREE.Vector2(); + var dollyEnd = new THREE.Vector2(); + var dollyDelta = new THREE.Vector2(); + + var theta = 0; + var phi = 0; + var phiDelta = 0; + var thetaDelta = 0; + var scale = 1; + var pan = new THREE.Vector3(); + + var lastPosition = new THREE.Vector3(); + var lastQuaternion = new THREE.Quaternion(); + + var momentumLeft = 0, momentumUp = 0; + var eventPrevious; + var momentumOn = false; + + var keyUp, keyBottom, keyLeft, keyRight; + + var STATE = { NONE: -1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 }; + + var state = STATE.NONE; + + // for reset + + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.zoom0 = this.object.zoom; + + // so camera.up is the orbit axis + + var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); + var quatInverse = quat.clone().inverse(); + + // events + + var changeEvent = { type: 'change' }; + var startEvent = { type: 'start' }; + var endEvent = { type: 'end' }; + + this.setLastQuaternion = function ( quaternion ) { + lastQuaternion.copy( quaternion ); + scope.object.quaternion.copy( quaternion ); + }; + + this.getLastPosition = function () { + return lastPosition; + }; + + this.rotateLeft = function ( angle ) { + + if ( angle === undefined ) { + + angle = getAutoRotationAngle(); + + } + + thetaDelta -= angle; + + + }; + + this.rotateUp = function ( angle ) { + + if ( angle === undefined ) { + + angle = getAutoRotationAngle(); + + } + + phiDelta -= angle; + + }; + + // pass in distance in world space to move left + this.panLeft = function ( distance ) { + + var te = this.object.matrix.elements; + + // get X column of matrix + panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] ); + panOffset.multiplyScalar( - distance ); + + pan.add( panOffset ); + + }; + + // pass in distance in world space to move up + this.panUp = function ( distance ) { + + var te = this.object.matrix.elements; + + // get Y column of matrix + panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] ); + panOffset.multiplyScalar( distance ); + + pan.add( panOffset ); + + }; + + /* + * pass in x,y of change desired in pixel space, + * right and down are positive + */ + this.pan = function ( deltaX, deltaY ) { + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + if ( scope.object instanceof THREE.PerspectiveCamera ) { + + // perspective + var position = scope.object.position; + var offset = position.clone().sub( scope.target ); + var targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + + // we actually don't use screenWidth, since perspective camera is fixed to screen height + scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight ); + scope.panUp( 2 * deltaY * targetDistance / element.clientHeight ); + + } else if ( scope.object instanceof THREE.OrthographicCamera ) { + + // orthographic + scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth ); + scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight ); + + } else { + + // camera neither orthographic or perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + + } + + }; + + this.momentum = function(){ + + if ( !momentumOn ) return; + + if ( Math.abs( momentumLeft ) < MEPS && Math.abs( momentumUp ) < MEPS ) { + + momentumOn = false; + return; + } + + momentumUp *= this.momentumDampingFactor; + momentumLeft *= this.momentumDampingFactor; + + thetaDelta -= this.momentumScalingFactor * momentumLeft; + phiDelta -= this.momentumScalingFactor * momentumUp; + + }; + + this.dollyIn = function ( dollyScale ) { + + if ( dollyScale === undefined ) { + + dollyScale = getZoomScale(); + + } + + if ( scope.object instanceof THREE.PerspectiveCamera ) { + + scale /= dollyScale; + + } else if ( scope.object instanceof THREE.OrthographicCamera ) { + + scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( changeEvent ); + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + + } + + }; + + this.dollyOut = function ( dollyScale ) { + + if ( dollyScale === undefined ) { + + dollyScale = getZoomScale(); + + } + + if ( scope.object instanceof THREE.PerspectiveCamera ) { + + scale *= dollyScale; + + } else if ( scope.object instanceof THREE.OrthographicCamera ) { + + scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( changeEvent ); + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + + } + + }; + + this.update = function ( ignoreUpdate ) { + + var position = this.object.position; + + offset.copy( position ).sub( this.target ); + + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); + + // angle from z-axis around y-axis + + theta = Math.atan2( offset.x, offset.z ); + + // angle from y-axis + + phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); + + if ( this.autoRotate && state === STATE.NONE ) { + + this.rotateLeft( getAutoRotationAngle() ); + + } + + this.momentum(); + + theta += thetaDelta; + phi += phiDelta; + + // restrict theta to be between desired limits + theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) ); + + // restrict phi to be between desired limits + phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); + + // restrict phi to be betwee EPS and PI-EPS + phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); + + var radius = offset.length() * scale; + + // restrict radius to be between desired limits + radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); + + // move target to panned location + this.target.add( pan ); + + offset.x = radius * Math.sin( phi ) * Math.sin( theta ); + offset.y = radius * Math.cos( phi ); + offset.z = radius * Math.sin( phi ) * Math.cos( theta ); + + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); + + position.copy( this.target ).add( offset ); + + this.object.lookAt( this.target ); + + thetaDelta = 0; + phiDelta = 0; + scale = 1; + pan.set( 0, 0, 0 ); + + /* + * update condition is: + * min(camera displacement, camera rotation in radians)^2 > EPS + * using small-angle approximation cos(x/2) = 1 - x^2 / 8 + */ + if ( lastPosition.distanceToSquared( this.object.position ) > EPS + || 8 * (1 - lastQuaternion.dot(this.object.quaternion)) > EPS ) { + + if ( ignoreUpdate !== true ) { this.dispatchEvent( changeEvent ); } + + lastPosition.copy( this.object.position ); + lastQuaternion.copy (this.object.quaternion ); + + } + + }; + + + this.reset = function () { + + state = STATE.NONE; + + this.target.copy( this.target0 ); + this.object.position.copy( this.position0 ); + this.object.zoom = this.zoom0; + + this.object.updateProjectionMatrix(); + this.dispatchEvent( changeEvent ); + + this.update(); + + }; + + this.getPolarAngle = function () { + + return phi; + + }; + + this.getAzimuthalAngle = function () { + + return theta; + + }; + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function onMouseDown( event ) { + + momentumOn = false; + + momentumLeft = momentumUp = 0; + + if ( scope.enabled === false ) return; + event.preventDefault(); + + if ( event.button === scope.mouseButtons.ORBIT ) { + if ( scope.noRotate === true ) return; + + state = STATE.ROTATE; + + rotateStart.set( event.clientX, event.clientY ); + + } else if ( event.button === scope.mouseButtons.ZOOM ) { + if ( scope.noZoom === true ) return; + + state = STATE.DOLLY; + + dollyStart.set( event.clientX, event.clientY ); + + } else if ( event.button === scope.mouseButtons.PAN ) { + if ( scope.noPan === true ) return; + + state = STATE.PAN; + + panStart.set( event.clientX, event.clientY ); + + } + + if ( state !== STATE.NONE ) { + document.addEventListener( 'mousemove', onMouseMove, false ); + document.addEventListener( 'mouseup', onMouseUp, false ); + scope.dispatchEvent( startEvent ); + } + + scope.update(); + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + if ( state === STATE.ROTATE ) { + + if ( scope.noRotate === true ) return; + + rotateEnd.set( event.clientX, event.clientY ); + rotateDelta.subVectors( rotateEnd, rotateStart ); + + // rotating across whole screen goes 360 degrees around + scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); + + // rotating up and down along whole screen attempts to go 360, but limited to 180 + scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); + + rotateStart.copy( rotateEnd ); + + if( eventPrevious ){ + momentumLeft = event.clientX - eventPrevious.clientX; + momentumUp = event.clientY - eventPrevious.clientY; + } + + eventPrevious = event; + + } else if ( state === STATE.DOLLY ) { + + if ( scope.noZoom === true ) return; + + dollyEnd.set( event.clientX, event.clientY ); + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + scope.dollyIn(); + + } else if ( dollyDelta.y < 0 ) { + + scope.dollyOut(); + + } + + dollyStart.copy( dollyEnd ); + + } else if ( state === STATE.PAN ) { + + if ( scope.noPan === true ) return; + + panEnd.set( event.clientX, event.clientY ); + panDelta.subVectors( panEnd, panStart ); + + scope.pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + } + + if ( state !== STATE.NONE ) scope.update(); + + } + + function onMouseUp( /* event */ ) { + + momentumOn = true; + + eventPrevious = undefined; + + if ( scope.enabled === false ) return; + + document.removeEventListener( 'mousemove', onMouseMove, false ); + document.removeEventListener( 'mouseup', onMouseUp, false ); + scope.dispatchEvent( endEvent ); + state = STATE.NONE; + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.noZoom === true || state !== STATE.NONE ) return; + + event.preventDefault(); + event.stopPropagation(); + + var delta = 0; + + if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 + + delta = event.wheelDelta; + + } else if ( event.detail !== undefined ) { // Firefox + + delta = - event.detail; + + } + + if ( delta > 0 ) { + + // scope.dollyOut(); + scope.object.fov = ( scope.object.fov < scope.maxFov ) + ? scope.object.fov + 1 + : scope.maxFov; + scope.object.updateProjectionMatrix(); + + } else if ( delta < 0 ) { + + // scope.dollyIn(); + scope.object.fov = ( scope.object.fov > scope.minFov ) + ? scope.object.fov - 1 + : scope.minFov; + scope.object.updateProjectionMatrix(); + + } + + scope.update(); + scope.dispatchEvent( changeEvent ); + scope.dispatchEvent( startEvent ); + scope.dispatchEvent( endEvent ); + + } + + function onKeyUp ( event ) { + + switch ( event.keyCode ) { + + case scope.keys.UP: + keyUp = false; + break; + + case scope.keys.BOTTOM: + keyBottom = false; + break; + + case scope.keys.LEFT: + keyLeft = false; + break; + + case scope.keys.RIGHT: + keyRight = false; + break; + + } + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.noKeys === true || scope.noRotate === true ) return; + + switch ( event.keyCode ) { + + case scope.keys.UP: + keyUp = true; + break; + + case scope.keys.BOTTOM: + keyBottom = true; + break; + + case scope.keys.LEFT: + keyLeft = true; + break; + + case scope.keys.RIGHT: + keyRight = true; + break; + + } + + if (keyUp || keyBottom || keyLeft || keyRight) { + + momentumOn = true; + + if (keyUp) momentumUp = - scope.rotateSpeed * scope.momentumKeydownFactor; + if (keyBottom) momentumUp = scope.rotateSpeed * scope.momentumKeydownFactor; + if (keyLeft) momentumLeft = - scope.rotateSpeed * scope.momentumKeydownFactor; + if (keyRight) momentumLeft = scope.rotateSpeed * scope.momentumKeydownFactor; + + } + + } + + function touchstart( event ) { + + momentumOn = false; + + momentumLeft = momentumUp = 0; + + if ( scope.enabled === false ) return; + + switch ( event.touches.length ) { + + case 1: // one-fingered touch: rotate + + if ( scope.noRotate === true ) return; + + state = STATE.TOUCH_ROTATE; + + rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + break; + + case 2: // two-fingered touch: dolly + + if ( scope.noZoom === true ) return; + + state = STATE.TOUCH_DOLLY; + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyStart.set( 0, distance ); + + break; + + case 3: // three-fingered touch: pan + + if ( scope.noPan === true ) return; + + state = STATE.TOUCH_PAN; + + panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) scope.dispatchEvent( startEvent ); + + } + + function touchmove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + switch ( event.touches.length ) { + + case 1: // one-fingered touch: rotate + + if ( scope.noRotate === true ) return; + if ( state !== STATE.TOUCH_ROTATE ) return; + + rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + rotateDelta.subVectors( rotateEnd, rotateStart ); + + // rotating across whole screen goes 360 degrees around + scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); + // rotating up and down along whole screen attempts to go 360, but limited to 180 + scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); + + rotateStart.copy( rotateEnd ); + + if( eventPrevious ){ + momentumLeft = event.touches[ 0 ].pageX - eventPrevious.pageX; + momentumUp = event.touches[ 0 ].pageY - eventPrevious.pageY; + } + + eventPrevious = { + pageX: event.touches[ 0 ].pageX, + pageY: event.touches[ 0 ].pageY, + }; + + scope.update(); + break; + + case 2: // two-fingered touch: dolly + + if ( scope.noZoom === true ) return; + if ( state !== STATE.TOUCH_DOLLY ) return; + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyEnd.set( 0, distance ); + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y < 0 ) { + + scope.object.fov = ( scope.object.fov < scope.maxFov ) + ? scope.object.fov + 1 + : scope.maxFov; + scope.object.updateProjectionMatrix(); + + } else if ( dollyDelta.y > 0 ) { + + scope.object.fov = ( scope.object.fov > scope.minFov ) + ? scope.object.fov - 1 + : scope.minFov; + scope.object.updateProjectionMatrix(); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + scope.dispatchEvent( changeEvent ); + break; + + case 3: // three-fingered touch: pan + + if ( scope.noPan === true ) return; + if ( state !== STATE.TOUCH_PAN ) return; + + panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + panDelta.subVectors( panEnd, panStart ); + + scope.pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + break; + + default: + + state = STATE.NONE; + + } + + } + + function touchend( /* event */ ) { + + momentumOn = true; + + eventPrevious = undefined; + + if ( scope.enabled === false ) return; + + scope.dispatchEvent( endEvent ); + state = STATE.NONE; + + } + + this.dispose = function() { + + this.domElement.removeEventListener( 'mousedown', onMouseDown ); + this.domElement.removeEventListener( 'mousewheel', onMouseWheel ); + this.domElement.removeEventListener( 'DOMMouseScroll', onMouseWheel ); + + this.domElement.removeEventListener( 'touchstart', touchstart ); + this.domElement.removeEventListener( 'touchend', touchend ); + this.domElement.removeEventListener( 'touchmove', touchmove ); + + window.removeEventListener( 'keyup', onKeyUp ); + window.removeEventListener( 'keydown', onKeyDown ); + + }; + + // this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); + this.domElement.addEventListener( 'mousedown', onMouseDown, { passive: false } ); + this.domElement.addEventListener( 'mousewheel', onMouseWheel, { passive: false } ); + this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, { passive: false } ); // firefox + + this.domElement.addEventListener( 'touchstart', touchstart, { passive: false } ); + this.domElement.addEventListener( 'touchend', touchend, { passive: false } ); + this.domElement.addEventListener( 'touchmove', touchmove, { passive: false } ); + + window.addEventListener( 'keyup', onKeyUp, { passive: false } ); + window.addEventListener( 'keydown', onKeyDown, { passive: false } ); + + // force an update at start + this.update(); + + } + OrbitControls.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype ), { + + constructor: OrbitControls + + } ); + + /** + * @classdesc Device Orientation Control + * @constructor + * @external DeviceOrientationControls + * @param {THREE.Camera} camera + * @param {HTMLElement} domElement + */ + function DeviceOrientationControls ( camera, domElement ) { + + var scope = this; + var changeEvent = { type: 'change' }; + + var rotY = 0; + var rotX = 0; + var tempX = 0; + var tempY = 0; + + this.camera = camera; + this.camera.rotation.reorder( 'YXZ' ); + this.domElement = ( domElement !== undefined ) ? domElement : document; + + this.enabled = true; + + this.deviceOrientation = {}; + this.screenOrientation = 0; + + this.alpha = 0; + this.alphaOffsetAngle = 0; + + + var onDeviceOrientationChangeEvent = function( event ) { + + scope.deviceOrientation = event; + + }; + + var onScreenOrientationChangeEvent = function() { + + scope.screenOrientation = window.orientation || 0; + + }; + + var onTouchStartEvent = function (event) { + + event.preventDefault(); + event.stopPropagation(); + + tempX = event.touches[ 0 ].pageX; + tempY = event.touches[ 0 ].pageY; + + }; + + var onTouchMoveEvent = function (event) { + + event.preventDefault(); + event.stopPropagation(); + + rotY += THREE.Math.degToRad( ( event.touches[ 0 ].pageX - tempX ) / 4 ); + rotX += THREE.Math.degToRad( ( tempY - event.touches[ 0 ].pageY ) / 4 ); + + scope.updateAlphaOffsetAngle( rotY ); + + tempX = event.touches[ 0 ].pageX; + tempY = event.touches[ 0 ].pageY; + + }; + + // The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y'' + + var setCameraQuaternion = function( quaternion, alpha, beta, gamma, orient ) { + + var zee = new THREE.Vector3( 0, 0, 1 ); + + var euler = new THREE.Euler(); + + var q0 = new THREE.Quaternion(); + + var q1 = new THREE.Quaternion( - Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis + + var vectorFingerY; + var fingerQY = new THREE.Quaternion(); + var fingerQX = new THREE.Quaternion(); + + if ( scope.screenOrientation == 0 ) { + + vectorFingerY = new THREE.Vector3( 1, 0, 0 ); + fingerQY.setFromAxisAngle( vectorFingerY, -rotX ); + + } else if ( scope.screenOrientation == 180 ) { + + vectorFingerY = new THREE.Vector3( 1, 0, 0 ); + fingerQY.setFromAxisAngle( vectorFingerY, rotX ); + + } else if ( scope.screenOrientation == 90 ) { + + vectorFingerY = new THREE.Vector3( 0, 1, 0 ); + fingerQY.setFromAxisAngle( vectorFingerY, rotX ); + + } else if ( scope.screenOrientation == - 90) { + + vectorFingerY = new THREE.Vector3( 0, 1, 0 ); + fingerQY.setFromAxisAngle( vectorFingerY, -rotX ); + + } + + q1.multiply( fingerQY ); + q1.multiply( fingerQX ); + + euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us + + quaternion.setFromEuler( euler ); // orient the device + + quaternion.multiply( q1 ); // camera looks out the back of the device, not the top + + quaternion.multiply( q0.setFromAxisAngle( zee, - orient ) ); // adjust for screen orientation + + }; + + this.connect = function() { + + onScreenOrientationChangeEvent(); // run once on load + + window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent, { passive: true } ); + window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, { passive: true } ); + window.addEventListener( 'deviceorientation', this.update.bind( this ), { passive: true } ); + + scope.domElement.addEventListener( 'touchstart', onTouchStartEvent, { passive: false } ); + scope.domElement.addEventListener( 'touchmove', onTouchMoveEvent, { passive: false } ); + + scope.enabled = true; + + }; + + this.disconnect = function() { + + window.removeEventListener( 'orientationchange', onScreenOrientationChangeEvent, false ); + window.removeEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false ); + window.removeEventListener( 'deviceorientation', this.update.bind( this ), false ); + + scope.domElement.removeEventListener( 'touchstart', onTouchStartEvent, false ); + scope.domElement.removeEventListener( 'touchmove', onTouchMoveEvent, false ); + + scope.enabled = false; + + }; + + this.update = function( ignoreUpdate ) { + + if ( scope.enabled === false ) return; + + var alpha = scope.deviceOrientation.alpha ? THREE.Math.degToRad( scope.deviceOrientation.alpha ) + scope.alphaOffsetAngle : 0; // Z + var beta = scope.deviceOrientation.beta ? THREE.Math.degToRad( scope.deviceOrientation.beta ) : 0; // X' + var gamma = scope.deviceOrientation.gamma ? THREE.Math.degToRad( scope.deviceOrientation.gamma ) : 0; // Y'' + var orient = scope.screenOrientation ? THREE.Math.degToRad( scope.screenOrientation ) : 0; // O + + setCameraQuaternion( scope.camera.quaternion, alpha, beta, gamma, orient ); + scope.alpha = alpha; + + if ( ignoreUpdate !== true ) { scope.dispatchEvent( changeEvent ); } + + }; + + this.updateAlphaOffsetAngle = function( angle ) { + + this.alphaOffsetAngle = angle; + this.update(); + + }; + + this.dispose = function() { + + this.disconnect(); + + }; + + this.connect(); + + } + DeviceOrientationControls.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype), { + + constructor: DeviceOrientationControls + + } ); + + /** + * @classdesc Google Cardboard Effect Composer + * @constructor + * @external CardboardEffect + * @param {THREE.WebGLRenderer} renderer + */ + function CardboardEffect ( renderer ) { + + var _camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); + + var _scene = new THREE.Scene(); + + var _stereo = new THREE.StereoCamera(); + _stereo.aspect = 0.5; + + var _params = { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBAFormat }; + + var _renderTarget = new THREE.WebGLRenderTarget( 512, 512, _params ); + _renderTarget.scissorTest = true; + _renderTarget.texture.generateMipmaps = false; + + /* + * Distortion Mesh ported from: + * https://github.com/borismus/webvr-boilerplate/blob/master/src/distortion/barrel-distortion-fragment.js + */ + + var distortion = new THREE.Vector2( 0.441, 0.156 ); + + var geometry = new THREE.PlaneBufferGeometry( 1, 1, 10, 20 ).removeAttribute( 'normal' ).toNonIndexed(); + + var positions = geometry.attributes.position.array; + var uvs = geometry.attributes.uv.array; + + // duplicate + geometry.attributes.position.count *= 2; + geometry.attributes.uv.count *= 2; + + var positions2 = new Float32Array( positions.length * 2 ); + positions2.set( positions ); + positions2.set( positions, positions.length ); + + var uvs2 = new Float32Array( uvs.length * 2 ); + uvs2.set( uvs ); + uvs2.set( uvs, uvs.length ); + + var vector = new THREE.Vector2(); + var length = positions.length / 3; + + for ( var i = 0, l = positions2.length / 3; i < l; i ++ ) { + + vector.x = positions2[ i * 3 + 0 ]; + vector.y = positions2[ i * 3 + 1 ]; + + var dot = vector.dot( vector ); + var scalar = 1.5 + ( distortion.x + distortion.y * dot ) * dot; + + var offset = i < length ? 0 : 1; + + positions2[ i * 3 + 0 ] = ( vector.x / scalar ) * 1.5 - 0.5 + offset; + positions2[ i * 3 + 1 ] = ( vector.y / scalar ) * 3.0; + + uvs2[ i * 2 ] = ( uvs2[ i * 2 ] + offset ) * 0.5; + + } + + geometry.attributes.position.array = positions2; + geometry.attributes.uv.array = uvs2; + + // + + var material = new THREE.MeshBasicMaterial( { map: _renderTarget.texture } ); + var mesh = new THREE.Mesh( geometry, material ); + _scene.add( mesh ); + + // + + this.setSize = function ( width, height ) { + + renderer.setSize( width, height ); + + var pixelRatio = renderer.getPixelRatio(); + + _renderTarget.setSize( width * pixelRatio, height * pixelRatio ); + + }; + + this.render = function ( scene, camera ) { + + scene.updateMatrixWorld(); + + if ( camera.parent === null ) camera.updateMatrixWorld(); + + _stereo.update( camera ); + + var width = _renderTarget.width / 2; + var height = _renderTarget.height; + + if ( renderer.autoClear ) renderer.clear(); + + _renderTarget.scissor.set( 0, 0, width, height ); + _renderTarget.viewport.set( 0, 0, width, height ); + renderer.setRenderTarget( _renderTarget ); + renderer.render( scene, _stereo.cameraL ); + + renderer.clearDepth(); + + _renderTarget.scissor.set( width, 0, width, height ); + _renderTarget.viewport.set( width, 0, width, height ); + renderer.setRenderTarget( _renderTarget ); + renderer.render( scene, _stereo.cameraR ); + + renderer.clearDepth(); + + renderer.setRenderTarget( null ); + renderer.render( _scene, _camera ); + }; + + } + + /** + * @classdesc Stereo Effect Composer + * @constructor + * @external StereoEffect + * @param {THREE.WebGLRenderer} renderer + */ + const StereoEffect = function ( renderer ) { + + var _stereo = new THREE.StereoCamera(); + _stereo.aspect = 0.5; + var size = new THREE.Vector2(); + + this.setEyeSeparation = function ( eyeSep ) { + + _stereo.eyeSep = eyeSep; + + }; + + this.setSize = function ( width, height ) { + + renderer.setSize( width, height ); + + }; + + this.render = function ( scene, camera ) { + + scene.updateMatrixWorld(); + + if ( camera.parent === null ) camera.updateMatrixWorld(); + + _stereo.update( camera ); + + renderer.getSize( size ); + + if ( renderer.autoClear ) renderer.clear(); + renderer.setScissorTest( true ); + + renderer.setScissor( 0, 0, size.width / 2, size.height ); + renderer.setViewport( 0, 0, size.width / 2, size.height ); + renderer.render( scene, _stereo.cameraL ); + + renderer.setScissor( size.width / 2, 0, size.width / 2, size.height ); + renderer.setViewport( size.width / 2, 0, size.width / 2, size.height ); + renderer.render( scene, _stereo.cameraR ); + + renderer.setScissorTest( false ); + + }; + + }; + + /** + * @classdesc Viewer contains pre-defined scene, camera and renderer + * @constructor + * @param {object} [options] - Use custom or default config options + * @param {HTMLElement} [options.container] - A HTMLElement to host the canvas + * @param {THREE.Scene} [options.scene=THREE.Scene] - A THREE.Scene which contains panorama and 3D objects + * @param {THREE.Camera} [options.camera=THREE.PerspectiveCamera] - A THREE.Camera to view the scene + * @param {THREE.WebGLRenderer} [options.renderer=THREE.WebGLRenderer] - A THREE.WebGLRenderer to render canvas + * @param {boolean} [options.controlBar=true] - Show/hide control bar on the bottom of the container + * @param {array} [options.controlButtons=[]] - Button names to mount on controlBar if controlBar exists, Defaults to ['fullscreen', 'setting', 'video'] + * @param {boolean} [options.autoHideControlBar=false] - Auto hide control bar when click on non-active area + * @param {boolean} [options.autoHideInfospot=true] - Auto hide infospots when click on non-active area + * @param {boolean} [options.horizontalView=false] - Allow only horizontal camera control + * @param {number} [options.clickTolerance=10] - Distance tolerance to tigger click / tap event + * @param {number} [options.cameraFov=60] - Camera field of view value + * @param {boolean} [options.reverseDragging=false] - Reverse dragging direction + * @param {boolean} [options.enableReticle=false] - Enable reticle for mouseless interaction other than VR mode + * @param {number} [options.dwellTime=1500] - Dwell time for reticle selection in ms + * @param {boolean} [options.autoReticleSelect=true] - Auto select a clickable target after dwellTime + * @param {boolean} [options.viewIndicator=false] - Adds an angle view indicator in upper left corner + * @param {number} [options.indicatorSize=30] - Size of View Indicator + * @param {string} [options.output='none'] - Whether and where to output raycast position. Could be 'event', 'console' or 'overlay'. + * @param {boolean} [options.autoRotate=false] - Auto rotate + * @param {number} [options.autoRotateSpeed=2.0] - Auto rotate speed as in degree per second. Positive is counter-clockwise and negative is clockwise. + * @param {number} [options.autoRotateActivationDuration=5000] - Duration before auto rotatation when no user interactivity in ms + */ + function Viewer ( options ) { + + let container; + + options = options || {}; + options.controlBar = options.controlBar !== undefined ? options.controlBar : true; + options.controlButtons = options.controlButtons || [ 'fullscreen', 'setting', 'video' ]; + options.autoHideControlBar = options.autoHideControlBar !== undefined ? options.autoHideControlBar : false; + options.autoHideInfospot = options.autoHideInfospot !== undefined ? options.autoHideInfospot : true; + options.horizontalView = options.horizontalView !== undefined ? options.horizontalView : false; + options.clickTolerance = options.clickTolerance || 10; + options.cameraFov = options.cameraFov || 60; + options.reverseDragging = options.reverseDragging || false; + options.enableReticle = options.enableReticle || false; + options.dwellTime = options.dwellTime || 1500; + options.autoReticleSelect = options.autoReticleSelect !== undefined ? options.autoReticleSelect : true; + options.viewIndicator = options.viewIndicator !== undefined ? options.viewIndicator : false; + options.indicatorSize = options.indicatorSize || 30; + options.output = options.output ? options.output : 'none'; + options.autoRotate = options.autoRotate || false; + options.autoRotateSpeed = options.autoRotateSpeed || 2.0; + options.autoRotateActivationDuration = options.autoRotateActivationDuration || 5000; + + this.options = options; + + /* + * CSS Icon + * const styleLoader = new StyleLoader(); + * styleLoader.inject( 'icono' ); + */ + + // Container + if ( options.container ) { + + container = options.container; + container._width = container.clientWidth; + container._height = container.clientHeight; + + } else { + + container = document.createElement( 'div' ); + container.classList.add( 'panolens-container' ); + container.style.width = '100%'; + container.style.height = '100%'; + container._width = window.innerWidth; + container._height = window.innerHeight; + document.body.appendChild( container ); + + } + + this.container = container; + + this.camera = options.camera || new THREE.PerspectiveCamera( this.options.cameraFov, this.container.clientWidth / this.container.clientHeight, 1, 10000 ); + this.scene = options.scene || new THREE.Scene(); + this.renderer = options.renderer || new THREE.WebGLRenderer( { alpha: true, antialias: false } ); + this.sceneReticle = new THREE.Scene(); + + this.viewIndicatorSize = this.options.indicatorSize; + + this.reticle = {}; + this.tempEnableReticle = this.options.enableReticle; + + this.mode = MODES.NORMAL; + + this.panorama = null; + this.widget = null; + + this.hoverObject = null; + this.infospot = null; + this.pressEntityObject = null; + this.pressObject = null; + + this.raycaster = new THREE.Raycaster(); + this.raycasterPoint = new THREE.Vector2(); + this.userMouse = new THREE.Vector2(); + this.updateCallbacks = []; + this.requestAnimationId = null; + + this.cameraFrustum = new THREE.Frustum(); + this.cameraViewProjectionMatrix = new THREE.Matrix4(); + + this.autoRotateRequestId = null; + + this.outputDivElement = null; + + this.touchSupported = 'ontouchstart' in window || window.DocumentTouch && document instanceof DocumentTouch; + + // Handler references + this.HANDLER_MOUSE_DOWN = this.onMouseDown.bind( this ); + this.HANDLER_MOUSE_UP = this.onMouseUp.bind( this ); + this.HANDLER_MOUSE_MOVE = this.onMouseMove.bind( this ); + this.HANDLER_WINDOW_RESIZE = this.onWindowResize.bind( this ); + this.HANDLER_KEY_DOWN = this.onKeyDown.bind( this ); + this.HANDLER_KEY_UP = this.onKeyUp.bind( this ); + this.HANDLER_TAP = this.onTap.bind( this, { + clientX: this.container.clientWidth / 2, + clientY: this.container.clientHeight / 2 + } ); + + // Flag for infospot output + this.OUTPUT_INFOSPOT = false; + + // Animations + this.tweenLeftAnimation = new Tween.Tween(); + this.tweenUpAnimation = new Tween.Tween(); + + // Renderer + this.renderer.setPixelRatio( window.devicePixelRatio ); + this.renderer.setSize( this.container.clientWidth, this.container.clientHeight ); + this.renderer.setClearColor( 0x000000, 0 ); + this.renderer.autoClear = false; + + // Append Renderer Element to container + this.renderer.domElement.classList.add( 'panolens-canvas' ); + this.renderer.domElement.style.display = 'block'; + this.container.style.backgroundColor = '#000'; + this.container.appendChild( this.renderer.domElement ); + + // Camera Controls + this.OrbitControls = new OrbitControls( this.camera, this.container ); + this.OrbitControls.id = 'orbit'; + this.OrbitControls.minDistance = 1; + this.OrbitControls.noPan = true; + this.OrbitControls.autoRotate = this.options.autoRotate; + this.OrbitControls.autoRotateSpeed = this.options.autoRotateSpeed; + + this.DeviceOrientationControls = new DeviceOrientationControls( this.camera, this.container ); + this.DeviceOrientationControls.id = 'device-orientation'; + this.DeviceOrientationControls.enabled = false; + this.camera.position.z = 1; + + // Register change event if passiveRenering + if ( this.options.passiveRendering ) { + + console.warn( 'passiveRendering is now deprecated' ); + + } + + // Controls + this.controls = [ this.OrbitControls, this.DeviceOrientationControls ]; + this.control = this.OrbitControls; + + // Cardboard effect + this.CardboardEffect = new CardboardEffect( this.renderer ); + this.CardboardEffect.setSize( this.container.clientWidth, this.container.clientHeight ); + + // Stereo effect + this.StereoEffect = new StereoEffect( this.renderer ); + this.StereoEffect.setSize( this.container.clientWidth, this.container.clientHeight ); + + this.effect = this.CardboardEffect; + + // Add default hidden reticle + this.addReticle(); + + // Lock horizontal view + if ( this.options.horizontalView ) { + this.OrbitControls.minPolarAngle = Math.PI / 2; + this.OrbitControls.maxPolarAngle = Math.PI / 2; + } + + // Add Control UI + if ( this.options.controlBar !== false ) { + this.addDefaultControlBar( this.options.controlButtons ); + } + + // Add View Indicator + if ( this.options.viewIndicator ) { + this.addViewIndicator(); + } + + // Reverse dragging direction + if ( this.options.reverseDragging ) { + this.reverseDraggingDirection(); + } + + // Register event if reticle is enabled, otherwise defaults to mouse + if ( this.options.enableReticle ) { + this.enableReticleControl(); + } else { + this.registerMouseAndTouchEvents(); + } + + // Output infospot position to an overlay container if specified + if ( this.options.output === 'overlay' ) { + this.addOutputElement(); + } + + // Register dom event listeners + this.registerEventListeners(); + + // Animate + this.animate.call( this ); + + } + Viewer.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype ), { + + constructor: Viewer, + + /** + * Add an object to the scene + * Automatically hookup with panolens-viewer-handler listener + * to communicate with viewer method + * @param {THREE.Object3D} object - The object to be added + * @memberOf Viewer + * @instance + */ + add: function ( object ) { + + if ( arguments.length > 1 ) { + + for ( let i = 0; i < arguments.length; i ++ ) { + + this.add( arguments[ i ] ); + + } + + return this; + + } + + this.scene.add( object ); + + // All object added to scene has 'panolens-viewer-handler' event to handle viewer communication + if ( object.addEventListener ) { + + object.addEventListener( 'panolens-viewer-handler', this.eventHandler.bind( this ) ); + + } + + // All object added to scene being passed with container + if ( object instanceof Panorama && object.dispatchEvent ) { + + object.dispatchEvent( { type: 'panolens-container', container: this.container } ); + + } + + if ( object instanceof CameraPanorama ) { + + object.dispatchEvent( { type: 'panolens-scene', scene: this.scene } ); + + } + + // Hookup default panorama event listeners + if ( object.type === 'panorama' ) { + + this.addPanoramaEventListener( object ); + + if ( !this.panorama ) { + + this.setPanorama( object ); + + } + + } + + }, + + /** + * Remove an object from the scene + * @param {THREE.Object3D} object - Object to be removed + * @memberOf Viewer + * @instance + */ + remove: function ( object ) { + + if ( object.removeEventListener ) { + + object.removeEventListener( 'panolens-viewer-handler', this.eventHandler.bind( this ) ); + + } + + this.scene.remove( object ); + + }, + + /** + * Add default control bar + * @param {array} array - The control buttons array + * @memberOf Viewer + * @instance + */ + addDefaultControlBar: function ( array ) { + + if ( this.widget ) { + + console.warn( 'Default control bar exists' ); + return; + + } + + const widget = new Widget( this.container ); + widget.addEventListener( 'panolens-viewer-handler', this.eventHandler.bind( this ) ); + widget.addControlBar(); + array.forEach( buttonName => { + + widget.addControlButton( buttonName ); + + } ); + + this.widget = widget; + + }, + + /** + * Set a panorama to be the current one + * @param {Panorama} pano - Panorama to be set + * @memberOf Viewer + * @instance + */ + setPanorama: function ( pano ) { + + const leavingPanorama = this.panorama; + + if ( pano.type === 'panorama' && leavingPanorama !== pano ) { + + // Clear exisiting infospot + this.hideInfospot(); + + const afterEnterComplete = function () { + + if ( leavingPanorama ) { leavingPanorama.onLeave(); } + pano.removeEventListener( 'enter-fade-start', afterEnterComplete ); + + }; + + pano.addEventListener( 'enter-fade-start', afterEnterComplete ); + + // Assign and enter panorama + (this.panorama = pano).onEnter(); + + } + + }, + + /** + * Event handler to execute commands from child objects + * @param {object} event - The dispatched event with method as function name and data as an argument + * @memberOf Viewer + * @instance + */ + eventHandler: function ( event ) { + + if ( event.method && this[ event.method ] ) { + + this[ event.method ]( event.data ); + + } + + }, + + /** + * Dispatch event to all descendants + * @param {object} event - Event to be passed along + * @memberOf Viewer + * @instance + */ + dispatchEventToChildren: function ( event ) { + + this.scene.traverse( function ( object ) { + + if ( object.dispatchEvent ) { + + object.dispatchEvent( event ); + + } + + }); + + }, + + /** + * Set widget content + * @method activateWidgetItem + * @param {integer} controlIndex - Control index + * @param {integer} mode - Modes for effects + * @memberOf Viewer + * @instance + */ + activateWidgetItem: function ( controlIndex, mode ) { + + const mainMenu = this.widget.mainMenu; + const ControlMenuItem = mainMenu.children[ 0 ]; + const ModeMenuItem = mainMenu.children[ 1 ]; + + let item; + + if ( controlIndex !== undefined ) { + + switch ( controlIndex ) { + + case 0: + + item = ControlMenuItem.subMenu.children[ 1 ]; + + break; + + case 1: + + item = ControlMenuItem.subMenu.children[ 2 ]; + + break; + + default: + + item = ControlMenuItem.subMenu.children[ 1 ]; + + break; + + } + + ControlMenuItem.subMenu.setActiveItem( item ); + ControlMenuItem.setSelectionTitle( item.textContent ); + + } + + if ( mode !== undefined ) { + + switch( mode ) { + + case MODES.CARDBOARD: + + item = ModeMenuItem.subMenu.children[ 2 ]; + + break; + + case MODES.STEREO: + + item = ModeMenuItem.subMenu.children[ 3 ]; + + break; + + default: + + item = ModeMenuItem.subMenu.children[ 1 ]; + + break; + } + + ModeMenuItem.subMenu.setActiveItem( item ); + ModeMenuItem.setSelectionTitle( item.textContent ); + + } + + }, + + /** + * Enable rendering effect + * @param {MODES} mode - Modes for effects + * @memberOf Viewer + * @instance + */ + enableEffect: function ( mode ) { + + if ( this.mode === mode ) { return; } + if ( mode === MODES.NORMAL ) { this.disableEffect(); return; } + else { this.mode = mode; } + + const fov = this.camera.fov; + + switch( mode ) { + + case MODES.CARDBOARD: + + this.effect = this.CardboardEffect; + this.enableReticleControl(); + + break; + + case MODES.STEREO: + + this.effect = this.StereoEffect; + this.enableReticleControl(); + + break; + + default: + + this.effect = null; + this.disableReticleControl(); + + break; + + } + + this.activateWidgetItem( undefined, this.mode ); + + /** + * Dual eye effect event + * @type {object} + * @event Infospot#panolens-dual-eye-effect + * @property {MODES} mode - Current display mode + */ + this.dispatchEventToChildren( { type: 'panolens-dual-eye-effect', mode: this.mode } ); + + // Force effect stereo camera to update by refreshing fov + this.camera.fov = fov + 10e-3; + this.effect.setSize( this.container.clientWidth, this.container.clientHeight ); + this.render(); + this.camera.fov = fov; + + /** + * Dispatch mode change event + * @type {object} + * @event Viewer#mode-change + * @property {MODES} mode - Current display mode + */ + this.dispatchEvent( { type: 'mode-change', mode: this.mode } ); + + }, + + /** + * Disable additional rendering effect + * @memberOf Viewer + * @instance + */ + disableEffect: function () { + + if ( this.mode === MODES.NORMAL ) { return; } + + this.mode = MODES.NORMAL; + this.disableReticleControl(); + + this.activateWidgetItem( undefined, this.mode ); + + /** + * Dual eye effect event + * @type {object} + * @event Infospot#panolens-dual-eye-effect + * @property {MODES} mode - Current display mode + */ + this.dispatchEventToChildren( { type: 'panolens-dual-eye-effect', mode: this.mode } ); + + this.renderer.setSize( this.container.clientWidth, this.container.clientHeight ); + this.render(); + + /** + * Dispatch mode change event + * @type {object} + * @event Viewer#mode-change + * @property {MODES} mode - Current display mode + */ + this.dispatchEvent( { type: 'mode-change', mode: this.mode } ); + }, + + /** + * Enable reticle control + * @memberOf Viewer + * @instance + */ + enableReticleControl: function () { + + if ( this.reticle.visible ) { return; } + + this.tempEnableReticle = true; + + // Register reticle event and unregister mouse event + this.unregisterMouseAndTouchEvents(); + this.reticle.show(); + this.registerReticleEvent(); + this.updateReticleEvent(); + + }, + + /** + * Disable reticle control + * @memberOf Viewer + * @instance + */ + disableReticleControl: function () { + + this.tempEnableReticle = false; + + // Register mouse event and unregister reticle event + if ( !this.options.enableReticle ) { + + this.reticle.hide(); + this.unregisterReticleEvent(); + this.registerMouseAndTouchEvents(); + + } else { + + this.updateReticleEvent(); + + } + + }, + + /** + * Enable auto rotation + * @memberOf Viewer + * @instance + */ + enableAutoRate: function () { + + this.options.autoRotate = true; + this.OrbitControls.autoRotate = true; + + }, + + /** + * Disable auto rotation + * @memberOf Viewer + * @instance + */ + disableAutoRate: function () { + + clearTimeout( this.autoRotateRequestId ); + this.options.autoRotate = false; + this.OrbitControls.autoRotate = false; + + }, + + /** + * Toggle video play or stop + * @param {boolean} pause + * @memberOf Viewer + * @instance + * @fires Viewer#video-toggle + */ + toggleVideoPlay: function ( pause ) { + + if ( this.panorama instanceof VideoPanorama ) { + + /** + * Toggle video event + * @type {object} + * @event Viewer#video-toggle + */ + this.panorama.dispatchEvent( { type: 'video-toggle', pause: pause } ); + + } + + }, + + /** + * Set currentTime in a video + * @param {number} percentage - Percentage of a video. Range from 0.0 to 1.0 + * @memberOf Viewer + * @instance + * @fires Viewer#video-time + */ + setVideoCurrentTime: function ( percentage ) { + + if ( this.panorama instanceof VideoPanorama ) { + + /** + * Setting video time event + * @type {object} + * @event Viewer#video-time + * @property {number} percentage - Percentage of a video. Range from 0.0 to 1.0 + */ + this.panorama.dispatchEvent( { type: 'video-time', percentage: percentage } ); + + } + + }, + + /** + * This will be called when video updates if an widget is present + * @param {number} percentage - Percentage of a video. Range from 0.0 to 1.0 + * @memberOf Viewer + * @instance + * @fires Viewer#video-update + */ + onVideoUpdate: function ( percentage ) { + + const { widget } = this; + + /** + * Video update event + * @type {object} + * @event Viewer#video-update + * @property {number} percentage - Percentage of a video. Range from 0.0 to 1.0 + */ + if( widget ) { widget.dispatchEvent( { type: 'video-update', percentage: percentage } ); } + + }, + + /** + * Add update callback to be called every animation frame + * @param {function} callback + * @memberOf Viewer + * @instance + */ + addUpdateCallback: function ( fn ) { + + if ( fn ) { + + this.updateCallbacks.push( fn ); + + } + + }, + + /** + * Remove update callback + * @param {function} fn - The function to be removed + * @memberOf Viewer + * @instance + */ + removeUpdateCallback: function ( fn ) { + + const index = this.updateCallbacks.indexOf( fn ); + + if ( fn && index >= 0 ) { + + this.updateCallbacks.splice( index, 1 ); + + } + + }, + + /** + * Show video widget + * @memberOf Viewer + * @instance + */ + showVideoWidget: function () { + + const { widget } = this; + + /** + * Show video widget event + * @type {object} + * @event Viewer#video-control-show + */ + if( widget ) { widget.dispatchEvent( { type: 'video-control-show' } ); } + + }, + + /** + * Hide video widget + * @memberOf Viewer + * @instance + */ + hideVideoWidget: function () { + + const { widget } = this; + + /** + * Hide video widget + * @type {object} + * @event Viewer#video-control-hide + */ + if( widget ) { widget.dispatchEvent( { type: 'video-control-hide' } ); } + + }, + + /** + * Update video play button + * @param {boolean} paused + * @memberOf Viewer + * @instance + */ + updateVideoPlayButton: function ( paused ) { + + const { widget } = this; + + if ( widget && widget.videoElement && widget.videoElement.controlButton ) { + + widget.videoElement.controlButton.update( paused ); + + } + + }, + + /** + * Add default panorama event listeners + * @param {Panorama} pano - The panorama to be added with event listener + * @memberOf Viewer + * @instance + */ + addPanoramaEventListener: function ( pano ) { + + // Set camera control on every panorama + pano.addEventListener( 'enter-fade-start', this.setCameraControl.bind( this ) ); + + // Show and hide widget event only when it's VideoPanorama + if ( pano instanceof VideoPanorama ) { + + pano.addEventListener( 'enter-fade-start', this.showVideoWidget.bind( this ) ); + pano.addEventListener( 'leave', function () { + + if ( !(this.panorama instanceof VideoPanorama) ) { + + this.hideVideoWidget.call( this ); + + } + + }.bind( this ) ); + + } + + }, + + /** + * Set camera control + * @memberOf Viewer + * @instance + */ + setCameraControl: function () { + + this.OrbitControls.target.copy( this.panorama.position ); + + }, + + /** + * Get current camera control + * @return {object} - Current navigation control + * @memberOf Viewer + * @instance + * @returns {THREE.OrbitControls|THREE.DeviceOrientationControls} + */ + getControl: function () { + + return this.control; + + }, + + /** + * Get scene + * @memberOf Viewer + * @instance + * @return {THREE.Scene} - Current scene which the viewer is built on + */ + getScene: function () { + + return this.scene; + + }, + + /** + * Get camera + * @memberOf Viewer + * @instance + * @return {THREE.Camera} - The scene camera + */ + getCamera: function () { + + return this.camera; + + }, + + /** + * Get renderer + * @memberOf Viewer + * @instance + * @return {THREE.WebGLRenderer} - The renderer using webgl + */ + getRenderer: function () { + + return this.renderer; + + }, + + /** + * Get container + * @memberOf Viewer + * @instance + * @return {HTMLElement} - The container holds rendererd canvas + */ + getContainer: function () { + + return this.container; + + }, + + /** + * Get control id + * @memberOf Viewer + * @instance + * @return {string} - Control id. 'orbit' or 'device-orientation' + */ + getControlId: function () { + + return this.control.id; + + }, + + /** + * Get next navigation control id + * @memberOf Viewer + * @instance + * @return {string} - Next control id + */ + getNextControlId: function () { + + return this.controls[ this.getNextControlIndex() ].id; + + }, + + /** + * Get next navigation control index + * @memberOf Viewer + * @instance + * @return {number} - Next control index + */ + getNextControlIndex: function () { + + const controls = this.controls; + const control = this.control; + const nextIndex = controls.indexOf( control ) + 1; + + return ( nextIndex >= controls.length ) ? 0 : nextIndex; + + }, + + /** + * Set field of view of camera + * @param {number} fov + * @memberOf Viewer + * @instance + */ + setCameraFov: function ( fov ) { + + this.camera.fov = fov; + this.camera.updateProjectionMatrix(); + + }, + + /** + * Enable control by index + * @param {CONTROLS} index - Index of camera control + * @memberOf Viewer + * @instance + */ + enableControl: function ( index ) { + + index = ( index >= 0 && index < this.controls.length ) ? index : 0; + + this.control.enabled = false; + + this.control = this.controls[ index ]; + + this.control.enabled = true; + + switch ( index ) { + + case CONTROLS.ORBIT: + + this.camera.position.copy( this.panorama.position ); + this.camera.position.z += 1; + + break; + + case CONTROLS.DEVICEORIENTATION: + + this.camera.position.copy( this.panorama.position ); + + break; + + default: + + break; + } + + this.control.update(); + + this.activateWidgetItem( index, undefined ); + + }, + + /** + * Disable current control + * @memberOf Viewer + * @instance + */ + disableControl: function () { + + this.control.enabled = false; + + }, + + /** + * Toggle next control + * @memberOf Viewer + * @instance + */ + toggleNextControl: function () { + + this.enableControl( this.getNextControlIndex() ); + + }, + + /** + * Screen Space Projection + * @memberOf Viewer + * @instance + */ + getScreenVector: function ( worldVector ) { + + const vector = worldVector.clone(); + const widthHalf = ( this.container.clientWidth ) / 2; + const heightHalf = this.container.clientHeight / 2; + + vector.project( this.camera ); + + vector.x = ( vector.x * widthHalf ) + widthHalf; + vector.y = - ( vector.y * heightHalf ) + heightHalf; + vector.z = 0; + + return vector; + + }, + + /** + * Check Sprite in Viewport + * @memberOf Viewer + * @instance + */ + checkSpriteInViewport: function ( sprite ) { + + this.camera.matrixWorldInverse.getInverse( this.camera.matrixWorld ); + this.cameraViewProjectionMatrix.multiplyMatrices( this.camera.projectionMatrix, this.camera.matrixWorldInverse ); + this.cameraFrustum.setFromMatrix( this.cameraViewProjectionMatrix ); + + return sprite.visible && this.cameraFrustum.intersectsSprite( sprite ); + + }, + + /** + * Reverse dragging direction + * @memberOf Viewer + * @instance + */ + reverseDraggingDirection: function () { + + this.OrbitControls.rotateSpeed *= -1; + this.OrbitControls.momentumScalingFactor *= -1; + + }, + + /** + * Add reticle + * @memberOf Viewer + * @instance + */ + addReticle: function () { + + this.reticle = new Reticle( 0xffffff, true, this.options.dwellTime ); + this.reticle.hide(); + this.camera.add( this.reticle ); + this.sceneReticle.add( this.camera ); + + }, + + /** + * Tween control looking center + * @param {THREE.Vector3} vector - Vector to be looked at the center + * @param {number} [duration=1000] - Duration to tween + * @param {function} [easing=TWEEN.Easing.Exponential.Out] - Easing function + * @memberOf Viewer + * @instance + */ + tweenControlCenter: function ( vector, duration, easing ) { + + if ( this.control !== this.OrbitControls ) { + + return; + + } + + // Pass in arguments as array + if ( vector instanceof Array ) { + + duration = vector[ 1 ]; + easing = vector[ 2 ]; + vector = vector[ 0 ]; + + } + + duration = duration !== undefined ? duration : 1000; + easing = easing || Tween.Easing.Exponential.Out; + + let scope, ha, va, chv, cvv, hv, vv, vptc, ov, nv; + + scope = this; + + chv = this.camera.getWorldDirection( new THREE.Vector3() ); + cvv = chv.clone(); + + vptc = this.panorama.getWorldPosition( new THREE.Vector3() ).sub( this.camera.getWorldPosition( new THREE.Vector3() ) ); + + hv = vector.clone(); + // Scale effect + hv.x *= -1; + hv.add( vptc ).normalize(); + vv = hv.clone(); + + chv.y = 0; + hv.y = 0; + + ha = Math.atan2( hv.z, hv.x ) - Math.atan2( chv.z, chv.x ); + ha = ha > Math.PI ? ha - 2 * Math.PI : ha; + ha = ha < -Math.PI ? ha + 2 * Math.PI : ha; + va = Math.abs( cvv.angleTo( chv ) + ( cvv.y * vv.y <= 0 ? vv.angleTo( hv ) : -vv.angleTo( hv ) ) ); + va *= vv.y < cvv.y ? 1 : -1; + + ov = { left: 0, up: 0 }; + nv = { left: 0, up: 0 }; + + this.tweenLeftAnimation.stop(); + this.tweenUpAnimation.stop(); + + this.tweenLeftAnimation = new Tween.Tween( ov ) + .to( { left: ha }, duration ) + .easing( easing ) + .onUpdate(function(ov){ + scope.control.rotateLeft( ov.left - nv.left ); + nv.left = ov.left; + }) + .start(); + + this.tweenUpAnimation = new Tween.Tween( ov ) + .to( { up: va }, duration ) + .easing( easing ) + .onUpdate(function(ov){ + scope.control.rotateUp( ov.up - nv.up ); + nv.up = ov.up; + }) + .start(); + + }, + + /** + * Tween control looking center by object + * @param {THREE.Object3D} object - Object to be looked at the center + * @param {number} [duration=1000] - Duration to tween + * @param {function} [easing=TWEEN.Easing.Exponential.Out] - Easing function + * @memberOf Viewer + * @instance + */ + tweenControlCenterByObject: function ( object, duration, easing ) { + + let isUnderScalePlaceHolder = false; + + object.traverseAncestors( function ( ancestor ) { + + if ( ancestor.scalePlaceHolder ) { + + isUnderScalePlaceHolder = true; + + } + } ); + + if ( isUnderScalePlaceHolder ) { + + const invertXVector = new THREE.Vector3( -1, 1, 1 ); + + this.tweenControlCenter( object.getWorldPosition( new THREE.Vector3() ).multiply( invertXVector ), duration, easing ); + + } else { + + this.tweenControlCenter( object.getWorldPosition( new THREE.Vector3() ), duration, easing ); + + } + + }, + + /** + * This is called when window size is changed + * @fires Viewer#window-resize + * @param {number} [windowWidth] - Specify if custom element has changed width + * @param {number} [windowHeight] - Specify if custom element has changed height + * @memberOf Viewer + * @instance + */ + onWindowResize: function ( windowWidth, windowHeight ) { + + let width, height; + + const expand = this.container.classList.contains( 'panolens-container' ) || this.container.isFullscreen; + + if ( windowWidth !== undefined && windowHeight !== undefined ) { + + width = windowWidth; + height = windowHeight; + this.container._width = windowWidth; + this.container._height = windowHeight; + + } else { + + const isAndroid = /(android)/i.test(window.navigator.userAgent); + + const adjustWidth = isAndroid + ? Math.min(document.documentElement.clientWidth, window.innerWidth || 0) + : Math.max(document.documentElement.clientWidth, window.innerWidth || 0); + + const adjustHeight = isAndroid + ? Math.min(document.documentElement.clientHeight, window.innerHeight || 0) + : Math.max(document.documentElement.clientHeight, window.innerHeight || 0); + + width = expand ? adjustWidth : this.container.clientWidth; + height = expand ? adjustHeight : this.container.clientHeight; + + this.container._width = width; + this.container._height = height; + + } + + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + + this.renderer.setSize( width, height ); + + // Update reticle + if ( this.options.enableReticle || this.tempEnableReticle ) { + + this.updateReticleEvent(); + + } + + /** + * Window resizing event + * @type {object} + * @event Viewer#window-resize + * @property {number} width - Width of the window + * @property {number} height - Height of the window + */ + this.dispatchEvent( { type: 'window-resize', width: width, height: height }); + this.scene.traverse( function ( object ) { + + if ( object.dispatchEvent ) { + + object.dispatchEvent( { type: 'window-resize', width: width, height: height }); + + } + + } ); + + }, + + /** + * Add output element + * @memberOf Viewer + * @instance + */ + addOutputElement: function () { + + const element = document.createElement( 'div' ); + element.style.position = 'absolute'; + element.style.right = '10px'; + element.style.top = '10px'; + element.style.color = '#fff'; + this.container.appendChild( element ); + this.outputDivElement = element; + + }, + + /** + * Output position in developer console by holding down Ctrl button + * @memberOf Viewer + * @instance + */ + outputPosition: function () { + + const intersects = this.raycaster.intersectObject( this.panorama, true ); + + if ( intersects.length > 0 ) { + + const point = intersects[ 0 ].point.clone(); + const converter = new THREE.Vector3( -1, 1, 1 ); + const world = this.panorama.getWorldPosition( new THREE.Vector3() ); + point.sub( world ).multiply( converter ); + + const position = { + x: point.x.toFixed(2), + y: point.y.toFixed(2), + z: point.z.toFixed(2), + }; + + const message = `${position.x}, ${position.y}, ${position.z}`; + + if ( point.length() === 0 ) { return; } + + switch ( this.options.output ) { + + case 'event': + /** + * Dispatch raycast position as event + * @type {object} + * @event Viewer#position-output + */ + this.dispatchEvent( { type: 'position-output', position: position } ); + break; + + case 'console': + console.info( message ); + break; + + case 'overlay': + this.outputDivElement.textContent = message; + break; + + default: + break; + + } + + } + + }, + + /** + * On mouse down + * @param {MouseEvent} event + * @memberOf Viewer + * @instance + */ + onMouseDown: function ( event ) { + + event.preventDefault(); + + this.userMouse.x = ( event.clientX >= 0 ) ? event.clientX : event.touches[0].clientX; + this.userMouse.y = ( event.clientY >= 0 ) ? event.clientY : event.touches[0].clientY; + this.userMouse.type = 'mousedown'; + this.onTap( event ); + + }, + + /** + * On mouse move + * @param {MouseEvent} event + * @memberOf Viewer + * @instance + */ + onMouseMove: function ( event ) { + + event.preventDefault(); + this.userMouse.type = 'mousemove'; + this.onTap( event ); + + }, + + /** + * On mouse up + * @param {MouseEvent} event + * @memberOf Viewer + * @instance + */ + onMouseUp: function ( event ) { + + let onTarget = false; + + this.userMouse.type = 'mouseup'; + + const type = ( this.userMouse.x >= event.clientX - this.options.clickTolerance + && this.userMouse.x <= event.clientX + this.options.clickTolerance + && this.userMouse.y >= event.clientY - this.options.clickTolerance + && this.userMouse.y <= event.clientY + this.options.clickTolerance ) + || ( event.changedTouches + && this.userMouse.x >= event.changedTouches[0].clientX - this.options.clickTolerance + && this.userMouse.x <= event.changedTouches[0].clientX + this.options.clickTolerance + && this.userMouse.y >= event.changedTouches[0].clientY - this.options.clickTolerance + && this.userMouse.y <= event.changedTouches[0].clientY + this.options.clickTolerance ) + ? 'click' : undefined; + + // Event should happen on canvas + if ( event && event.target && !event.target.classList.contains( 'panolens-canvas' ) ) { return; } + + event.preventDefault(); + + if ( event.changedTouches && event.changedTouches.length === 1 ) { + + onTarget = this.onTap( { clientX: event.changedTouches[0].clientX, clientY: event.changedTouches[0].clientY }, type ); + + } else { + + onTarget = this.onTap( event, type ); + + } + + this.userMouse.type = 'none'; + + if ( onTarget ) { + + return; + + } + + if ( type === 'click' ) { + + const { options: { autoHideInfospot, autoHideControlBar }, panorama, toggleControlBar } = this; + + if ( autoHideInfospot && panorama ) { + + panorama.toggleInfospotVisibility(); + + } + + if ( autoHideControlBar ) { + + toggleControlBar(); + + } + + } + + }, + + /** + * On tap eveny frame + * @param {MouseEvent} event + * @param {string} type + * @memberOf Viewer + * @instance + */ + onTap: function ( event, type ) { + + const { left, top } = this.container.getBoundingClientRect(); + const { clientWidth, clientHeight } = this.container; + + this.raycasterPoint.x = ( ( event.clientX - left ) / clientWidth ) * 2 - 1; + this.raycasterPoint.y = - ( ( event.clientY - top ) / clientHeight ) * 2 + 1; + + this.raycaster.setFromCamera( this.raycasterPoint, this.camera ); + + // Return if no panorama + if ( !this.panorama ) { + + return; + + } + + // output infospot information + if ( event.type !== 'mousedown' && this.touchSupported || this.OUTPUT_INFOSPOT ) { + + this.outputPosition(); + + } + + + const intersects = this.raycaster.intersectObjects( this.panorama.children, true ); + const intersect_entity = this.getConvertedIntersect( intersects ); + const intersect = ( intersects.length > 0 ) ? intersects[0].object : undefined; + + if ( this.userMouse.type === 'mouseup' ) { + + if ( intersect_entity && this.pressEntityObject === intersect_entity && this.pressEntityObject.dispatchEvent ) { + + this.pressEntityObject.dispatchEvent( { type: 'pressstop-entity', mouseEvent: event } ); + + } + + this.pressEntityObject = undefined; + + } + + if ( this.userMouse.type === 'mouseup' ) { + + if ( intersect && this.pressObject === intersect && this.pressObject.dispatchEvent ) { + + this.pressObject.dispatchEvent( { type: 'pressstop', mouseEvent: event } ); + + } + + this.pressObject = undefined; + + } + + if ( type === 'click' ) { + + this.panorama.dispatchEvent( { type: 'click', intersects: intersects, mouseEvent: event } ); + + if ( intersect_entity && intersect_entity.dispatchEvent ) { + + intersect_entity.dispatchEvent( { type: 'click-entity', mouseEvent: event } ); + + } + + if ( intersect && intersect.dispatchEvent ) { + + intersect.dispatchEvent( { type: 'click', mouseEvent: event } ); + + } + + } else { + + this.panorama.dispatchEvent( { type: 'hover', intersects: intersects, mouseEvent: event } ); + + if ( ( this.hoverObject && intersects.length > 0 && this.hoverObject !== intersect_entity ) + || ( this.hoverObject && intersects.length === 0 ) ){ + + if ( this.hoverObject.dispatchEvent ) { + + this.hoverObject.dispatchEvent( { type: 'hoverleave', mouseEvent: event } ); + + this.reticle.end(); + + } + + this.hoverObject = undefined; + + } + + if ( intersect_entity && intersects.length > 0 ) { + + if ( this.hoverObject !== intersect_entity ) { + + this.hoverObject = intersect_entity; + + if ( this.hoverObject.dispatchEvent ) { + + this.hoverObject.dispatchEvent( { type: 'hoverenter', mouseEvent: event } ); + + // Start reticle timer + if ( this.options.autoReticleSelect && this.options.enableReticle || this.tempEnableReticle ) { + this.reticle.start( this.onTap.bind( this, event, 'click' ) ); + } + + } + + } + + if ( this.userMouse.type === 'mousedown' && this.pressEntityObject != intersect_entity ) { + + this.pressEntityObject = intersect_entity; + + if ( this.pressEntityObject.dispatchEvent ) { + + this.pressEntityObject.dispatchEvent( { type: 'pressstart-entity', mouseEvent: event } ); + + } + + } + + if ( this.userMouse.type === 'mousedown' && this.pressObject != intersect ) { + + this.pressObject = intersect; + + if ( this.pressObject.dispatchEvent ) { + + this.pressObject.dispatchEvent( { type: 'pressstart', mouseEvent: event } ); + + } + + } + + if ( this.userMouse.type === 'mousemove' || this.options.enableReticle ) { + + if ( intersect && intersect.dispatchEvent ) { + + intersect.dispatchEvent( { type: 'hover', mouseEvent: event } ); + + } + + if ( this.pressEntityObject && this.pressEntityObject.dispatchEvent ) { + + this.pressEntityObject.dispatchEvent( { type: 'pressmove-entity', mouseEvent: event } ); + + } + + if ( this.pressObject && this.pressObject.dispatchEvent ) { + + this.pressObject.dispatchEvent( { type: 'pressmove', mouseEvent: event } ); + + } + + } + + } + + if ( !intersect_entity && this.pressEntityObject && this.pressEntityObject.dispatchEvent ) { + + this.pressEntityObject.dispatchEvent( { type: 'pressstop-entity', mouseEvent: event } ); + + this.pressEntityObject = undefined; + + } + + if ( !intersect && this.pressObject && this.pressObject.dispatchEvent ) { + + this.pressObject.dispatchEvent( { type: 'pressstop', mouseEvent: event } ); + + this.pressObject = undefined; + + } + + } + + // Infospot handler + if ( intersect && intersect instanceof Infospot ) { + + this.infospot = intersect; + + if ( type === 'click' ) { + + return true; + + } + + + } else if ( this.infospot ) { + + this.hideInfospot(); + + } + + // Auto rotate + if ( this.options.autoRotate && this.userMouse.type !== 'mousemove' ) { + + // Auto-rotate idle timer + clearTimeout( this.autoRotateRequestId ); + + if ( this.control === this.OrbitControls ) { + + this.OrbitControls.autoRotate = false; + this.autoRotateRequestId = window.setTimeout( this.enableAutoRate.bind( this ), this.options.autoRotateActivationDuration ); + + } + + } + + }, + + /** + * Get converted intersect + * @param {array} intersects + * @memberOf Viewer + * @instance + */ + getConvertedIntersect: function ( intersects ) { + + let intersect; + + for ( let i = 0; i < intersects.length; i++ ) { + + if ( intersects[i].distance >= 0 && intersects[i].object && !intersects[i].object.passThrough ) { + + if ( intersects[i].object.entity && intersects[i].object.entity.passThrough ) { + continue; + } else if ( intersects[i].object.entity && !intersects[i].object.entity.passThrough ) { + intersect = intersects[i].object.entity; + break; + } else { + intersect = intersects[i].object; + break; + } + + } + + } + + return intersect; + + }, + + /** + * Hide infospot + * @memberOf Viewer + * @instance + */ + hideInfospot: function () { + + if ( this.infospot ) { + + this.infospot.onHoverEnd(); + + this.infospot = undefined; + + } + + }, + + /** + * Toggle control bar + * @memberOf Viewer + * @instance + * @fires Viewer#control-bar-toggle + */ + toggleControlBar: function () { + + const { widget } = this; + + /** + * Toggle control bar event + * @type {object} + * @event Viewer#control-bar-toggle + */ + if ( widget ) { + + widget.dispatchEvent( { type: 'control-bar-toggle' } ); + + } + + }, + + /** + * On key down + * @param {KeyboardEvent} event + * @memberOf Viewer + * @instance + */ + onKeyDown: function ( event ) { + + if ( this.options.output && this.options.output !== 'none' && event.key === 'Control' ) { + + this.OUTPUT_INFOSPOT = true; + + } + + }, + + /** + * On key up + * @param {KeyboardEvent} event + * @memberOf Viewer + * @instance + */ + onKeyUp: function () { + + this.OUTPUT_INFOSPOT = false; + + }, + + /** + * Update control and callbacks + * @memberOf Viewer + * @instance + */ + update: function () { + + Tween.update(); + + this.updateCallbacks.forEach( function( callback ){ callback(); } ); + + this.control.update(); + + this.scene.traverse( function( child ){ + if ( child instanceof Infospot + && child.element + && ( this.hoverObject === child + || child.element.style.display !== 'none' + || (child.element.left && child.element.left.style.display !== 'none') + || (child.element.right && child.element.right.style.display !== 'none') ) ) { + if ( this.checkSpriteInViewport( child ) ) { + const { x, y } = this.getScreenVector( child.getWorldPosition( new THREE.Vector3() ) ); + child.translateElement( x, y ); + } else { + child.onDismiss(); + } + + } + }.bind( this ) ); + + }, + + /** + * Rendering function to be called on every animation frame + * Render reticle last + * @memberOf Viewer + * @instance + */ + render: function () { + + if ( this.mode === MODES.CARDBOARD || this.mode === MODES.STEREO ) { + + this.renderer.clear(); + this.effect.render( this.scene, this.camera ); + this.effect.render( this.sceneReticle, this.camera ); + + + } else { + + this.renderer.clear(); + this.renderer.render( this.scene, this.camera ); + this.renderer.clearDepth(); + this.renderer.render( this.sceneReticle, this.camera ); + + } + + }, + + /** + * Animate + * @memberOf Viewer + * @instance + */ + animate: function () { + + this.requestAnimationId = window.requestAnimationFrame( this.animate.bind( this ) ); + + this.onChange(); + + }, + + /** + * On change + * @memberOf Viewer + * @instance + */ + onChange: function () { + + this.update(); + this.render(); + + }, + + /** + * Register mouse and touch event on container + * @memberOf Viewer + * @instance + */ + registerMouseAndTouchEvents: function () { + + const options = { passive: false }; + + this.container.addEventListener( 'mousedown' , this.HANDLER_MOUSE_DOWN, options ); + this.container.addEventListener( 'mousemove' , this.HANDLER_MOUSE_MOVE, options ); + this.container.addEventListener( 'mouseup' , this.HANDLER_MOUSE_UP , options ); + this.container.addEventListener( 'touchstart', this.HANDLER_MOUSE_DOWN, options ); + this.container.addEventListener( 'touchend' , this.HANDLER_MOUSE_UP , options ); + + }, + + /** + * Unregister mouse and touch event on container + * @memberOf Viewer + * @instance + */ + unregisterMouseAndTouchEvents: function () { + + this.container.removeEventListener( 'mousedown' , this.HANDLER_MOUSE_DOWN, false ); + this.container.removeEventListener( 'mousemove' , this.HANDLER_MOUSE_MOVE, false ); + this.container.removeEventListener( 'mouseup' , this.HANDLER_MOUSE_UP , false ); + this.container.removeEventListener( 'touchstart', this.HANDLER_MOUSE_DOWN, false ); + this.container.removeEventListener( 'touchend' , this.HANDLER_MOUSE_UP , false ); + + }, + + /** + * Register reticle event + * @memberOf Viewer + * @instance + */ + registerReticleEvent: function () { + + this.addUpdateCallback( this.HANDLER_TAP ); + + }, + + /** + * Unregister reticle event + * @memberOf Viewer + * @instance + */ + unregisterReticleEvent: function () { + + this.removeUpdateCallback( this.HANDLER_TAP ); + + }, + + /** + * Update reticle event + * @memberOf Viewer + * @instance + */ + updateReticleEvent: function () { + + const clientX = this.container.clientWidth / 2 + this.container.offsetLeft; + const clientY = this.container.clientHeight / 2; + + this.removeUpdateCallback( this.HANDLER_TAP ); + this.HANDLER_TAP = this.onTap.bind( this, { clientX, clientY } ); + this.addUpdateCallback( this.HANDLER_TAP ); + + }, + + /** + * Register container and window listeners + * @memberOf Viewer + * @instance + */ + registerEventListeners: function () { + + // Resize Event + window.addEventListener( 'resize' , this.HANDLER_WINDOW_RESIZE, true ); + + // Keyboard Event + window.addEventListener( 'keydown', this.HANDLER_KEY_DOWN, true ); + window.addEventListener( 'keyup' , this.HANDLER_KEY_UP , true ); + + }, + + /** + * Unregister container and window listeners + * @memberOf Viewer + * @instance + */ + unregisterEventListeners: function () { + + // Resize Event + window.removeEventListener( 'resize' , this.HANDLER_WINDOW_RESIZE, true ); + + // Keyboard Event + window.removeEventListener( 'keydown', this.HANDLER_KEY_DOWN, true ); + window.removeEventListener( 'keyup' , this.HANDLER_KEY_UP , true ); + + }, + + /** + * Dispose all scene objects and clear cache + * @memberOf Viewer + * @instance + */ + dispose: function () { + + this.tweenLeftAnimation.stop(); + this.tweenUpAnimation.stop(); + + // Unregister dom event listeners + this.unregisterEventListeners(); + + // recursive disposal on 3d objects + function recursiveDispose ( object ) { + + for ( let i = object.children.length - 1; i >= 0; i-- ) { + + recursiveDispose( object.children[i] ); + object.remove( object.children[i] ); + + } + + if ( object instanceof Panorama || object instanceof Infospot ) { + + object.dispose(); + object = null; + + } else if ( object.dispatchEvent ){ + + object.dispatchEvent( 'dispose' ); + + } + + } + + recursiveDispose( this.scene ); + + // dispose widget + if ( this.widget ) { + + this.widget.dispose(); + this.widget = null; + + } + + // clear cache + if ( THREE.Cache && THREE.Cache.enabled ) { + + THREE.Cache.clear(); + + } + + }, + + /** + * Destroy viewer by disposing and stopping requestAnimationFrame + * @memberOf Viewer + * @instance + */ + destroy: function () { + + this.dispose(); + this.render(); + window.cancelAnimationFrame( this.requestAnimationId ); + + }, + + /** + * On panorama dispose + * @memberOf Viewer + * @instance + */ + onPanoramaDispose: function ( panorama ) { + + if ( panorama instanceof VideoPanorama ) { + + this.hideVideoWidget(); + + } + + if ( panorama === this.panorama ) { + + this.panorama = null; + + } + + }, + + /** + * Load ajax call + * @param {string} url - URL to be requested + * @param {function} [callback] - Callback after request completes + * @memberOf Viewer + * @instance + */ + loadAsyncRequest: function ( url, callback = () => {} ) { + + const request = new window.XMLHttpRequest(); + request.onloadend = function ( event ) { + callback( event ); + }; + request.open( 'GET', url, true ); + request.send( null ); + + }, + + /** + * View indicator in upper left + * @memberOf Viewer + * @instance + */ + addViewIndicator: function () { + + const scope = this; + + function loadViewIndicator ( asyncEvent ) { + + if ( asyncEvent.loaded === 0 ) return; + + const viewIndicatorDiv = asyncEvent.target.responseXML.documentElement; + viewIndicatorDiv.style.width = scope.viewIndicatorSize + 'px'; + viewIndicatorDiv.style.height = scope.viewIndicatorSize + 'px'; + viewIndicatorDiv.style.position = 'absolute'; + viewIndicatorDiv.style.top = '10px'; + viewIndicatorDiv.style.left = '10px'; + viewIndicatorDiv.style.opacity = '0.5'; + viewIndicatorDiv.style.cursor = 'pointer'; + viewIndicatorDiv.id = 'panolens-view-indicator-container'; + + scope.container.appendChild( viewIndicatorDiv ); + + const indicator = viewIndicatorDiv.querySelector( '#indicator' ); + const setIndicatorD = function () { + + scope.radius = scope.viewIndicatorSize * 0.225; + scope.currentPanoAngle = scope.camera.rotation.y - THREE.Math.degToRad( 90 ); + scope.fovAngle = THREE.Math.degToRad( scope.camera.fov ) ; + scope.leftAngle = -scope.currentPanoAngle - scope.fovAngle / 2; + scope.rightAngle = -scope.currentPanoAngle + scope.fovAngle / 2; + scope.leftX = scope.radius * Math.cos( scope.leftAngle ); + scope.leftY = scope.radius * Math.sin( scope.leftAngle ); + scope.rightX = scope.radius * Math.cos( scope.rightAngle ); + scope.rightY = scope.radius * Math.sin( scope.rightAngle ); + scope.indicatorD = 'M ' + scope.leftX + ' ' + scope.leftY + ' A ' + scope.radius + ' ' + scope.radius + ' 0 0 1 ' + scope.rightX + ' ' + scope.rightY; + + if ( scope.leftX && scope.leftY && scope.rightX && scope.rightY && scope.radius ) { + + indicator.setAttribute( 'd', scope.indicatorD ); + + } + + }; + + scope.addUpdateCallback( setIndicatorD ); + + const indicatorOnMouseEnter = function () { + + this.style.opacity = '1'; + + }; + + const indicatorOnMouseLeave = function () { + + this.style.opacity = '0.5'; + + }; + + viewIndicatorDiv.addEventListener( 'mouseenter', indicatorOnMouseEnter ); + viewIndicatorDiv.addEventListener( 'mouseleave', indicatorOnMouseLeave ); + } + + this.loadAsyncRequest( DataImage.ViewIndicator, loadViewIndicator ); + + }, + + /** + * Append custom control item to existing control bar + * @param {object} [option={}] - Style object to overwirte default element style. It takes 'style', 'onTap' and 'group' properties. + * @memberOf Viewer + * @instance + */ + appendControlItem: function ( option ) { + + const item = this.widget.createCustomItem( option ); + + if ( option.group === 'video' ) { + + this.widget.videoElement.appendChild( item ); + + } else { + + this.widget.barElement.appendChild( item ); + + } + + return item; + + }, + + /** + * Clear all cached files + * @memberOf Viewer + * @instance + */ + clearAllCache: function () { + + THREE.Cache.clear(); + + } + + } ); + + if ( THREE.REVISION != THREE_REVISION ) { + + console.warn( `three.js version is not matched. Please consider use the target revision ${THREE_REVISION}` ); + + } + + /** + * Panolens.js + * @author pchen66 + * @namespace PANOLENS + */ + window.TWEEN = Tween; + + exports.BasicPanorama = BasicPanorama; + exports.CONTROLS = CONTROLS; + exports.CameraPanorama = CameraPanorama; + exports.CubePanorama = CubePanorama; + exports.CubeTextureLoader = CubeTextureLoader; + exports.DataImage = DataImage; + exports.EmptyPanorama = EmptyPanorama; + exports.GoogleStreetviewPanorama = GoogleStreetviewPanorama; + exports.ImageLittlePlanet = ImageLittlePlanet; + exports.ImageLoader = ImageLoader; + exports.ImagePanorama = ImagePanorama; + exports.Infospot = Infospot; + exports.LittlePlanet = LittlePlanet; + exports.MODES = MODES; + exports.Media = Media; + exports.Panorama = Panorama; + exports.REVISION = REVISION; + exports.Reticle = Reticle; + exports.THREE_REVISION = THREE_REVISION; + exports.THREE_VERSION = THREE_VERSION; + exports.TextureLoader = TextureLoader; + exports.VERSION = VERSION; + exports.VideoPanorama = VideoPanorama; + exports.Viewer = Viewer; + exports.Widget = Widget; + + Object.defineProperty(exports, '__esModule', { value: true }); + +})); diff --git a/build/panolens.min.js b/build/panolens.min.js new file mode 100644 index 00000000..078ef538 --- /dev/null +++ b/build/panolens.min.js @@ -0,0 +1,202 @@ +(function(h,e){"object"===typeof exports&&"undefined"!==typeof module?e(exports,require("three")):"function"===typeof define&&define.amd?define(["exports","three"],e):(h=h||self,e(h.PANOLENS={},h.THREE))})(this,function(h,e){function S(a){this.constraints=Object.assign({video:{width:{ideal:1920},height:{ideal:1080},facingMode:{exact:"environment"}},audio:!1},a);this.element=this.scene=this.container=null;this.devices=[];this.stream=null;this.ratioScalar=1;this.videoDeviceIndex=0}function M(a,b,c){a= +void 0===a?16777215:a;b=void 0===b?!0:b;c=void 0===c?1500:c;this.dpr=window.devicePixelRatio;var d=this.createCanvas(),g=d.canvas;d=d.context;var k=new e.SpriteMaterial({color:a,map:this.createCanvasTexture(g)});e.Sprite.call(this,k);this.canvasWidth=g.width;this.canvasHeight=g.height;this.context=d;this.color=a instanceof e.Color?a:new e.Color(a);this.autoSelect=b;this.dwellTime=c;this.rippleDuration=500;this.position.z=-10;this.center.set(.5,.5);this.scale.set(.5,.5,1);this.callback=this.timerId= +this.startTimestamp=null;this.frustumCulled=!1;this.updateCanvasArcByProgress(0)}function z(a,b,c){a=void 0===a?300:a;b=b||u.Info;e.Sprite.call(this);this.type="infospot";this.animated=void 0!==c?c:!0;this.frustumCulled=this.isHovering=!1;this.cursorStyle=this.toPanorama=this.element=null;this.mode=t.NORMAL;this.scale.set(a,a,1);this.rotation.y=Math.PI;this.container=null;this.originalRaycast=this.raycast;this.HANDLER_FOCUS=null;this.material.side=e.DoubleSide;this.material.depthTest=!1;this.material.transparent= +!0;this.material.opacity=0;this.scaleUpAnimation=new r.Tween;this.scaleDownAnimation=new r.Tween;c=function(d){if(this.material){var b=d.image.width/d.image.height,c=new e.Vector3;d.image.width=d.image.naturalWidth||64;d.image.height=d.image.naturalHeight||64;this.scale.set(b*a,a,1);c.copy(this.scale);this.scaleUpAnimation=(new r.Tween(this.scale)).to({x:1.3*c.x,y:1.3*c.y},500).easing(r.Easing.Elastic.Out);this.scaleDownAnimation=(new r.Tween(this.scale)).to({x:c.x,y:c.y},500).easing(r.Easing.Elastic.Out); +this.material.map=d;this.material.needsUpdate=!0}}.bind(this);this.showAnimation=(new r.Tween(this.material)).to({opacity:1},500).onStart(this.enableRaycast.bind(this,!0)).easing(r.Easing.Quartic.Out);this.hideAnimation=(new r.Tween(this.material)).to({opacity:0},500).onStart(this.enableRaycast.bind(this,!1)).easing(r.Easing.Quartic.Out);this.addEventListener("click",this.onClick);this.addEventListener("hover",this.onHover);this.addEventListener("hoverenter",this.onHoverStart);this.addEventListener("hoverleave", +this.onHoverEnd);this.addEventListener("panolens-dual-eye-effect",this.onDualEyeEffect);this.addEventListener("panolens-container",this.setContainer.bind(this));this.addEventListener("dismiss",this.onDismiss);this.addEventListener("panolens-infospot-focus",this.setFocusMethod);N.load(b,c)}function I(a){a||console.warn("PANOLENS.Widget: No container specified");e.EventDispatcher.call(this);this.DEFAULT_TRANSITION="all 0.27s ease";this.TOUCH_ENABLED=!!("ontouchstart"in window||window.DocumentTouch&& +document instanceof DocumentTouch);this.PREVENT_EVENT_HANDLER=function(a){a.preventDefault();a.stopPropagation()};this.container=a;this.mask=this.activeSubMenu=this.activeMainItem=this.mainMenu=this.settingElement=this.videoElement=this.fullscreenElement=this.barElement=null}function n(a,b){e.Mesh.call(this,a,b);this.type="panorama";this.ImageQualityLow=1;this.ImageQualityFair=2;this.ImageQualityMedium=3;this.ImageQualityHigh=4;this.ImageQualitySuperHigh=5;this.animationDuration=1E3;this.defaultInfospotSize= +350;this.container=void 0;this.loaded=!1;this.linkedSpots=[];this.isInfospotVisible=!1;this.linkingImageScale=this.linkingImageURL=void 0;this.material.side=e.BackSide;this.material.opacity=0;this.scale.x*=-1;this.renderOrder=-1;this.active=!1;this.infospotAnimation=(new r.Tween(this)).to({},this.animationDuration/2);this.addEventListener("load",this.fadeIn.bind(this));this.addEventListener("panolens-container",this.setContainer.bind(this));this.addEventListener("click",this.onClick.bind(this));this.setupTransitions()} +function y(a,b,c){b=b||new e.SphereBufferGeometry(5E3,60,40);c=c||new e.MeshBasicMaterial({opacity:0,transparent:!0});n.call(this,b,c);this.src=a;this.radius=5E3}function W(){var a=new e.BufferGeometry,b=new e.MeshBasicMaterial({color:0,opacity:0,transparent:!0});a.addAttribute("position",new e.BufferAttribute(new Float32Array,1));n.call(this,a,b)}function F(a){a=void 0===a?[]:a;var b=Object.assign({},e.ShaderLib.cube),c=new e.BoxBufferGeometry(1E4,1E4,1E4);b=new e.ShaderMaterial({fragmentShader:b.fragmentShader, +vertexShader:b.vertexShader,uniforms:b.uniforms,side:e.BackSide,transparent:!0});n.call(this,c,b);this.images=a;this.edgeLength=1E4;this.material.uniforms.opacity.value=0}function O(){for(var a=[],b=0;6>b;b++)a.push(u.WhiteTile);F.call(this,a)}function B(a,b){b=void 0===b?{}:b;var c=new e.SphereBufferGeometry(5E3,60,40),d=new e.MeshBasicMaterial({opacity:0,transparent:!0});n.call(this,c,d);this.src=a;this.options={videoElement:document.createElement("video"),loop:!0,muted:!0,autoplay:!1,playsinline:!0, +crossOrigin:"anonymous"};Object.assign(this.options,b);this.videoElement=this.options.videoElement;this.videoProgress=0;this.radius=5E3;this.addEventListener("leave",this.pauseVideo.bind(this));this.addEventListener("enter-fade-start",this.resumeVideoProgress.bind(this));this.addEventListener("video-toggle",this.toggleVideo.bind(this));this.addEventListener("video-time",this.setVideoCurrentTime.bind(this))}function P(a){this._parameters=a=void 0===a?{}:a;this._panoId=this._zoom=null;this._panoClient= +new google.maps.StreetViewService;this._total=this._count=0;this._canvas=[];this._ctx=[];this._hc=this._wc=0;this.result=null;this.rotation=0;this.copyright="";this.onPanoramaLoad=this.onSizeChange=null;this.levelsW=[1,2,4,7,13,26];this.levelsH=[1,1,2,4,7,13];this.widths=[416,832,1664,3328,6656,13312];this.heights=[416,416,832,1664,3328,6656];this.maxH=this.maxW=6656;var b;try{var c=document.createElement("canvas");(b=c.getContext("experimental-webgl"))||(b=c.getContext("webgl"))}catch(d){}this.maxW= +Math.max(b.getParameter(b.MAX_TEXTURE_SIZE),this.maxW);this.maxH=Math.max(b.getParameter(b.MAX_TEXTURE_SIZE),this.maxH)}function Z(a,b){y.call(this);this.panoId=a;this.gsvLoader=null;this.loadRequested=!1;this.setupGoogleMapAPI(b)}function C(a,b,c,d){c=void 0===c?1E4:c;d=void 0===d?.5:d;"image"===(void 0===a?"image":a)&&y.call(this,b,this.createGeometry(c,d),this.createMaterial(c));this.size=c;this.ratio=d;this.EPS=1E-6;this.frameId=null;this.dragging=!1;this.userMouse=new e.Vector2;this.quatA=new e.Quaternion; +this.quatB=new e.Quaternion;this.quatCur=new e.Quaternion;this.quatSlerp=new e.Quaternion;this.vectorX=new e.Vector3(1,0,0);this.vectorY=new e.Vector3(0,1,0);this.addEventListener("window-resize",this.onWindowResize)}function aa(a,b,c){C.call(this,"image",a,b,c)}function J(a){var b=new e.SphereBufferGeometry(5E3,60,40),c=new e.MeshBasicMaterial({visible:!1});n.call(this,b,c);this.media=new S(a);this.radius=5E3;this.addEventListener("enter",this.start.bind(this));this.addEventListener("leave",this.stop.bind(this)); +this.addEventListener("panolens-container",this.onPanolensContainer.bind(this));this.addEventListener("panolens-scene",this.onPanolensScene.bind(this))}function ba(a,b){function c(a){Q=!1;K=L=0;if(!1!==f.enabled){a.preventDefault();if(a.button===f.mouseButtons.ORBIT){if(!0===f.noRotate)return;x=w.ROTATE;D.set(a.clientX,a.clientY)}else if(a.button===f.mouseButtons.ZOOM){if(!0===f.noZoom)return;x=w.DOLLY;y.set(a.clientX,a.clientY)}else if(a.button===f.mouseButtons.PAN){if(!0===f.noPan)return;x=w.PAN; +n.set(a.clientX,a.clientY)}x!==w.NONE&&(document.addEventListener("mousemove",d,!1),document.addEventListener("mouseup",g,!1),f.dispatchEvent(O));f.update()}}function d(a){if(!1!==f.enabled){a.preventDefault();var d=f.domElement===document?f.domElement.body:f.domElement;if(x===w.ROTATE){if(!0===f.noRotate)return;h.set(a.clientX,a.clientY);r.subVectors(h,D);f.rotateLeft(2*Math.PI*r.x/d.clientWidth*f.rotateSpeed);f.rotateUp(2*Math.PI*r.y/d.clientHeight*f.rotateSpeed);D.copy(h);G&&(K=a.clientX-G.clientX, +L=a.clientY-G.clientY);G=a}else if(x===w.DOLLY){if(!0===f.noZoom)return;z.set(a.clientX,a.clientY);T.subVectors(z,y);0T.y&&f.dollyOut();y.copy(z)}else if(x===w.PAN){if(!0===f.noPan)return;U.set(a.clientX,a.clientY);t.subVectors(U,n);f.pan(t.x,t.y);n.copy(U)}x!==w.NONE&&f.update()}}function g(){Q=!0;G=void 0;!1!==f.enabled&&(document.removeEventListener("mousemove",d,!1),document.removeEventListener("mouseup",g,!1),f.dispatchEvent(P),x=w.NONE)}function k(a){if(!1!==f.enabled&&!0!== +f.noZoom&&x===w.NONE){a.preventDefault();a.stopPropagation();var d=0;void 0!==a.wheelDelta?d=a.wheelDelta:void 0!==a.detail&&(d=-a.detail);0d&&(f.object.fov=f.object.fov>f.minFov?f.object.fov-1:f.minFov,f.object.updateProjectionMatrix());f.update();f.dispatchEvent(V);f.dispatchEvent(O);f.dispatchEvent(P)}}function p(a){switch(a.keyCode){case f.keys.UP:I=!1;break;case f.keys.BOTTOM:J=!1;break;case f.keys.LEFT:X= +!1;break;case f.keys.RIGHT:Y=!1}}function m(a){if(!1!==f.enabled&&!0!==f.noKeys&&!0!==f.noRotate){switch(a.keyCode){case f.keys.UP:I=!0;break;case f.keys.BOTTOM:J=!0;break;case f.keys.LEFT:X=!0;break;case f.keys.RIGHT:Y=!0}if(I||J||X||Y)Q=!0,I&&(L=-f.rotateSpeed*f.momentumKeydownFactor),J&&(L=f.rotateSpeed*f.momentumKeydownFactor),X&&(K=-f.rotateSpeed*f.momentumKeydownFactor),Y&&(K=f.rotateSpeed*f.momentumKeydownFactor)}}function l(a){Q=!1;K=L=0;if(!1!==f.enabled){switch(a.touches.length){case 1:if(!0=== +f.noRotate)return;x=w.TOUCH_ROTATE;D.set(a.touches[0].pageX,a.touches[0].pageY);break;case 2:if(!0===f.noZoom)return;x=w.TOUCH_DOLLY;var d=a.touches[0].pageX-a.touches[1].pageX;a=a.touches[0].pageY-a.touches[1].pageY;y.set(0,Math.sqrt(d*d+a*a));break;case 3:if(!0===f.noPan)return;x=w.TOUCH_PAN;n.set(a.touches[0].pageX,a.touches[0].pageY);break;default:x=w.NONE}x!==w.NONE&&f.dispatchEvent(O)}}function v(a){if(!1!==f.enabled){a.preventDefault();a.stopPropagation();var d=f.domElement===document?f.domElement.body: +f.domElement;switch(a.touches.length){case 1:if(!0===f.noRotate)break;if(x!==w.TOUCH_ROTATE)break;h.set(a.touches[0].pageX,a.touches[0].pageY);r.subVectors(h,D);f.rotateLeft(2*Math.PI*r.x/d.clientWidth*f.rotateSpeed);f.rotateUp(2*Math.PI*r.y/d.clientHeight*f.rotateSpeed);D.copy(h);G&&(K=a.touches[0].pageX-G.pageX,L=a.touches[0].pageY-G.pageY);G={pageX:a.touches[0].pageX,pageY:a.touches[0].pageY};f.update();break;case 2:if(!0===f.noZoom)break;if(x!==w.TOUCH_DOLLY)break;d=a.touches[0].pageX-a.touches[1].pageX; +a=a.touches[0].pageY-a.touches[1].pageY;z.set(0,Math.sqrt(d*d+a*a));T.subVectors(z,y);0>T.y?(f.object.fov=f.object.fovf.minFov?f.object.fov-1:f.minFov,f.object.updateProjectionMatrix());y.copy(z);f.update();f.dispatchEvent(V);break;case 3:if(!0===f.noPan)break;if(x!==w.TOUCH_PAN)break;U.set(a.touches[0].pageX,a.touches[0].pageY);t.subVectors(U,n);f.pan(t.x,t.y);n.copy(U);f.update();break;default:x= +w.NONE}}}function q(){Q=!0;G=void 0;!1!==f.enabled&&(f.dispatchEvent(P),x=w.NONE)}this.object=a;this.domElement=void 0!==b?b:document;this.frameId=null;this.enabled=!0;this.center=this.target=new e.Vector3;this.noZoom=!1;this.zoomSpeed=1;this.minDistance=0;this.maxDistance=Infinity;this.minZoom=0;this.maxZoom=Infinity;this.noRotate=!1;this.rotateSpeed=-.15;this.noPan=!0;this.keyPanSpeed=7;this.autoRotate=!1;this.autoRotateSpeed=2;this.minPolarAngle=0;this.maxPolarAngle=Math.PI;this.momentumDampingFactor= +.9;this.momentumScalingFactor=-.005;this.momentumKeydownFactor=20;this.minFov=30;this.maxFov=120;this.minAzimuthAngle=-Infinity;this.maxAzimuthAngle=Infinity;this.noKeys=!1;this.keys={LEFT:37,UP:38,RIGHT:39,BOTTOM:40};this.mouseButtons={ORBIT:e.MOUSE.LEFT,ZOOM:e.MOUSE.MIDDLE,PAN:e.MOUSE.RIGHT};var f=this,D=new e.Vector2,h=new e.Vector2,r=new e.Vector2,n=new e.Vector2,U=new e.Vector2,t=new e.Vector2,u=new e.Vector3,A=new e.Vector3,y=new e.Vector2,z=new e.Vector2,T=new e.Vector2,R=0,H=0,B=0,C=0,E=1, +F=new e.Vector3,M=new e.Vector3,N=new e.Quaternion,K=0,L=0,G,Q=!1,I,J,X,Y,w={NONE:-1,ROTATE:0,DOLLY:1,PAN:2,TOUCH_ROTATE:3,TOUCH_DOLLY:4,TOUCH_PAN:5},x=w.NONE;this.target0=this.target.clone();this.position0=this.object.position.clone();this.zoom0=this.object.zoom;var S=(new e.Quaternion).setFromUnitVectors(a.up,new e.Vector3(0,1,0)),W=S.clone().inverse(),V={type:"change"},O={type:"start"},P={type:"end"};this.setLastQuaternion=function(a){N.copy(a);f.object.quaternion.copy(a)};this.getLastPosition= +function(){return M};this.rotateLeft=function(a){void 0===a&&(a=2*Math.PI/60/60*f.autoRotateSpeed);C-=a};this.rotateUp=function(a){void 0===a&&(a=2*Math.PI/60/60*f.autoRotateSpeed);B-=a};this.panLeft=function(a){var d=this.object.matrix.elements;u.set(d[0],d[1],d[2]);u.multiplyScalar(-a);F.add(u)};this.panUp=function(a){var d=this.object.matrix.elements;u.set(d[4],d[5],d[6]);u.multiplyScalar(a);F.add(u)};this.pan=function(a,d){var b=f.domElement===document?f.domElement.body:f.domElement;if(f.object instanceof +e.PerspectiveCamera){var c=f.object.position.clone().sub(f.target).length();c*=Math.tan(f.object.fov/2*Math.PI/180);f.panLeft(2*a*c/b.clientHeight);f.panUp(2*d*c/b.clientHeight)}else f.object instanceof e.OrthographicCamera?(f.panLeft(a*(f.object.right-f.object.left)/b.clientWidth),f.panUp(d*(f.object.top-f.object.bottom)/b.clientHeight)):console.warn("WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.")};this.momentum=function(){Q&&(1E-4>Math.abs(K)&&1E-4>Math.abs(L)?Q= +!1:(L*=this.momentumDampingFactor,K*=this.momentumDampingFactor,C-=this.momentumScalingFactor*K,B-=this.momentumScalingFactor*L))};this.dollyIn=function(a){void 0===a&&(a=Math.pow(.95,f.zoomSpeed));f.object instanceof e.PerspectiveCamera?E/=a:f.object instanceof e.OrthographicCamera?(f.object.zoom=Math.max(this.minZoom,Math.min(this.maxZoom,this.object.zoom*a)),f.object.updateProjectionMatrix(),f.dispatchEvent(V)):console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.")}; +this.dollyOut=function(a){void 0===a&&(a=Math.pow(.95,f.zoomSpeed));f.object instanceof e.PerspectiveCamera?E*=a:f.object instanceof e.OrthographicCamera?(f.object.zoom=Math.max(this.minZoom,Math.min(this.maxZoom,this.object.zoom/a)),f.object.updateProjectionMatrix(),f.dispatchEvent(V)):console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.")};this.update=function(a){var d=this.object.position;A.copy(d).sub(this.target);A.applyQuaternion(S);R=Math.atan2(A.x, +A.z);H=Math.atan2(Math.sqrt(A.x*A.x+A.z*A.z),A.y);this.autoRotate&&x===w.NONE&&this.rotateLeft(2*Math.PI/60/60*f.autoRotateSpeed);this.momentum();R+=C;H+=B;R=Math.max(this.minAzimuthAngle,Math.min(this.maxAzimuthAngle,R));H=Math.max(this.minPolarAngle,Math.min(this.maxPolarAngle,H));H=Math.max(1E-7,Math.min(Math.PI-1E-7,H));var b=A.length()*E;b=Math.max(this.minDistance,Math.min(this.maxDistance,b));this.target.add(F);A.x=b*Math.sin(H)*Math.sin(R);A.y=b*Math.cos(H);A.z=b*Math.sin(H)*Math.cos(R);A.applyQuaternion(W); +d.copy(this.target).add(A);this.object.lookAt(this.target);B=C=0;E=1;F.set(0,0,0);if(1E-7g&&(l.total=l.total/g*6);c(l)},d)});return k}};S.prototype=Object.assign(Object.create(e.EventDispatcher.prototype),{setContainer:function(a){this.container=a},setScene:function(a){this.scene=a},enumerateDevices:function(){var a=this.devices,b=new Promise(function(b){b(a)});return 0=g.length?(c(0),d--):c(d);b(g[d])})},getDevices:function(a){a=void 0===a?"video":a;var b=this.devices;return this.enumerateDevices().then(function(a){return a.map(function(a){b.includes(a)||b.push(a);return a})}).then(function(b){var d=new RegExp(a,"i");return b.filter(function(a){return d.test(a.kind)})})},getUserMedia:function(a){var b=this.setMediaStream.bind(this),c=this.playVideo.bind(this); +return window.navigator.mediaDevices.getUserMedia(a).then(b).then(c).catch(function(a){console.warn("PANOLENS.Media: "+a)})},setVideDeviceIndex:function(a){this.videoDeviceIndex=a},start:function(a){var b=this.constraints,c=this.getUserMedia.bind(this);this.element=this.createVideoElement();return this.getDevices().then(function(d){if(!d||0===d.length)throw Error("no video device found");b.video.deviceId=(a||d[0]).deviceId;return c(b)})},stop:function(){var a=this.stream;a&&a.active&&(a.getTracks()[0].stop(), +window.removeEventListener("resize",this.onWindowResize.bind(this)),this.stream=this.element=null)},setMediaStream:function(a){this.stream=a;this.element.srcObject=a;this.scene&&(this.scene.background=this.createVideoTexture());window.addEventListener("resize",this.onWindowResize.bind(this))},playVideo:function(){var a=this.element;a&&(a.play(),this.dispatchEvent({type:"play"}))},pauseVideo:function(){var a=this.element;a&&(a.pause(),this.dispatchEvent({type:"pause"}))},createVideoTexture:function(){var a= +this.element,b=new e.VideoTexture(a);b.generateMipmaps=!1;b.minFilter=e.LinearFilter;b.magFilter=e.LinearFilter;b.format=e.RGBFormat;b.center.set(.5,.5);a.addEventListener("canplay",this.onWindowResize.bind(this));return b},createVideoElement:function(){var a=this.dispatchEvent.bind(this),b=document.createElement("video");b.setAttribute("autoplay","");b.setAttribute("muted","");b.setAttribute("playsinline","");b.style.position="absolute";b.style.top="0";b.style.left="0";b.style.width="100%";b.style.height= +"100%";b.style.objectPosition="center";b.style.objectFit="cover";b.style.display=this.scene?"none":"";b.addEventListener("canplay",function(){return a({type:"canplay"})});return b},onWindowResize:function(){if(this.element&&this.element.videoWidth&&this.element.videoHeight&&this.scene){var a=this.container,b=a.clientWidth;a=a.clientHeight;var c=this.scene.background,d=this.element;d=d.videoHeight/d.videoWidth*(this.container?b/a:1)*this.ratioScalar;b>a?c.repeat.set(d,1):c.repeat.set(1,1/d)}}});M.prototype= +Object.assign(Object.create(e.Sprite.prototype),{constructor:M,setColor:function(a){this.material.color.copy(a instanceof e.Color?a:new e.Color(a))},createCanvasTexture:function(a){a=new e.CanvasTexture(a);a.minFilter=e.LinearFilter;a.magFilter=e.LinearFilter;a.generateMipmaps=!1;return a},createCanvas:function(){var a=document.createElement("canvas"),b=a.getContext("2d"),c=this.dpr;a.width=32*c;a.height=32*c;b.scale(c,c);b.shadowBlur=5;b.shadowColor="rgba(200,200,200,0.9)";return{canvas:a,context:b}}, +updateCanvasArcByProgress:function(a){var b=this.context,c=this.canvasWidth,d=this.canvasHeight,g=this.material,k=this.dpr,e=a*Math.PI*2,m=this.color.getStyle(),l=.5*c/k;k=.5*d/k;b.clearRect(0,0,c,d);b.beginPath();0===a?(b.arc(l,k,c/16,0,2*Math.PI),b.fillStyle=m,b.fill()):(b.arc(l,k,c/4-3,-Math.PI/2,-Math.PI/2+e),b.strokeStyle=m,b.lineWidth=3,b.stroke());b.closePath();g.map.needsUpdate=!0},ripple:function(){var a=this,b=this.context,c=this.canvasWidth,d=this.canvasHeight,g=this.material,k=this.rippleDuration, +e=performance.now(),m=this.color,l=this.dpr,h=.5*c/l,q=.5*d/l,f=function(){var p=window.requestAnimationFrame(f),v=(performance.now()-e)/k,n=0<1-v?1-v:0,r=v*c*.5/l;b.clearRect(0,0,c,d);b.beginPath();b.arc(h,q,r,0,2*Math.PI);b.fillStyle="rgba("+255*m.r+", "+255*m.g+", "+255*m.b+", "+n+")";b.fill();b.closePath();1<=v&&(window.cancelAnimationFrame(p),a.updateCanvasArcByProgress(0),a.dispatchEvent({type:"reticle-ripple-end"}));g.map.needsUpdate=!0};this.dispatchEvent({type:"reticle-ripple-start"});f()}, +show:function(){this.visible=!0},hide:function(){this.visible=!1},start:function(a){this.autoSelect&&(this.dispatchEvent({type:"reticle-start"}),this.startTimestamp=performance.now(),this.callback=a,this.update())},end:function(){this.startTimestamp&&(window.cancelAnimationFrame(this.timerId),this.updateCanvasArcByProgress(0),this.startTimestamp=this.timerId=this.callback=null,this.dispatchEvent({type:"reticle-end"}))},update:function(){this.timerId=window.requestAnimationFrame(this.update.bind(this)); +var a=(performance.now()-this.startTimestamp)/this.dwellTime;this.updateCanvasArcByProgress(a);this.dispatchEvent({type:"reticle-update",progress:a});1<=a&&(window.cancelAnimationFrame(this.timerId),this.callback&&this.callback(),this.end(),this.ripple())}});var r=function(a,b){return b={exports:{}},a(b,b.exports),b.exports}(function(a,b){b=function(){this._tweens={};this._tweensAddedDuringUpdate={}};b.prototype={getAll:function(){return Object.keys(this._tweens).map(function(a){return this._tweens[a]}.bind(this))}, +removeAll:function(){this._tweens={}},add:function(a){this._tweens[a.getId()]=a;this._tweensAddedDuringUpdate[a.getId()]=a},remove:function(a){delete this._tweens[a.getId()];delete this._tweensAddedDuringUpdate[a.getId()]},update:function(a,b){var d=Object.keys(this._tweens);if(0===d.length)return!1;for(a=void 0!==a?a:c.now();0(a*=2)?.5*a*a:-.5*(--a*(a-2)-1)}},Cubic:{In:function(a){return a*a*a},Out:function(a){return--a*a*a+1},InOut:function(a){return 1> +(a*=2)?.5*a*a*a:.5*((a-=2)*a*a+2)}},Quartic:{In:function(a){return a*a*a*a},Out:function(a){return 1- --a*a*a*a},InOut:function(a){return 1>(a*=2)?.5*a*a*a*a:-.5*((a-=2)*a*a*a-2)}},Quintic:{In:function(a){return a*a*a*a*a},Out:function(a){return--a*a*a*a*a+1},InOut:function(a){return 1>(a*=2)?.5*a*a*a*a*a:.5*((a-=2)*a*a*a*a+2)}},Sinusoidal:{In:function(a){return 1-Math.cos(a*Math.PI/2)},Out:function(a){return Math.sin(a*Math.PI/2)},InOut:function(a){return.5*(1-Math.cos(Math.PI*a))}},Exponential:{In:function(a){return 0=== +a?0:Math.pow(1024,a-1)},Out:function(a){return 1===a?1:1-Math.pow(2,-10*a)},InOut:function(a){return 0===a?0:1===a?1:1>(a*=2)?.5*Math.pow(1024,a-1):.5*(-Math.pow(2,-10*(a-1))+2)}},Circular:{In:function(a){return 1-Math.sqrt(1-a*a)},Out:function(a){return Math.sqrt(1- --a*a)},InOut:function(a){return 1>(a*=2)?-.5*(Math.sqrt(1-a*a)-1):.5*(Math.sqrt(1-(a-=2)*a)+1)}},Elastic:{In:function(a){return 0===a?0:1===a?1:-Math.pow(2,10*(a-1))*Math.sin(5*(a-1.1)*Math.PI)},Out:function(a){return 0===a?0:1===a? +1:Math.pow(2,-10*a)*Math.sin(5*(a-.1)*Math.PI)+1},InOut:function(a){if(0===a)return 0;if(1===a)return 1;a*=2;return 1>a?-.5*Math.pow(2,10*(a-1))*Math.sin(5*(a-1.1)*Math.PI):.5*Math.pow(2,-10*(a-1))*Math.sin(5*(a-1.1)*Math.PI)+1}},Back:{In:function(a){return a*a*(2.70158*a-1.70158)},Out:function(a){return--a*a*(2.70158*a+1.70158)+1},InOut:function(a){return 1>(a*=2)?.5*a*a*(3.5949095*a-2.5949095):.5*((a-=2)*a*(3.5949095*a+2.5949095)+2)}},Bounce:{In:function(a){return 1-c.Easing.Bounce.Out(1-a)},Out:function(a){return a< +1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+.9375:7.5625*(a-=2.625/2.75)*a+.984375},InOut:function(a){return.5>a?.5*c.Easing.Bounce.In(2*a):.5*c.Easing.Bounce.Out(2*a-1)+.5}}};c.Interpolation={Linear:function(a,b){var d=a.length-1,g=d*b,e=Math.floor(g),l=c.Interpolation.Utils.Linear;return 0>b?l(a[0],a[1],g):1d?d:e+1],g-e)},Bezier:function(a,b){for(var d=0,g=a.length-1,e=Math.pow,l=c.Interpolation.Utils.Bernstein,h=0;h<= +g;h++)d+=e(1-b,g-h)*e(b,h)*a[h]*l(g,h);return d},CatmullRom:function(a,b){var d=a.length-1,g=d*b,e=Math.floor(g),l=c.Interpolation.Utils.CatmullRom;return a[0]===a[d]?(0>b&&(e=Math.floor(g=d*(1+b))),l(a[(e-1+d)%d],a[e],a[(e+1)%d],a[(e+2)%d],g-e)):0>b?a[0]-(l(a[0],a[0],a[1],a[1],-g)-a[0]):1l?0:l,f.setProgress(l),g.dispatchEvent({type:"panolens-viewer-handler",method:"setVideoCurrentTime",data:l}))}function c(a){a.stopPropagation();e=!1;d()}function d(){g.container.removeEventListener("mousemove",b,!1);g.container.removeEventListener("mouseup",c,!1);g.container.removeEventListener("touchmove",b,!1);g.container.removeEventListener("touchend",c,!1)}var g=this,e=!1,p,m,l;var h=document.createElement("div");h.style.width="0%";h.style.height="100%";h.style.backgroundColor="#fff";var q= +document.createElement("div");q.style.float="right";q.style.width="14px";q.style.height="14px";q.style.transform="translate(7px, -5px)";q.style.borderRadius="50%";q.style.backgroundColor="#ddd";q.addEventListener("mousedown",a,{passive:!0});q.addEventListener("touchstart",a,{passive:!0});h.appendChild(q);var f=this.createCustomItem({style:{float:"left",width:"30%",height:"4px",marginTop:"20px",backgroundColor:"rgba(188,188,188,0.8)"},onTap:function(a){a.preventDefault();a.stopPropagation();if(a.target!== +q){var b=a.changedTouches&&0=window.innerWidth?this.ImageQualityFair:800=window.innerWidth?this.ImageQualityMedium:1280=window.innerWidth?this.ImageQualityHigh:1920=d&&(b.zoom.value=d)},onUpdateCallback:function(){this.frameId=window.requestAnimationFrame(this.onUpdateCallback.bind(this));this.quatSlerp.slerp(this.quatCur,.1);this.material&& +this.material.uniforms.transform.value.makeRotationFromQuaternion(this.quatSlerp);!this.dragging&&1-this.quatSlerp.clone().dot(this.quatCur)=a.length?0:b},setCameraFov:function(a){this.camera.fov=a;this.camera.updateProjectionMatrix()}, +enableControl:function(a){a=0<=a&&aMath.PI?m-2*Math.PI:m;m=m<-Math.PI?m+2*Math.PI:m;k=Math.abs(h.angleTo(k)+(0>=h.y*l.y?l.angleTo(a):-l.angleTo(a)));k*=l.y=a.clientX-this.options.clickTolerance&&this.userMouse.x<=a.clientX+ +this.options.clickTolerance&&this.userMouse.y>=a.clientY-this.options.clickTolerance&&this.userMouse.y<=a.clientY+this.options.clickTolerance||a.changedTouches&&this.userMouse.x>=a.changedTouches[0].clientX-this.options.clickTolerance&&this.userMouse.x<=a.changedTouches[0].clientX+this.options.clickTolerance&&this.userMouse.y>=a.changedTouches[0].clientY-this.options.clickTolerance&&this.userMouse.y<=a.changedTouches[0].clientY+this.options.clickTolerance?"click":void 0;if(!a||!a.target||a.target.classList.contains("panolens-canvas"))if(a.preventDefault(), +a=a.changedTouches&&1===a.changedTouches.length?this.onTap({clientX:a.changedTouches[0].clientX,clientY:a.changedTouches[0].clientY},b):this.onTap(a,b),this.userMouse.type="none",!a&&"click"===b){b=this.options;a=b.autoHideControlBar;var c=this.panorama,d=this.toggleControlBar;b.autoHideInfospot&&c&&c.toggleInfospotVisibility();a&&d()}},onTap:function(a,b){var c=this.container.getBoundingClientRect(),d=c.top,e=this.container,h=e.clientHeight;this.raycasterPoint.x=(a.clientX-c.left)/e.clientWidth* +2-1;this.raycasterPoint.y=2*-((a.clientY-d)/h)+1;this.raycaster.setFromCamera(this.raycasterPoint,this.camera);if(this.panorama){("mousedown"!==a.type&&this.touchSupported||this.OUTPUT_INFOSPOT)&&this.outputPosition();c=this.raycaster.intersectObjects(this.panorama.children,!0);d=this.getConvertedIntersect(c);e=0 {}, onProgress = () => {}, onError = () => {} ) { + + // Enable cache + Cache.enabled = true; + + let cached, request, arrayBufferView, blob, urlCreator, image, reference; + + // Reference key + for (let iconName in DataImage) { + + if (DataImage.hasOwnProperty(iconName) && url === DataImage[iconName]) { + + reference = iconName; + + } + + } + + // Cached + cached = Cache.get(reference ? reference : url); + + if (cached !== undefined) { + + if (onLoad) { + + setTimeout(function () { + + onProgress({loaded: 1, total: 1}); + onLoad(cached); + + }, 0); + + } + + return cached; + + } + + // Construct a new XMLHttpRequest + urlCreator = window.URL || window.webkitURL; + image = document.createElementNS('http://www.w3.org/1999/xhtml', 'img'); + + // Add to cache + Cache.add(reference ? reference : url, image); + + const onImageLoaded = () => { + + urlCreator.revokeObjectURL(image.src); + onLoad(image); + + }; + + if (url.indexOf('data:') === 0) { + + image.addEventListener('load', onImageLoaded, false); + image.src = url; + return image; + } + + image.crossOrigin = this.crossOrigin !== undefined ? this.crossOrigin : ''; + + request = new window.XMLHttpRequest(); + request.open('GET', url, true); + if (process.env.npm_lifecycle_event !== 'test') { + request.onreadystatechange = function () { + if (this.readyState === 4 && this.status >= 400) { + onError(); + } + }; + } + request.responseType = 'arraybuffer'; + request.addEventListener( 'error', onError ); + request.addEventListener( 'progress', event => { + + if ( !event ) return; + + const { loaded, total, lengthComputable } = event; + + if ( lengthComputable ) { + + onProgress( { loaded, total } ); + + } + + } ); + + request.addEventListener( 'loadend', event => { + + if ( !event ) return; + const { currentTarget: { response } } = event; + + arrayBufferView = new Uint8Array( response ); + blob = new window.Blob( [ arrayBufferView ] ); + + image.addEventListener( 'load', onImageLoaded, false ); + image.src = urlCreator.createObjectURL( blob ); + + } ); + + request.send(null); + + } + +}; + +/** + * @module TextureLoader + * @description Texture loader based on {@link https://github.com/mrdoob/three.js/blob/master/src/loaders/TextureLoader.js} + */ +const TextureLoader = { + + /** + * Load image texture + * @example PANOLENS.TextureLoader.load( IMAGE_URL ) + * @method load + * @param {string} url - An image url + * @param {function} onLoad - On load callback + * @param {function} onProgress - In progress callback + * @param {function} onError - On error callback + * @return {THREE.Texture} - Image texture + */ + load: function ( url, onLoad = () => {}, onProgress, onError ) { + + var texture = new Texture(); + + ImageLoader.load( url, function ( image ) { + + texture.image = image; + + // JPEGs can't have an alpha channel, so memory can be saved by storing them as RGB. + const isJPEG = url.search( /\.(jpg|jpeg)$/ ) > 0 || url.search( /^data\:image\/jpeg/ ) === 0; + + texture.format = isJPEG ? RGBFormat : RGBAFormat; + texture.needsUpdate = true; + + onLoad( texture ); + + }, onProgress, onError ); + + return texture; + + } + +}; + +/** + * @module CubeTextureLoader + * @description Cube Texture Loader based on {@link https://github.com/mrdoob/three.js/blob/master/src/loaders/CubeTextureLoader.js} + */ +const CubeTextureLoader = { + + /** + * Load 6 images as a cube texture + * @example PANOLENS.CubeTextureLoader.load( [ 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' ] ) + * @method load + * @param {array} urls - array of 6 urls to images, one for each side of the CubeTexture. The urls should be specified in the following order: pos-x, neg-x, pos-y, neg-y, pos-z, neg-z + * @param {function} onLoad - On load callback + * @param {function} onProgress - In progress callback + * @param {function} onError - On error callback + * @return {THREE.CubeTexture} - Cube texture + */ + load: function ( urls, onLoad = () => {}, onProgress = () => {}, onError ) { + + var texture, loaded, progress, all, loadings; + + texture = new CubeTexture( [] ); + + loaded = 0; + progress = {}; + all = {}; + + urls.map( function ( url, index ) { + + ImageLoader.load( url, function ( image ) { + + texture.images[ index ] = image; + + loaded++; + + if ( loaded === 6 ) { + + texture.needsUpdate = true; + + onLoad( texture ); + + } + + }, function ( event ) { + + progress[ index ] = { loaded: event.loaded, total: event.total }; + + all.loaded = 0; + all.total = 0; + loadings = 0; + + for ( var i in progress ) { + + loadings++; + all.loaded += progress[ i ].loaded; + all.total += progress[ i ].total; + + } + + if ( loadings < 6 ) { + + all.total = all.total / loadings * 6; + + } + + onProgress( all ); + + }, onError ); + + } ); + + return texture; + + } + +}; + +/** + * @classdesc User Media + * @constructor + * @param {object} [constraints={ video: { width: { ideal: 1920 }, height: { ideal: 1080 }, facingMode: { exact: 'environment' } }, audio: false }] + */ +function Media ( constraints ) { + + const defaultConstraints = { video: { width: { ideal: 1920 }, height: { ideal: 1080 }, facingMode: { exact: 'environment' } }, audio: false }; + + this.constraints = Object.assign( defaultConstraints, constraints ); + + this.container = null; + this.scene = null; + this.element = null; + this.devices = []; + this.stream = null; + this.ratioScalar = 1; + this.videoDeviceIndex = 0; + +} +Media.prototype = Object.assign( Object.create( EventDispatcher.prototype ), { + + setContainer: function ( container ) { + + this.container = container; + + }, + + setScene: function ( scene ) { + + this.scene = scene; + + }, + + /** + * Enumerate devices + * @memberOf Media + * @instance + * @returns {Promise} + */ + enumerateDevices: function () { + + const devices = this.devices; + const resolvedPromise = new Promise( resolve => { resolve( devices ); } ); + + return devices.length > 0 ? resolvedPromise : window.navigator.mediaDevices.enumerateDevices(); + + }, + + /** + * Switch to next available video device + * @memberOf Media + * @instance + */ + switchNextVideoDevice: function () { + + const stop = this.stop.bind( this ); + const start = this.start.bind( this ); + const setVideDeviceIndex = this.setVideDeviceIndex.bind( this ); + + let index = this.videoDeviceIndex; + + this.getDevices( 'video' ) + .then( devices => { + stop(); + index++; + if ( index >= devices.length ) { + setVideDeviceIndex( 0 ); + index--; + } else { + setVideDeviceIndex( index ); + } + + start( devices[ index ] ); + + + } ); + + }, + + /** + * Get devices + * @param {string} type - type keyword to match device.kind + * @memberOf Media + * @instance + */ + getDevices: function ( type = 'video' ) { + + const devices = this.devices; + const validate = _devices => { + + return _devices.map( device => { + + if ( !devices.includes( device ) ) { devices.push( device ); } + return device; + + } ); + + }; + const filter = _devices => { + + const reg = new RegExp( type, 'i' ); + return _devices.filter( device => reg.test( device.kind ) ); + + }; + + return this.enumerateDevices() + .then( validate ) + .then( filter ); + + }, + + /** + * Get user media + * @param {MediaStreamConstraints} constraints + * @memberOf Media + * @instance + */ + getUserMedia: function ( constraints ) { + + const setMediaStream = this.setMediaStream.bind( this ); + const playVideo = this.playVideo.bind( this ); + const onCatchError = error => { console.warn( `PANOLENS.Media: ${error}` ); }; + + return window.navigator.mediaDevices.getUserMedia( constraints ) + .then( setMediaStream ) + .then( playVideo ) + .catch( onCatchError ); + + }, + + /** + * Set video device index + * @param {number} index + * @memberOf Media + * @instance + */ + setVideDeviceIndex: function ( index ) { + + this.videoDeviceIndex = index; + + }, + + /** + * Start streaming + * @param {MediaDeviceInfo} [targetDevice] + * @memberOf Media + * @instance + */ + start: function( targetDevice ) { + + const constraints = this.constraints; + const getUserMedia = this.getUserMedia.bind( this ); + const onVideoDevices = devices => { + + if ( !devices || devices.length === 0 ) { + + throw Error( 'no video device found' ); + + } + + const device = targetDevice || devices[ 0 ]; + constraints.video.deviceId = device.deviceId; + + return getUserMedia( constraints ); + + }; + + this.element = this.createVideoElement(); + + return this.getDevices().then( onVideoDevices ); + + }, + + /** + * Stop streaming + * @memberOf Media + * @instance + */ + stop: function () { + + const stream = this.stream; + + if ( stream && stream.active ) { + + const track = stream.getTracks()[ 0 ]; + + track.stop(); + + window.removeEventListener( 'resize', this.onWindowResize.bind( this ) ); + + this.element = null; + this.stream = null; + + } + + }, + + /** + * Set media stream + * @param {MediaStream} stream + * @memberOf Media + * @instance + */ + setMediaStream: function ( stream ) { + + this.stream = stream; + this.element.srcObject = stream; + + if ( this.scene ) { + + this.scene.background = this.createVideoTexture(); + + } + + window.addEventListener( 'resize', this.onWindowResize.bind( this ) ); + + }, + + /** + * Play video element + * @memberOf Media + * @instance + */ + playVideo: function () { + + const { element } = this; + + if ( element ) { + + element.play(); + this.dispatchEvent( { type: 'play' } ); + + } + + }, + + /** + * Pause video element + * @memberOf Media + * @instance + */ + pauseVideo: function () { + + const { element } = this; + + if ( element ) { + + element.pause(); + this.dispatchEvent( { type: 'pause' } ); + + } + + }, + + /** + * Create video texture + * @memberOf Media + * @instance + * @returns {THREE.VideoTexture} + */ + createVideoTexture: function () { + + const video = this.element; + const texture = new VideoTexture( video ); + + texture.generateMipmaps = false; + texture.minFilter = LinearFilter; + texture.magFilter = LinearFilter; + texture.format = RGBFormat; + texture.center.set( 0.5, 0.5 ); + + video.addEventListener( 'canplay', this.onWindowResize.bind( this ) ); + + return texture; + + }, + + /** + * Create video element + * @memberOf Media + * @instance + * @returns {HTMLVideoElement} + * @fires Media#canplay + */ + createVideoElement: function() { + + const dispatchEvent = this.dispatchEvent.bind( this ); + const video = document.createElement( 'video' ); + + /** + * Video can play event + * @type {object} + * @event Media#canplay + */ + const canPlay = () => dispatchEvent( { type: 'canplay' } ); + + video.setAttribute( 'autoplay', '' ); + video.setAttribute( 'muted', '' ); + video.setAttribute( 'playsinline', '' ); + + video.style.position = 'absolute'; + video.style.top = '0'; + video.style.left = '0'; + video.style.width = '100%'; + video.style.height = '100%'; + video.style.objectPosition = 'center'; + video.style.objectFit = 'cover'; + video.style.display = this.scene ? 'none' : ''; + + video.addEventListener( 'canplay', canPlay ); + + return video; + + }, + + /** + * On window resize event + * @param {Event} event + * @memberOf Media + * @instance + */ + onWindowResize: function () { + + if ( this.element && this.element.videoWidth && this.element.videoHeight && this.scene ) { + + const { clientWidth: width, clientHeight: height } = this.container; + const texture = this.scene.background; + const { videoWidth, videoHeight } = this.element; + const cameraRatio = videoHeight / videoWidth; + const viewportRatio = this.container ? width / height : 1.0; + const ratio = cameraRatio * viewportRatio * this.ratioScalar; + + if ( width > height ) { + texture.repeat.set( ratio, 1 ); + } else { + texture.repeat.set( 1, 1 / ratio ); + } + + } + + } + +} ); + +/** + * @classdesc Reticle 3D Sprite + * @constructor + * @param {THREE.Color} [color=0xffffff] - Color of the reticle sprite + * @param {boolean} [autoSelect=true] - Auto selection + * @param {number} [dwellTime=1500] - Duration for dwelling sequence to complete + */ + +function Reticle ( color = 0xffffff, autoSelect = true, dwellTime = 1500 ) { + + this.dpr = window.devicePixelRatio; + + const { canvas, context } = this.createCanvas(); + const material = new SpriteMaterial( { color, map: this.createCanvasTexture( canvas ) } ); + + Sprite.call( this, material ); + + this.canvasWidth = canvas.width; + this.canvasHeight = canvas.height; + this.context = context; + this.color = color instanceof Color ? color : new Color( color ); + + this.autoSelect = autoSelect; + this.dwellTime = dwellTime; + this.rippleDuration = 500; + this.position.z = -10; + this.center.set( 0.5, 0.5 ); + this.scale.set( 0.5, 0.5, 1 ); + + this.startTimestamp = null; + this.timerId = null; + this.callback = null; + + this.frustumCulled = false; + + this.updateCanvasArcByProgress( 0 ); + +} +Reticle.prototype = Object.assign( Object.create( Sprite.prototype ), { + + constructor: Reticle, + + /** + * Set material color + * @param {THREE.Color} color + * @memberOf Reticle + * @instance + */ + setColor: function ( color ) { + + this.material.color.copy( color instanceof Color ? color : new Color( color ) ); + + }, + + /** + * Create canvas texture + * @param {HTMLCanvasElement} canvas + * @memberOf Reticle + * @instance + * @returns {THREE.CanvasTexture} + */ + createCanvasTexture: function ( canvas ) { + + const texture = new CanvasTexture( canvas ); + texture.minFilter = LinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; + + return texture; + + }, + + /** + * Create canvas element + * @memberOf Reticle + * @instance + * @returns {object} object + * @returns {HTMLCanvasElement} object.canvas + * @returns {CanvasRenderingContext2D} object.context + */ + createCanvas: function () { + + const width = 32; + const height = 32; + const canvas = document.createElement( 'canvas' ); + const context = canvas.getContext( '2d' ); + const dpr = this.dpr; + + canvas.width = width * dpr; + canvas.height = height * dpr; + context.scale( dpr, dpr ); + + context.shadowBlur = 5; + context.shadowColor = 'rgba(200,200,200,0.9)'; + + return { canvas, context }; + + }, + + /** + * Update canvas arc by progress + * @param {number} progress + * @memberOf Reticle + * @instance + */ + updateCanvasArcByProgress: function ( progress ) { + + const context = this.context; + const { canvasWidth, canvasHeight, material } = this; + const dpr = this.dpr; + const degree = progress * Math.PI * 2; + const color = this.color.getStyle(); + const x = canvasWidth * 0.5 / dpr; + const y = canvasHeight * 0.5 / dpr; + const lineWidth = 3; + + context.clearRect( 0, 0, canvasWidth, canvasHeight ); + context.beginPath(); + + if ( progress === 0 ) { + context.arc( x, y, canvasWidth / 16, 0, 2 * Math.PI ); + context.fillStyle = color; + context.fill(); + } else { + context.arc( x, y, canvasWidth / 4 - lineWidth, -Math.PI / 2, -Math.PI / 2 + degree ); + context.strokeStyle = color; + context.lineWidth = lineWidth; + context.stroke(); + } + + context.closePath(); + + material.map.needsUpdate = true; + + }, + + /** + * Ripple effect + * @memberOf Reticle + * @instance + * @fires Reticle#reticle-ripple-start + * @fires Reticle#reticle-ripple-end + */ + ripple: function () { + + const context = this.context; + const { canvasWidth, canvasHeight, material } = this; + const duration = this.rippleDuration; + const timestamp = performance.now(); + const color = this.color; + const dpr = this.dpr; + const x = canvasWidth * 0.5 / dpr; + const y = canvasHeight * 0.5 / dpr; + + const update = () => { + + const timerId = window.requestAnimationFrame( update ); + const elapsed = performance.now() - timestamp; + const progress = elapsed / duration; + const opacity = 1.0 - progress > 0 ? 1.0 - progress : 0; + const radius = progress * canvasWidth * 0.5 / dpr; + + context.clearRect( 0, 0, canvasWidth, canvasHeight ); + context.beginPath(); + context.arc( x, y, radius, 0, Math.PI * 2 ); + context.fillStyle = `rgba(${color.r * 255}, ${color.g * 255}, ${color.b * 255}, ${opacity})`; + context.fill(); + context.closePath(); + + if ( progress >= 1.0 ) { + + window.cancelAnimationFrame( timerId ); + this.updateCanvasArcByProgress( 0 ); + + /** + * Reticle ripple end event + * @type {object} + * @event Reticle#reticle-ripple-end + */ + this.dispatchEvent( { type: 'reticle-ripple-end' } ); + + } + + material.map.needsUpdate = true; + + }; + + /** + * Reticle ripple start event + * @type {object} + * @event Reticle#reticle-ripple-start + */ + this.dispatchEvent( { type: 'reticle-ripple-start' } ); + + update(); + + }, + + /** + * Make reticle visible + * @memberOf Reticle + * @instance + */ + show: function () { + + this.visible = true; + + }, + + /** + * Make reticle invisible + * @memberOf Reticle + * @instance + */ + hide: function () { + + this.visible = false; + + }, + + /** + * Start dwelling + * @param {function} callback + * @memberOf Reticle + * @instance + * @fires Reticle#reticle-start + */ + start: function ( callback ) { + + if ( !this.autoSelect ) { + + return; + + } + + /** + * Reticle start event + * @type {object} + * @event Reticle#reticle-start + */ + this.dispatchEvent( { type: 'reticle-start' } ); + + this.startTimestamp = performance.now(); + this.callback = callback; + this.update(); + + }, + + /** + * End dwelling + * @memberOf Reticle + * @instance + * @fires Reticle#reticle-end + */ + end: function(){ + + if ( !this.startTimestamp ) { return; } + + window.cancelAnimationFrame( this.timerId ); + + this.updateCanvasArcByProgress( 0 ); + this.callback = null; + this.timerId = null; + this.startTimestamp = null; + + /** + * Reticle end event + * @type {object} + * @event Reticle#reticle-end + */ + this.dispatchEvent( { type: 'reticle-end' } ); + + }, + + /** + * Update dwelling + * @memberOf Reticle + * @instance + * @fires Reticle#reticle-update + */ + update: function () { + + this.timerId = window.requestAnimationFrame( this.update.bind( this ) ); + + const elapsed = performance.now() - this.startTimestamp; + const progress = elapsed / this.dwellTime; + + this.updateCanvasArcByProgress( progress ); + + /** + * Reticle update event + * @type {object} + * @event Reticle#reticle-update + */ + this.dispatchEvent( { type: 'reticle-update', progress } ); + + if ( progress >= 1.0 ) { + + window.cancelAnimationFrame( this.timerId ); + if ( this.callback ) { this.callback(); } + this.end(); + this.ripple(); + + } + + } + +} ); + +function createCommonjsModule(fn, module) { + return module = { exports: {} }, fn(module, module.exports), module.exports; +} + +var Tween = createCommonjsModule(function (module, exports) { +/** + * Tween.js - Licensed under the MIT license + * https://github.com/tweenjs/tween.js + * ---------------------------------------------- + * + * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors. + * Thank you all, you're awesome! + */ + + +var _Group = function () { + this._tweens = {}; + this._tweensAddedDuringUpdate = {}; +}; + +_Group.prototype = { + getAll: function () { + + return Object.keys(this._tweens).map(function (tweenId) { + return this._tweens[tweenId]; + }.bind(this)); + + }, + + removeAll: function () { + + this._tweens = {}; + + }, + + add: function (tween) { + + this._tweens[tween.getId()] = tween; + this._tweensAddedDuringUpdate[tween.getId()] = tween; + + }, + + remove: function (tween) { + + delete this._tweens[tween.getId()]; + delete this._tweensAddedDuringUpdate[tween.getId()]; + + }, + + update: function (time, preserve) { + + var tweenIds = Object.keys(this._tweens); + + if (tweenIds.length === 0) { + return false; + } + + time = time !== undefined ? time : TWEEN.now(); + + // Tweens are updated in "batches". If you add a new tween during an update, then the + // new tween will be updated in the next batch. + // If you remove a tween during an update, it may or may not be updated. However, + // if the removed tween was added during the current batch, then it will not be updated. + while (tweenIds.length > 0) { + this._tweensAddedDuringUpdate = {}; + + for (var i = 0; i < tweenIds.length; i++) { + + var tween = this._tweens[tweenIds[i]]; + + if (tween && tween.update(time) === false) { + tween._isPlaying = false; + + if (!preserve) { + delete this._tweens[tweenIds[i]]; + } + } + } + + tweenIds = Object.keys(this._tweensAddedDuringUpdate); + } + + return true; + + } +}; + +var TWEEN = new _Group(); + +TWEEN.Group = _Group; +TWEEN._nextId = 0; +TWEEN.nextId = function () { + return TWEEN._nextId++; +}; + + +// Include a performance.now polyfill. +// In node.js, use process.hrtime. +if (typeof (self) === 'undefined' && typeof (process) !== 'undefined' && process.hrtime) { + TWEEN.now = function () { + var time = process.hrtime(); + + // Convert [seconds, nanoseconds] to milliseconds. + return time[0] * 1000 + time[1] / 1000000; + }; +} +// In a browser, use self.performance.now if it is available. +else if (typeof (self) !== 'undefined' && + self.performance !== undefined && + self.performance.now !== undefined) { + // This must be bound, because directly assigning this function + // leads to an invocation exception in Chrome. + TWEEN.now = self.performance.now.bind(self.performance); +} +// Use Date.now if it is available. +else if (Date.now !== undefined) { + TWEEN.now = Date.now; +} +// Otherwise, use 'new Date().getTime()'. +else { + TWEEN.now = function () { + return new Date().getTime(); + }; +} + + +TWEEN.Tween = function (object, group) { + this._object = object; + this._valuesStart = {}; + this._valuesEnd = {}; + this._valuesStartRepeat = {}; + this._duration = 1000; + this._repeat = 0; + this._repeatDelayTime = undefined; + this._yoyo = false; + this._isPlaying = false; + this._reversed = false; + this._delayTime = 0; + this._startTime = null; + this._easingFunction = TWEEN.Easing.Linear.None; + this._interpolationFunction = TWEEN.Interpolation.Linear; + this._chainedTweens = []; + this._onStartCallback = null; + this._onStartCallbackFired = false; + this._onUpdateCallback = null; + this._onRepeatCallback = null; + this._onCompleteCallback = null; + this._onStopCallback = null; + this._group = group || TWEEN; + this._id = TWEEN.nextId(); + +}; + +TWEEN.Tween.prototype = { + getId: function () { + return this._id; + }, + + isPlaying: function () { + return this._isPlaying; + }, + + to: function (properties, duration) { + + this._valuesEnd = Object.create(properties); + + if (duration !== undefined) { + this._duration = duration; + } + + return this; + + }, + + duration: function duration(d) { + this._duration = d; + return this; + }, + + start: function (time) { + + this._group.add(this); + + this._isPlaying = true; + + this._onStartCallbackFired = false; + + this._startTime = time !== undefined ? typeof time === 'string' ? TWEEN.now() + parseFloat(time) : time : TWEEN.now(); + this._startTime += this._delayTime; + + for (var property in this._valuesEnd) { + + // Check if an Array was provided as property value + if (this._valuesEnd[property] instanceof Array) { + + if (this._valuesEnd[property].length === 0) { + continue; + } + + // Create a local copy of the Array with the start value at the front + this._valuesEnd[property] = [this._object[property]].concat(this._valuesEnd[property]); + + } + + // If `to()` specifies a property that doesn't exist in the source object, + // we should not set that property in the object + if (this._object[property] === undefined) { + continue; + } + + // Save the starting value. + this._valuesStart[property] = this._object[property]; + + if ((this._valuesStart[property] instanceof Array) === false) { + this._valuesStart[property] *= 1.0; // Ensures we're using numbers, not strings + } + + this._valuesStartRepeat[property] = this._valuesStart[property] || 0; + + } + + return this; + + }, + + stop: function () { + + if (!this._isPlaying) { + return this; + } + + this._group.remove(this); + this._isPlaying = false; + + if (this._onStopCallback !== null) { + this._onStopCallback(this._object); + } + + this.stopChainedTweens(); + return this; + + }, + + end: function () { + + this.update(Infinity); + return this; + + }, + + stopChainedTweens: function () { + + for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { + this._chainedTweens[i].stop(); + } + + }, + + group: function (group) { + this._group = group; + return this; + }, + + delay: function (amount) { + + this._delayTime = amount; + return this; + + }, + + repeat: function (times) { + + this._repeat = times; + return this; + + }, + + repeatDelay: function (amount) { + + this._repeatDelayTime = amount; + return this; + + }, + + yoyo: function (yoyo) { + + this._yoyo = yoyo; + return this; + + }, + + easing: function (easingFunction) { + + this._easingFunction = easingFunction; + return this; + + }, + + interpolation: function (interpolationFunction) { + + this._interpolationFunction = interpolationFunction; + return this; + + }, + + chain: function () { + + this._chainedTweens = arguments; + return this; + + }, + + onStart: function (callback) { + + this._onStartCallback = callback; + return this; + + }, + + onUpdate: function (callback) { + + this._onUpdateCallback = callback; + return this; + + }, + + onRepeat: function onRepeat(callback) { + + this._onRepeatCallback = callback; + return this; + + }, + + onComplete: function (callback) { + + this._onCompleteCallback = callback; + return this; + + }, + + onStop: function (callback) { + + this._onStopCallback = callback; + return this; + + }, + + update: function (time) { + + var property; + var elapsed; + var value; + + if (time < this._startTime) { + return true; + } + + if (this._onStartCallbackFired === false) { + + if (this._onStartCallback !== null) { + this._onStartCallback(this._object); + } + + this._onStartCallbackFired = true; + } + + elapsed = (time - this._startTime) / this._duration; + elapsed = (this._duration === 0 || elapsed > 1) ? 1 : elapsed; + + value = this._easingFunction(elapsed); + + for (property in this._valuesEnd) { + + // Don't update properties that do not exist in the source object + if (this._valuesStart[property] === undefined) { + continue; + } + + var start = this._valuesStart[property] || 0; + var end = this._valuesEnd[property]; + + if (end instanceof Array) { + + this._object[property] = this._interpolationFunction(end, value); + + } else { + + // Parses relative end values with start as base (e.g.: +10, -3) + if (typeof (end) === 'string') { + + if (end.charAt(0) === '+' || end.charAt(0) === '-') { + end = start + parseFloat(end); + } else { + end = parseFloat(end); + } + } + + // Protect against non numeric properties. + if (typeof (end) === 'number') { + this._object[property] = start + (end - start) * value; + } + + } + + } + + if (this._onUpdateCallback !== null) { + this._onUpdateCallback(this._object, elapsed); + } + + if (elapsed === 1) { + + if (this._repeat > 0) { + + if (isFinite(this._repeat)) { + this._repeat--; + } + + // Reassign starting values, restart by making startTime = now + for (property in this._valuesStartRepeat) { + + if (typeof (this._valuesEnd[property]) === 'string') { + this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]); + } + + if (this._yoyo) { + var tmp = this._valuesStartRepeat[property]; + + this._valuesStartRepeat[property] = this._valuesEnd[property]; + this._valuesEnd[property] = tmp; + } + + this._valuesStart[property] = this._valuesStartRepeat[property]; + + } + + if (this._yoyo) { + this._reversed = !this._reversed; + } + + if (this._repeatDelayTime !== undefined) { + this._startTime = time + this._repeatDelayTime; + } else { + this._startTime = time + this._delayTime; + } + + if (this._onRepeatCallback !== null) { + this._onRepeatCallback(this._object); + } + + return true; + + } else { + + if (this._onCompleteCallback !== null) { + + this._onCompleteCallback(this._object); + } + + for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { + // Make the chained tweens start exactly at the time they should, + // even if the `update()` method was called way past the duration of the tween + this._chainedTweens[i].start(this._startTime + this._duration); + } + + return false; + + } + + } + + return true; + + } +}; + + +TWEEN.Easing = { + + Linear: { + + None: function (k) { + + return k; + + } + + }, + + Quadratic: { + + In: function (k) { + + return k * k; + + }, + + Out: function (k) { + + return k * (2 - k); + + }, + + InOut: function (k) { + + if ((k *= 2) < 1) { + return 0.5 * k * k; + } + + return - 0.5 * (--k * (k - 2) - 1); + + } + + }, + + Cubic: { + + In: function (k) { + + return k * k * k; + + }, + + Out: function (k) { + + return --k * k * k + 1; + + }, + + InOut: function (k) { + + if ((k *= 2) < 1) { + return 0.5 * k * k * k; + } + + return 0.5 * ((k -= 2) * k * k + 2); + + } + + }, + + Quartic: { + + In: function (k) { + + return k * k * k * k; + + }, + + Out: function (k) { + + return 1 - (--k * k * k * k); + + }, + + InOut: function (k) { + + if ((k *= 2) < 1) { + return 0.5 * k * k * k * k; + } + + return - 0.5 * ((k -= 2) * k * k * k - 2); + + } + + }, + + Quintic: { + + In: function (k) { + + return k * k * k * k * k; + + }, + + Out: function (k) { + + return --k * k * k * k * k + 1; + + }, + + InOut: function (k) { + + if ((k *= 2) < 1) { + return 0.5 * k * k * k * k * k; + } + + return 0.5 * ((k -= 2) * k * k * k * k + 2); + + } + + }, + + Sinusoidal: { + + In: function (k) { + + return 1 - Math.cos(k * Math.PI / 2); + + }, + + Out: function (k) { + + return Math.sin(k * Math.PI / 2); + + }, + + InOut: function (k) { + + return 0.5 * (1 - Math.cos(Math.PI * k)); + + } + + }, + + Exponential: { + + In: function (k) { + + return k === 0 ? 0 : Math.pow(1024, k - 1); + + }, + + Out: function (k) { + + return k === 1 ? 1 : 1 - Math.pow(2, - 10 * k); + + }, + + InOut: function (k) { + + if (k === 0) { + return 0; + } + + if (k === 1) { + return 1; + } + + if ((k *= 2) < 1) { + return 0.5 * Math.pow(1024, k - 1); + } + + return 0.5 * (- Math.pow(2, - 10 * (k - 1)) + 2); + + } + + }, + + Circular: { + + In: function (k) { + + return 1 - Math.sqrt(1 - k * k); + + }, + + Out: function (k) { + + return Math.sqrt(1 - (--k * k)); + + }, + + InOut: function (k) { + + if ((k *= 2) < 1) { + return - 0.5 * (Math.sqrt(1 - k * k) - 1); + } + + return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1); + + } + + }, + + Elastic: { + + In: function (k) { + + if (k === 0) { + return 0; + } + + if (k === 1) { + return 1; + } + + return -Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI); + + }, + + Out: function (k) { + + if (k === 0) { + return 0; + } + + if (k === 1) { + return 1; + } + + return Math.pow(2, -10 * k) * Math.sin((k - 0.1) * 5 * Math.PI) + 1; + + }, + + InOut: function (k) { + + if (k === 0) { + return 0; + } + + if (k === 1) { + return 1; + } + + k *= 2; + + if (k < 1) { + return -0.5 * Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI); + } + + return 0.5 * Math.pow(2, -10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI) + 1; + + } + + }, + + Back: { + + In: function (k) { + + var s = 1.70158; + + return k * k * ((s + 1) * k - s); + + }, + + Out: function (k) { + + var s = 1.70158; + + return --k * k * ((s + 1) * k + s) + 1; + + }, + + InOut: function (k) { + + var s = 1.70158 * 1.525; + + if ((k *= 2) < 1) { + return 0.5 * (k * k * ((s + 1) * k - s)); + } + + return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2); + + } + + }, + + Bounce: { + + In: function (k) { + + return 1 - TWEEN.Easing.Bounce.Out(1 - k); + + }, + + Out: function (k) { + + if (k < (1 / 2.75)) { + return 7.5625 * k * k; + } else if (k < (2 / 2.75)) { + return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75; + } else if (k < (2.5 / 2.75)) { + return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375; + } else { + return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375; + } + + }, + + InOut: function (k) { + + if (k < 0.5) { + return TWEEN.Easing.Bounce.In(k * 2) * 0.5; + } + + return TWEEN.Easing.Bounce.Out(k * 2 - 1) * 0.5 + 0.5; + + } + + } + +}; + +TWEEN.Interpolation = { + + Linear: function (v, k) { + + var m = v.length - 1; + var f = m * k; + var i = Math.floor(f); + var fn = TWEEN.Interpolation.Utils.Linear; + + if (k < 0) { + return fn(v[0], v[1], f); + } + + if (k > 1) { + return fn(v[m], v[m - 1], m - f); + } + + return fn(v[i], v[i + 1 > m ? m : i + 1], f - i); + + }, + + Bezier: function (v, k) { + + var b = 0; + var n = v.length - 1; + var pw = Math.pow; + var bn = TWEEN.Interpolation.Utils.Bernstein; + + for (var i = 0; i <= n; i++) { + b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i); + } + + return b; + + }, + + CatmullRom: function (v, k) { + + var m = v.length - 1; + var f = m * k; + var i = Math.floor(f); + var fn = TWEEN.Interpolation.Utils.CatmullRom; + + if (v[0] === v[m]) { + + if (k < 0) { + i = Math.floor(f = m * (1 + k)); + } + + return fn(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i); + + } else { + + if (k < 0) { + return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]); + } + + if (k > 1) { + return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]); + } + + return fn(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i); + + } + + }, + + Utils: { + + Linear: function (p0, p1, t) { + + return (p1 - p0) * t + p0; + + }, + + Bernstein: function (n, i) { + + var fc = TWEEN.Interpolation.Utils.Factorial; + + return fc(n) / fc(i) / fc(n - i); + + }, + + Factorial: (function () { + + var a = [1]; + + return function (n) { + + var s = 1; + + if (a[n]) { + return a[n]; + } + + for (var i = n; i > 1; i--) { + s *= i; + } + + a[n] = s; + return s; + + }; + + })(), + + CatmullRom: function (p0, p1, p2, p3, t) { + + var v0 = (p2 - p0) * 0.5; + var v1 = (p3 - p1) * 0.5; + var t2 = t * t; + var t3 = t * t2; + + return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (- 3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1; + + } + + } + +}; + +// UMD (Universal Module Definition) +(function (root) { + + { + + // Node.js + module.exports = TWEEN; + + } + +})(); +}); + +/** + * @classdesc Information spot attached to panorama + * @constructor + * @param {number} [scale=300] - Default scale + * @param {string} [imageSrc=PANOLENS.DataImage.Info] - Image overlay info + * @param {boolean} [animated=true] - Enable default hover animation + */ +function Infospot ( scale = 300, imageSrc, animated ) { + + const duration = 500, scaleFactor = 1.3; + + imageSrc = imageSrc || DataImage.Info; + + Sprite.call( this ); + + this.type = 'infospot'; + + this.animated = animated !== undefined ? animated : true; + this.isHovering = false; + + /* + * TODO: Three.js bug hotfix for sprite raycasting r104 + * https://github.com/mrdoob/three.js/issues/14624 + */ + this.frustumCulled = false; + + this.element = null; + this.toPanorama = null; + this.cursorStyle = null; + + this.mode = MODES.NORMAL; + + this.scale.set( scale, scale, 1 ); + this.rotation.y = Math.PI; + + this.container = null; + + this.originalRaycast = this.raycast; + + // Event Handler + this.HANDLER_FOCUS = null; + + this.material.side = DoubleSide; + this.material.depthTest = false; + this.material.transparent = true; + this.material.opacity = 0; + + this.scaleUpAnimation = new Tween.Tween(); + this.scaleDownAnimation = new Tween.Tween(); + + + const postLoad = function ( texture ) { + + if ( !this.material ) { return; } + + const ratio = texture.image.width / texture.image.height; + const textureScale = new Vector3(); + + texture.image.width = texture.image.naturalWidth || 64; + texture.image.height = texture.image.naturalHeight || 64; + + this.scale.set( ratio * scale, scale, 1 ); + + textureScale.copy( this.scale ); + + this.scaleUpAnimation = new Tween.Tween( this.scale ) + .to( { x: textureScale.x * scaleFactor, y: textureScale.y * scaleFactor }, duration ) + .easing( Tween.Easing.Elastic.Out ); + + this.scaleDownAnimation = new Tween.Tween( this.scale ) + .to( { x: textureScale.x, y: textureScale.y }, duration ) + .easing( Tween.Easing.Elastic.Out ); + + this.material.map = texture; + this.material.needsUpdate = true; + + }.bind( this ); + + // Add show and hide animations + this.showAnimation = new Tween.Tween( this.material ) + .to( { opacity: 1 }, duration ) + .onStart( this.enableRaycast.bind( this, true ) ) + .easing( Tween.Easing.Quartic.Out ); + + this.hideAnimation = new Tween.Tween( this.material ) + .to( { opacity: 0 }, duration ) + .onStart( this.enableRaycast.bind( this, false ) ) + .easing( Tween.Easing.Quartic.Out ); + + // Attach event listeners + this.addEventListener( 'click', this.onClick ); + this.addEventListener( 'hover', this.onHover ); + this.addEventListener( 'hoverenter', this.onHoverStart ); + this.addEventListener( 'hoverleave', this.onHoverEnd ); + this.addEventListener( 'panolens-dual-eye-effect', this.onDualEyeEffect ); + this.addEventListener( 'panolens-container', this.setContainer.bind( this ) ); + this.addEventListener( 'dismiss', this.onDismiss ); + this.addEventListener( 'panolens-infospot-focus', this.setFocusMethod ); + + TextureLoader.load( imageSrc, postLoad ); + +} +Infospot.prototype = Object.assign( Object.create( Sprite.prototype ), { + + constructor: Infospot, + + /** + * Set infospot container + * @param {HTMLElement|object} data - Data with container information + * @memberOf Infospot + * @instance + */ + setContainer: function ( data ) { + + let container; + + if ( data instanceof HTMLElement ) { + + container = data; + + } else if ( data && data.container ) { + + container = data.container; + + } + + // Append element if exists + if ( container && this.element ) { + + container.appendChild( this.element ); + + } + + this.container = container; + + }, + + /** + * Get container + * @memberOf Infospot + * @instance + * @return {HTMLElement} - The container of this infospot + */ + getContainer: function () { + + return this.container; + + }, + + /** + * This will be called by a click event + * Translate and lock the hovering element if any + * @param {object} event - Event containing mouseEvent with clientX and clientY + * @memberOf Infospot + * @instance + */ + onClick: function ( event ) { + + if ( this.element && this.getContainer() ) { + + this.onHoverStart( event ); + + // Lock element + this.lockHoverElement(); + + } + + }, + + /** + * Dismiss current element if any + * @param {object} event - Dismiss event + * @memberOf Infospot + * @instance + */ + onDismiss: function () { + + if ( this.element ) { + + this.unlockHoverElement(); + this.onHoverEnd(); + + } + + }, + + /** + * This will be called by a mouse hover event + * Translate the hovering element if any + * @param {object} event - Event containing mouseEvent with clientX and clientY + * @memberOf Infospot + * @instance + */ + onHover: function () {}, + + /** + * This will be called on a mouse hover start + * Sets cursor style to 'pointer', display the element and scale up the infospot + * @param {object} event + * @memberOf Infospot + * @instance + */ + onHoverStart: function ( event ) { + + if ( !this.getContainer() ) { return; } + + const cursorStyle = this.cursorStyle || ( this.mode === MODES.NORMAL ? 'pointer' : 'default' ); + const { scaleDownAnimation, scaleUpAnimation, element } = this; + + this.isHovering = true; + this.container.style.cursor = cursorStyle; + + if ( this.animated ) { + + scaleDownAnimation.stop(); + scaleUpAnimation.start(); + + } + + if ( element && event.mouseEvent.clientX >= 0 && event.mouseEvent.clientY >= 0 ) { + + const { left, right, style } = element; + + if ( this.mode === MODES.CARDBOARD || this.mode === MODES.STEREO ) { + + style.display = 'none'; + left.style.display = 'block'; + right.style.display = 'block'; + + // Store element width for reference + element._width = left.clientWidth; + element._height = left.clientHeight; + + } else { + + style.display = 'block'; + if ( left ) { left.style.display = 'none'; } + if ( right ) { right.style.display = 'none'; } + + // Store element width for reference + element._width = element.clientWidth; + element._height = element.clientHeight; + + } + + } + + }, + + /** + * This will be called on a mouse hover end + * Sets cursor style to 'default', hide the element and scale down the infospot + * @memberOf Infospot + * @instance + */ + onHoverEnd: function () { + + if ( !this.getContainer() ) { return; } + + const { scaleDownAnimation, scaleUpAnimation, element } = this; + + this.isHovering = false; + this.container.style.cursor = 'default'; + + if ( this.animated ) { + + scaleUpAnimation.stop(); + scaleDownAnimation.start(); + + } + + if ( element && !this.element.locked ) { + + const { left, right, style } = element; + + style.display = 'none'; + if ( left ) { left.style.display = 'none'; } + if ( right ) { right.style.display = 'none'; } + + this.unlockHoverElement(); + + } + + }, + + /** + * On dual eye effect handler + * Creates duplicate left and right element + * @param {object} event - panolens-dual-eye-effect event + * @memberOf Infospot + * @instance + */ + onDualEyeEffect: function ( event ) { + + if ( !this.getContainer() ) { return; } + + let element, halfWidth, halfHeight; + + this.mode = event.mode; + + element = this.element; + + halfWidth = this.container.clientWidth / 2; + halfHeight = this.container.clientHeight / 2; + + if ( !element ) { + + return; + + } + + if ( !element.left && !element.right ) { + + element.left = element.cloneNode( true ); + element.right = element.cloneNode( true ); + + } + + if ( this.mode === MODES.CARDBOARD || this.mode === MODES.STEREO ) { + + element.left.style.display = element.style.display; + element.right.style.display = element.style.display; + element.style.display = 'none'; + + } else { + + element.style.display = element.left.style.display; + element.left.style.display = 'none'; + element.right.style.display = 'none'; + + } + + // Update elements translation + this.translateElement( halfWidth, halfHeight ); + + this.container.appendChild( element.left ); + this.container.appendChild( element.right ); + + }, + + /** + * Translate the hovering element by css transform + * @param {number} x - X position on the window screen + * @param {number} y - Y position on the window screen + * @memberOf Infospot + * @instance + */ + translateElement: function ( x, y ) { + + if ( !this.element._width || !this.element._height || !this.getContainer() ) { + + return; + + } + + let left, top, element, width, height, delta, container; + + container = this.container; + element = this.element; + width = element._width / 2; + height = element._height / 2; + delta = element.verticalDelta !== undefined ? element.verticalDelta : 40; + + left = x - width; + top = y - height - delta; + + if ( ( this.mode === MODES.CARDBOARD || this.mode === MODES.STEREO ) + && element.left && element.right + && !( x === container.clientWidth / 2 && y === container.clientHeight / 2 ) ) { + + left = container.clientWidth / 4 - width + ( x - container.clientWidth / 2 ); + top = container.clientHeight / 2 - height - delta + ( y - container.clientHeight / 2 ); + + this.setElementStyle( 'transform', element.left, 'translate(' + left + 'px, ' + top + 'px)' ); + + left += container.clientWidth / 2; + + this.setElementStyle( 'transform', element.right, 'translate(' + left + 'px, ' + top + 'px)' ); + + } else { + + this.setElementStyle( 'transform', element, 'translate(' + left + 'px, ' + top + 'px)' ); + + } + + }, + + /** + * Set vendor specific css + * @param {string} type - CSS style name + * @param {HTMLElement} element - The element to be modified + * @param {string} value - Style value + * @memberOf Infospot + * @instance + */ + setElementStyle: function ( type, element, value ) { + + const style = element.style; + + if ( type === 'transform' ) { + + style.webkitTransform = style.msTransform = style.transform = value; + + } + + }, + + /** + * Set hovering text content + * @param {string} text - Text to be displayed + * @memberOf Infospot + * @instance + */ + setText: function ( text ) { + + if ( this.element ) { + + this.element.textContent = text; + + } + + }, + + /** + * Set cursor css style on hover + * @memberOf Infospot + * @instance + */ + setCursorHoverStyle: function ( style ) { + + this.cursorStyle = style; + + }, + + /** + * Add hovering text element + * @param {string} text - Text to be displayed + * @param {number} [delta=40] - Vertical delta to the infospot + * @memberOf Infospot + * @instance + */ + addHoverText: function ( text, delta = 40 ) { + + if ( !this.element ) { + + this.element = document.createElement( 'div' ); + this.element.style.display = 'none'; + this.element.style.color = '#fff'; + this.element.style.top = 0; + this.element.style.maxWidth = '50%'; + this.element.style.maxHeight = '50%'; + this.element.style.textShadow = '0 0 3px #000000'; + this.element.style.fontFamily = '"Trebuchet MS", Helvetica, sans-serif'; + this.element.style.position = 'absolute'; + this.element.classList.add( 'panolens-infospot' ); + this.element.verticalDelta = delta; + + } + + this.setText( text ); + + }, + + /** + * Add hovering element by cloning an element + * @param {HTMLDOMElement} el - Element to be cloned and displayed + * @param {number} [delta=40] - Vertical delta to the infospot + * @memberOf Infospot + * @instance + */ + addHoverElement: function ( el, delta = 40 ) { + + if ( !this.element ) { + + this.element = el.cloneNode( true ); + this.element.style.display = 'none'; + this.element.style.top = 0; + this.element.style.position = 'absolute'; + this.element.classList.add( 'panolens-infospot' ); + this.element.verticalDelta = delta; + + } + + }, + + /** + * Remove hovering element + * @memberOf Infospot + * @instance + */ + removeHoverElement: function () { + + if ( this.element ) { + + if ( this.element.left ) { + + this.container.removeChild( this.element.left ); + this.element.left = null; + + } + + if ( this.element.right ) { + + this.container.removeChild( this.element.right ); + this.element.right = null; + + } + + this.container.removeChild( this.element ); + this.element = null; + + } + + }, + + /** + * Lock hovering element + * @memberOf Infospot + * @instance + */ + lockHoverElement: function () { + + if ( this.element ) { + + this.element.locked = true; + + } + + }, + + /** + * Unlock hovering element + * @memberOf Infospot + * @instance + */ + unlockHoverElement: function () { + + if ( this.element ) { + + this.element.locked = false; + + } + + }, + + /** + * Enable raycasting + * @param {boolean} [enabled=true] + * @memberOf Infospot + * @instance + */ + enableRaycast: function ( enabled = true ) { + + if ( enabled ) { + + this.raycast = this.originalRaycast; + + } else { + + this.raycast = () => {}; + + } + + }, + + /** + * Show infospot + * @param {number} [delay=0] - Delay time to show + * @memberOf Infospot + * @instance + */ + show: function ( delay = 0 ) { + + const { animated, hideAnimation, showAnimation, material } = this; + + if ( animated ) { + + hideAnimation.stop(); + showAnimation.delay( delay ).start(); + + } else { + + this.enableRaycast( true ); + material.opacity = 1; + + } + + }, + + /** + * Hide infospot + * @param {number} [delay=0] - Delay time to hide + * @memberOf Infospot + * @instance + */ + hide: function ( delay = 0 ) { + + const { animated, hideAnimation, showAnimation, material, element } = this; + + if ( element ) { + const { style } = element; + style.display = 'none'; + } + + if ( animated ) { + + showAnimation.stop(); + hideAnimation.delay( delay ).start(); + + } else { + + this.enableRaycast( false ); + material.opacity = 0; + + } + + }, + + /** + * Set focus event handler + * @memberOf Infospot + * @instance + */ + setFocusMethod: function ( event ) { + + if ( event ) { + + this.HANDLER_FOCUS = event.method; + + } + + }, + + /** + * Focus camera center to this infospot + * @param {number} [duration=1000] - Duration to tween + * @param {function} [easing=TWEEN.Easing.Exponential.Out] - Easing function + * @memberOf Infospot + * @instance + */ + focus: function ( duration, easing ) { + + if ( this.HANDLER_FOCUS ) { + + this.HANDLER_FOCUS( this.position, duration, easing ); + this.onDismiss(); + + } + + }, + + /** + * Dispose + * @memberOf Infospot + * @instance + */ + dispose: function () { + + const { geometry, material } = this; + const { map } = material; + + this.removeHoverElement(); + + if ( this.parent ) { + + this.parent.remove( this ); + + } + + if ( map ) { map.dispose(); material.map = null; } + if ( geometry ) { geometry.dispose(); this.geometry = null; } + if ( material ) { material.dispose(); this.material = null; } + + } + +} ); + +/** + * @classdesc Widget for controls + * @constructor + * @param {HTMLElement} container - A domElement where default control widget will be attached to + */ +function Widget ( container ) { + + if ( !container ) { + + console.warn( 'PANOLENS.Widget: No container specified' ); + + } + + EventDispatcher.call( this ); + + this.DEFAULT_TRANSITION = 'all 0.27s ease'; + this.TOUCH_ENABLED = !!(( 'ontouchstart' in window ) || window.DocumentTouch && document instanceof DocumentTouch); + this.PREVENT_EVENT_HANDLER = function ( event ) { + event.preventDefault(); + event.stopPropagation(); + }; + + this.container = container; + + this.barElement = null; + this.fullscreenElement = null; + this.videoElement = null; + this.settingElement = null; + + this.mainMenu = null; + + this.activeMainItem = null; + this.activeSubMenu = null; + this.mask = null; + +} + +Widget.prototype = Object.assign( Object.create( EventDispatcher.prototype ), { + + constructor: Widget, + + /** + * Add control bar + * @memberOf Widget + * @instance + */ + addControlBar: function () { + + if ( !this.container ) { + + console.warn( 'Widget container not set' ); + return; + } + + var scope = this, bar, styleTranslate, styleOpacity, gradientStyle; + + gradientStyle = 'linear-gradient(bottom, rgba(0,0,0,0.2), rgba(0,0,0,0))'; + + bar = document.createElement( 'div' ); + bar.style.width = '100%'; + bar.style.height = '44px'; + bar.style.float = 'left'; + bar.style.transform = bar.style.webkitTransform = bar.style.msTransform = 'translateY(-100%)'; + bar.style.background = '-webkit-' + gradientStyle; + bar.style.background = '-moz-' + gradientStyle; + bar.style.background = '-o-' + gradientStyle; + bar.style.background = '-ms-' + gradientStyle; + bar.style.background = gradientStyle; + bar.style.transition = this.DEFAULT_TRANSITION; + bar.style.pointerEvents = 'none'; + bar.isHidden = false; + bar.toggle = function () { + bar.isHidden = !bar.isHidden; + styleTranslate = bar.isHidden ? 'translateY(0)' : 'translateY(-100%)'; + styleOpacity = bar.isHidden ? 0 : 1; + bar.style.transform = bar.style.webkitTransform = bar.style.msTransform = styleTranslate; + bar.style.opacity = styleOpacity; + }; + + // Menu + var menu = this.createDefaultMenu(); + this.mainMenu = this.createMainMenu( menu ); + bar.appendChild( this.mainMenu ); + + // Mask + var mask = this.createMask(); + this.mask = mask; + this.container.appendChild( mask ); + + // Dispose + bar.dispose = function () { + + if ( scope.fullscreenElement ) { + + bar.removeChild( scope.fullscreenElement ); + scope.fullscreenElement.dispose(); + scope.fullscreenElement = null; + + } + + if ( scope.settingElement ) { + + bar.removeChild( scope.settingElement ); + scope.settingElement.dispose(); + scope.settingElement = null; + + } + + if ( scope.videoElement ) { + + bar.removeChild( scope.videoElement ); + scope.videoElement.dispose(); + scope.videoElement = null; + + } + + }; + + this.container.appendChild( bar ); + + // Mask events + this.mask.addEventListener( 'mousemove', this.PREVENT_EVENT_HANDLER, true ); + this.mask.addEventListener( 'mouseup', this.PREVENT_EVENT_HANDLER, true ); + this.mask.addEventListener( 'mousedown', this.PREVENT_EVENT_HANDLER, true ); + this.mask.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', function ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + scope.mask.hide(); + scope.settingElement.deactivate(); + + }, false ); + + // Event listener + this.addEventListener( 'control-bar-toggle', bar.toggle ); + + this.barElement = bar; + + }, + + /** + * Create default menu + * @memberOf Widget + * @instance + */ + createDefaultMenu: function () { + + var scope = this, handler; + + handler = function ( method, data ) { + + return function () { + + scope.dispatchEvent( { + + type: 'panolens-viewer-handler', + method: method, + data: data + + } ); + + }; + + }; + + return [ + + { + title: 'Control', + subMenu: [ + { + title: this.TOUCH_ENABLED ? 'Touch' : 'Mouse', + handler: handler( 'enableControl', CONTROLS.ORBIT ) + }, + { + title: 'Sensor', + handler: handler( 'enableControl', CONTROLS.DEVICEORIENTATION ) + } + ] + }, + + { + title: 'Mode', + subMenu: [ + { + title: 'Normal', + handler: handler( 'disableEffect' ) + }, + { + title: 'Cardboard', + handler: handler( 'enableEffect', MODES.CARDBOARD ) + }, + { + title: 'Stereoscopic', + handler: handler( 'enableEffect', MODES.STEREO ) + } + ] + } + + ]; + + }, + + /** + * Add buttons on top of control bar + * @param {string} name - The control button name to be created + * @memberOf Widget + * @instance + */ + addControlButton: function ( name ) { + + let element; + + switch( name ) { + + case 'fullscreen': + + element = this.createFullscreenButton(); + this.fullscreenElement = element; + + break; + + case 'setting': + + element = this.createSettingButton(); + this.settingElement = element; + + break; + + case 'video': + + element = this.createVideoControl(); + this.videoElement = element; + + break; + + default: + + return; + + } + + if ( !element ) { + + return; + + } + + this.barElement.appendChild( element ); + + }, + + /** + * Create modal mask + * @memberOf Widget + * @instance + */ + createMask: function () { + + const element = document.createElement( 'div' ); + element.style.position = 'absolute'; + element.style.top = 0; + element.style.left = 0; + element.style.width = '100%'; + element.style.height = '100%'; + element.style.background = 'transparent'; + element.style.display = 'none'; + + element.show = function () { + + this.style.display = 'block'; + + }; + + element.hide = function () { + + this.style.display = 'none'; + + }; + + return element; + + }, + + /** + * Create Setting button to toggle menu + * @memberOf Widget + * @instance + */ + createSettingButton: function () { + + let scope = this, item; + + function onTap ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + scope.mainMenu.toggle(); + + if ( this.activated ) { + + this.deactivate(); + + } else { + + this.activate(); + + } + + } + + item = this.createCustomItem( { + + style: { + + backgroundImage: 'url("' + DataImage.Setting + '")', + webkitTransition: this.DEFAULT_TRANSITION, + transition: this.DEFAULT_TRANSITION + + }, + + onTap: onTap + + } ); + + item.activate = function () { + + this.style.transform = 'rotate3d(0,0,1,90deg)'; + this.activated = true; + scope.mask.show(); + + }; + + item.deactivate = function () { + + this.style.transform = 'rotate3d(0,0,0,0)'; + this.activated = false; + scope.mask.hide(); + + if ( scope.mainMenu && scope.mainMenu.visible ) { + + scope.mainMenu.hide(); + + } + + if ( scope.activeSubMenu && scope.activeSubMenu.visible ) { + + scope.activeSubMenu.hide(); + + } + + if ( scope.mainMenu && scope.mainMenu._width ) { + + scope.mainMenu.changeSize( scope.mainMenu._width ); + scope.mainMenu.unslideAll(); + + } + + }; + + item.activated = false; + + return item; + + }, + + /** + * Create Fullscreen button + * @return {HTMLSpanElement} - The dom element icon for fullscreen + * @memberOf Widget + * @instance + * @fires Widget#panolens-viewer-handler + */ + createFullscreenButton: function () { + + let scope = this, item, isFullscreen = false, tapSkipped = true, stylesheetId; + + const { container } = this; + + stylesheetId = 'panolens-style-addon'; + + // Don't create button if no support + if ( !document.fullscreenEnabled && + !document.webkitFullscreenEnabled && + !document.mozFullScreenEnabled && + !document.msFullscreenEnabled ) { + return; + } + + function onTap ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + tapSkipped = false; + + if ( !isFullscreen ) { + + if ( container.requestFullscreen ) { container.requestFullscreen(); } + if ( container.msRequestFullscreen ) { container.msRequestFullscreen(); } + if ( container.mozRequestFullScreen ) { container.mozRequestFullScreen(); } + if ( container.webkitRequestFullscreen ) { container.webkitRequestFullscreen( Element.ALLOW_KEYBOARD_INPUT ); } + + isFullscreen = true; + + } else { + + if ( document.exitFullscreen ) { document.exitFullscreen(); } + if ( document.msExitFullscreen ) { document.msExitFullscreen(); } + if ( document.mozCancelFullScreen ) { document.mozCancelFullScreen(); } + if ( document.webkitExitFullscreen ) { document.webkitExitFullscreen( ); } + + isFullscreen = false; + + } + + this.style.backgroundImage = ( isFullscreen ) + ? 'url("' + DataImage.FullscreenLeave + '")' + : 'url("' + DataImage.FullscreenEnter + '")'; + + } + + function onFullScreenChange () { + + if ( tapSkipped ) { + + isFullscreen = !isFullscreen; + + item.style.backgroundImage = ( isFullscreen ) + ? 'url("' + DataImage.FullscreenLeave + '")' + : 'url("' + DataImage.FullscreenEnter + '")'; + + } + + /** + * Viewer handler event + * @type {object} + * @event Widget#panolens-viewer-handler + * @property {string} method - 'onWindowResize' function call on Viewer + */ + scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onWindowResize' } ); + + tapSkipped = true; + + } + + document.addEventListener( 'fullscreenchange', onFullScreenChange, false ); + document.addEventListener( 'webkitfullscreenchange', onFullScreenChange, false ); + document.addEventListener( 'mozfullscreenchange', onFullScreenChange, false ); + document.addEventListener( 'MSFullscreenChange', onFullScreenChange, false ); + + item = this.createCustomItem( { + + style: { + + backgroundImage: 'url("' + DataImage.FullscreenEnter + '")' + + }, + + onTap: onTap + + } ); + + // Add fullscreen stlye if not exists + if ( !document.querySelector( stylesheetId ) ) { + const sheet = document.createElement( 'style' ); + sheet.id = stylesheetId; + sheet.innerHTML = ':-webkit-full-screen { width: 100% !important; height: 100% !important }'; + document.body.appendChild( sheet ); + } + + return item; + + }, + + /** + * Create video control container + * @memberOf Widget + * @instance + * @return {HTMLSpanElement} - The dom element icon for video control + */ + createVideoControl: function () { + + const item = document.createElement( 'span' ); + item.style.display = 'none'; + item.show = function () { + + item.style.display = ''; + + }; + + item.hide = function () { + + item.style.display = 'none'; + item.controlButton.paused = true; + item.controlButton.update(); + + }; + + item.controlButton = this.createVideoControlButton(); + item.seekBar = this.createVideoControlSeekbar(); + + item.appendChild( item.controlButton ); + item.appendChild( item.seekBar ); + + item.dispose = function () { + + item.removeChild( item.controlButton ); + item.removeChild( item.seekBar ); + + item.controlButton.dispose(); + item.controlButton = null; + + item.seekBar.dispose(); + item.seekBar = null; + + }; + + this.addEventListener( 'video-control-show', item.show ); + this.addEventListener( 'video-control-hide', item.hide ); + + return item; + + }, + + /** + * Create video control button + * @memberOf Widget + * @instance + * @return {HTMLSpanElement} - The dom element icon for video control + * @fires Widget#panolens-viewer-handler + */ + createVideoControlButton: function () { + + const scope = this; + + function onTap ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + /** + * Viewer handler event + * @type {object} + * @event Widget#panolens-viewer-handler + * @property {string} method - 'toggleVideoPlay' function call on Viewer + */ + scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'toggleVideoPlay', data: !this.paused } ); + + this.paused = !this.paused; + + item.update(); + + } + const item = this.createCustomItem( { + + style: { + + float: 'left', + backgroundImage: 'url("' + DataImage.VideoPlay + '")' + + }, + + onTap: onTap + + } ); + + item.paused = true; + + item.update = function ( paused ) { + + this.paused = paused !== undefined ? paused : this.paused; + + this.style.backgroundImage = 'url("' + ( this.paused + ? DataImage.VideoPlay + : DataImage.VideoPause ) + '")'; + + }; + + return item; + + }, + + /** + * Create video seekbar + * @memberOf Widget + * @instance + * @return {HTMLSpanElement} - The dom element icon for video seekbar + * @fires Widget#panolens-viewer-handler + */ + createVideoControlSeekbar: function () { + + let scope = this, item, progressElement, progressElementControl, + isDragging = false, mouseX, percentageNow, percentageNext; + + progressElement = document.createElement( 'div' ); + progressElement.style.width = '0%'; + progressElement.style.height = '100%'; + progressElement.style.backgroundColor = '#fff'; + + progressElementControl = document.createElement( 'div' ); + progressElementControl.style.float = 'right'; + progressElementControl.style.width = '14px'; + progressElementControl.style.height = '14px'; + progressElementControl.style.transform = 'translate(7px, -5px)'; + progressElementControl.style.borderRadius = '50%'; + progressElementControl.style.backgroundColor = '#ddd'; + + progressElementControl.addEventListener( 'mousedown', onMouseDown, { passive: true } ); + progressElementControl.addEventListener( 'touchstart', onMouseDown, { passive: true } ); + + function onMouseDown ( event ) { + + event.stopPropagation(); + + isDragging = true; + + mouseX = event.clientX || ( event.changedTouches && event.changedTouches[0].clientX ); + + percentageNow = parseInt( progressElement.style.width ) / 100; + + addControlListeners(); + } + + function onVideoControlDrag ( event ) { + + if( isDragging ){ + + const clientX = event.clientX || ( event.changedTouches && event.changedTouches[0].clientX ); + + percentageNext = ( clientX - mouseX ) / item.clientWidth; + + percentageNext = percentageNow + percentageNext; + + percentageNext = percentageNext > 1 ? 1 : ( ( percentageNext < 0 ) ? 0 : percentageNext ); + + item.setProgress ( percentageNext ); + + /** + * Viewer handler event + * @type {object} + * @event Widget#panolens-viewer-handler + * @property {string} method - 'setVideoCurrentTime' function call on Viewer + * @property {number} data - Percentage of current video. Range from 0.0 to 1.0 + */ + scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'setVideoCurrentTime', data: percentageNext } ); + + } + + } + + function onVideoControlStop ( event ) { + + event.stopPropagation(); + + isDragging = false; + + removeControlListeners(); + + } + + function addControlListeners () { + + scope.container.addEventListener( 'mousemove', onVideoControlDrag, { passive: true } ); + scope.container.addEventListener( 'mouseup', onVideoControlStop, { passive: true } ); + scope.container.addEventListener( 'touchmove', onVideoControlDrag, { passive: true } ); + scope.container.addEventListener( 'touchend', onVideoControlStop, { passive: true } ); + + + } + + function removeControlListeners () { + + scope.container.removeEventListener( 'mousemove', onVideoControlDrag, false ); + scope.container.removeEventListener( 'mouseup', onVideoControlStop, false ); + scope.container.removeEventListener( 'touchmove', onVideoControlDrag, false ); + scope.container.removeEventListener( 'touchend', onVideoControlStop, false ); + + } + + function onTap ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + if ( event.target === progressElementControl ) { return; } + + const percentage = ( event.changedTouches && event.changedTouches.length > 0 ) + ? ( event.changedTouches[0].pageX - event.target.getBoundingClientRect().left ) / this.clientWidth + : event.offsetX / this.clientWidth; + + /** + * Viewer handler event + * @type {object} + * @property {string} method - 'setVideoCurrentTime' function call on Viewer + * @property {number} data - Percentage of current video. Range from 0.0 to 1.0 + */ + scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'setVideoCurrentTime', data: percentage } ); + + item.setProgress( event.offsetX / this.clientWidth ); + + } + function onDispose () { + + removeControlListeners(); + progressElement = null; + progressElementControl = null; + + } + + progressElement.appendChild( progressElementControl ); + + item = this.createCustomItem( { + + style: { + + float: 'left', + width: '30%', + height: '4px', + marginTop: '20px', + backgroundColor: 'rgba(188,188,188,0.8)' + + }, + + onTap: onTap, + onDispose: onDispose + + } ); + + item.appendChild( progressElement ); + + item.setProgress = function( percentage ) { + + progressElement.style.width = percentage * 100 + '%'; + + }; + + this.addEventListener( 'video-update', function ( event ) { + + item.setProgress( event.percentage ); + + } ); + + item.progressElement = progressElement; + item.progressElementControl = progressElementControl; + + return item; + + }, + + /** + * Create menu item + * @param {string} title - Title to display + * @memberOf Widget + * @instance + * @return {HTMLElement} - An anchor tag element + */ + createMenuItem: function ( title ) { + + const scope = this; + const item = document.createElement( 'a' ); + item.textContent = title; + item.style.display = 'block'; + item.style.padding = '10px'; + item.style.textDecoration = 'none'; + item.style.cursor = 'pointer'; + item.style.pointerEvents = 'auto'; + item.style.transition = this.DEFAULT_TRANSITION; + + item.slide = function ( right ) { + + this.style.transform = 'translateX(' + ( right ? '' : '-' ) + '100%)'; + + }; + + item.unslide = function () { + + this.style.transform = 'translateX(0)'; + + }; + + item.setIcon = function ( url ) { + + if ( this.icon ) { + + this.icon.style.backgroundImage = 'url(' + url + ')'; + + } + + }; + + item.setSelectionTitle = function ( title ) { + + if ( this.selection ) { + + this.selection.textContent = title; + + } + + }; + + item.addSelection = function ( name ) { + + const selection = document.createElement( 'span' ); + selection.style.fontSize = '13px'; + selection.style.fontWeight = '300'; + selection.style.float = 'right'; + + this.selection = selection; + this.setSelectionTitle( name ); + this.appendChild( selection ); + + return this; + + }; + + item.addIcon = function ( url = DataImage.ChevronRight, left = false, flip = false ) { + + const element = document.createElement( 'span' ); + element.style.float = left ? 'left' : 'right'; + element.style.width = '17px'; + element.style.height = '17px'; + element.style[ 'margin' + ( left ? 'Right' : 'Left' ) ] = '12px'; + element.style.backgroundSize = 'cover'; + + if ( flip ) { + + element.style.transform = 'rotateZ(180deg)'; + + } + + this.icon = element; + this.setIcon( url ); + this.appendChild( element ); + + return this; + + }; + + item.addSubMenu = function ( title, items ) { + + this.subMenu = scope.createSubMenu( title, items ); + + return this; + + }; + + item.addEventListener( 'mouseenter', function () { + + this.style.backgroundColor = '#e0e0e0'; + + }, false ); + + item.addEventListener( 'mouseleave', function () { + + this.style.backgroundColor = '#fafafa'; + + }, false ); + + return item; + + }, + + /** + * Create menu item header + * @param {string} title - Title to display + * @memberOf Widget + * @instance + * @return {HTMLElement} - An anchor tag element + */ + createMenuItemHeader: function ( title ) { + + const header = this.createMenuItem( title ); + + header.style.borderBottom = '1px solid #333'; + header.style.paddingBottom = '15px'; + + return header; + + }, + + /** + * Create main menu + * @param {array} menus - Menu array list + * @memberOf Widget + * @instance + * @return {HTMLElement} - A span element + */ + createMainMenu: function ( menus ) { + + let scope = this, menu = this.createMenu(); + + menu._width = 200; + menu.changeSize( menu._width ); + + function onTap ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + let mainMenu = scope.mainMenu, subMenu = this.subMenu; + + function onNextTick () { + + mainMenu.changeSize( subMenu.clientWidth ); + subMenu.show(); + subMenu.unslideAll(); + + } + + mainMenu.hide(); + mainMenu.slideAll(); + mainMenu.parentElement.appendChild( subMenu ); + + scope.activeMainItem = this; + scope.activeSubMenu = subMenu; + + window.requestAnimationFrame( onNextTick ); + + } + for ( var i = 0; i < menus.length; i++ ) { + + var item = menu.addItem( menus[ i ].title ); + + item.style.paddingLeft = '20px'; + + item.addIcon() + .addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', onTap, false ); + + if ( menus[ i ].subMenu && menus[ i ].subMenu.length > 0 ) { + + var title = menus[ i ].subMenu[ 0 ].title; + + item.addSelection( title ) + .addSubMenu( menus[ i ].title, menus[ i ].subMenu ); + + } + + } + + return menu; + + }, + + /** + * Create sub menu + * @param {string} title - Sub menu title + * @param {array} items - Item array list + * @memberOf Widget + * @instance + * @return {HTMLElement} - A span element + */ + createSubMenu: function ( title, items ) { + + let scope = this, menu, subMenu = this.createMenu(); + + subMenu.items = items; + subMenu.activeItem = null; + + function onTap ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + menu = scope.mainMenu; + menu.changeSize( menu._width ); + menu.unslideAll(); + menu.show(); + subMenu.slideAll( true ); + subMenu.hide(); + + if ( this.type !== 'header' ) { + + subMenu.setActiveItem( this ); + scope.activeMainItem.setSelectionTitle( this.textContent ); + + if ( this.handler ) { this.handler(); } + + } + + } + + subMenu.addHeader( title ).addIcon( undefined, true, true ).addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', onTap, false ); + + for ( let i = 0; i < items.length; i++ ) { + + const item = subMenu.addItem( items[ i ].title ); + + item.style.fontWeight = 300; + item.handler = items[ i ].handler; + item.addIcon( ' ', true ); + item.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', onTap, false ); + + if ( !subMenu.activeItem ) { + + subMenu.setActiveItem( item ); + + } + + } + + subMenu.slideAll( true ); + + return subMenu; + + }, + + /** + * Create general menu + * @memberOf Widget + * @instance + * @return {HTMLElement} - A span element + */ + createMenu: function () { + + const scope = this; + const menu = document.createElement( 'span' ); + const style = menu.style; + + style.padding = '5px 0'; + style.position = 'fixed'; + style.bottom = '100%'; + style.right = '14px'; + style.backgroundColor = '#fafafa'; + style.fontFamily = 'Helvetica Neue'; + style.fontSize = '14px'; + style.visibility = 'hidden'; + style.opacity = 0; + style.boxShadow = '0 0 12pt rgba(0,0,0,0.25)'; + style.borderRadius = '2px'; + style.overflow = 'hidden'; + style.willChange = 'width, height, opacity'; + style.pointerEvents = 'auto'; + style.transition = this.DEFAULT_TRANSITION; + + menu.visible = false; + + menu.changeSize = function ( width, height ) { + + if ( width ) { + + this.style.width = width + 'px'; + + } + + if ( height ) { + + this.style.height = height + 'px'; + + } + + }; + + menu.show = function () { + + this.style.opacity = 1; + this.style.visibility = 'visible'; + this.visible = true; + + }; + + menu.hide = function () { + + this.style.opacity = 0; + this.style.visibility = 'hidden'; + this.visible = false; + + }; + + menu.toggle = function () { + + if ( this.visible ) { + + this.hide(); + + } else { + + this.show(); + + } + + }; + + menu.slideAll = function ( right ) { + + for ( let i = 0; i < menu.children.length; i++ ){ + + if ( menu.children[ i ].slide ) { + + menu.children[ i ].slide( right ); + + } + + } + + }; + + menu.unslideAll = function () { + + for ( let i = 0; i < menu.children.length; i++ ){ + + if ( menu.children[ i ].unslide ) { + + menu.children[ i ].unslide(); + + } + + } + + }; + + menu.addHeader = function ( title ) { + + const header = scope.createMenuItemHeader( title ); + header.type = 'header'; + + this.appendChild( header ); + + return header; + + }; + + menu.addItem = function ( title ) { + + const item = scope.createMenuItem( title ); + item.type = 'item'; + + this.appendChild( item ); + + return item; + + }; + + menu.setActiveItem = function ( item ) { + + if ( this.activeItem ) { + + this.activeItem.setIcon( ' ' ); + + } + + item.setIcon( DataImage.Check ); + + this.activeItem = item; + + }; + + menu.addEventListener( 'mousemove', this.PREVENT_EVENT_HANDLER, true ); + menu.addEventListener( 'mouseup', this.PREVENT_EVENT_HANDLER, true ); + menu.addEventListener( 'mousedown', this.PREVENT_EVENT_HANDLER, true ); + + return menu; + + }, + + /** + * Create custom item element + * @memberOf Widget + * @instance + * @return {HTMLSpanElement} - The dom element icon + */ + createCustomItem: function ( options = {} ) { + + const scope = this; + const item = options.element || document.createElement( 'span' ); + const { onDispose } = options; + + item.style.cursor = 'pointer'; + item.style.float = 'right'; + item.style.width = '44px'; + item.style.height = '100%'; + item.style.backgroundSize = '60%'; + item.style.backgroundRepeat = 'no-repeat'; + item.style.backgroundPosition = 'center'; + item.style.webkitUserSelect = + item.style.MozUserSelect = + item.style.userSelect = 'none'; + item.style.position = 'relative'; + item.style.pointerEvents = 'auto'; + + // White glow on icon + item.addEventListener( scope.TOUCH_ENABLED ? 'touchstart' : 'mouseenter', function() { + item.style.filter = + item.style.webkitFilter = 'drop-shadow(0 0 5px rgba(255,255,255,1))'; + }, { passive: true }); + item.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'mouseleave', function() { + item.style.filter = + item.style.webkitFilter = ''; + }, { passive: true }); + + this.mergeStyleOptions( item, options.style ); + + if ( options.onTap ) { + + item.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', options.onTap, false ); + + } + + item.dispose = function () { + + item.removeEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', options.onTap, false ); + + if ( onDispose ) { options.onDispose(); } + + }; + + return item; + + }, + + /** + * Merge item css style + * @param {HTMLElement} element - The element to be merged with style + * @param {object} options - The style options + * @memberOf Widget + * @instance + * @return {HTMLElement} - The same element with merged styles + */ + mergeStyleOptions: function ( element, options = {} ) { + + for ( let property in options ){ + + if ( options.hasOwnProperty( property ) ) { + + element.style[ property ] = options[ property ]; + + } + + } + + return element; + + }, + + /** + * Dispose widgets by detaching dom elements from container + * @memberOf Widget + * @instance + */ + dispose: function () { + + if ( this.barElement ) { + this.container.removeChild( this.barElement ); + this.barElement.dispose(); + this.barElement = null; + + } + + } + +} ); + +/** + * @classdesc Base Panorama + * @constructor + * @param {THREE.Geometry} geometry - The geometry for this panorama + * @param {THREE.Material} material - The material for this panorama + */ +function Panorama ( geometry, material ) { + + Mesh.call( this, geometry, material ); + + this.type = 'panorama'; + + this.ImageQualityLow = 1; + this.ImageQualityFair = 2; + this.ImageQualityMedium = 3; + this.ImageQualityHigh = 4; + this.ImageQualitySuperHigh = 5; + + this.animationDuration = 1000; + + this.defaultInfospotSize = 350; + + this.container = undefined; + + this.loaded = false; + + this.linkedSpots = []; + + this.isInfospotVisible = false; + + this.linkingImageURL = undefined; + this.linkingImageScale = undefined; + + this.material.side = BackSide; + this.material.opacity = 0; + + this.scale.x *= -1; + this.renderOrder = -1; + + this.active = false; + + this.infospotAnimation = new Tween.Tween( this ).to( {}, this.animationDuration / 2 ); + + this.addEventListener( 'load', this.fadeIn.bind( this ) ); + this.addEventListener( 'panolens-container', this.setContainer.bind( this ) ); + this.addEventListener( 'click', this.onClick.bind( this ) ); + + this.setupTransitions(); + +} + +Panorama.prototype = Object.assign( Object.create( Mesh.prototype ), { + + constructor: Panorama, + + /** + * Adding an object + * To counter the scale.x = -1, it will automatically add an + * empty object with inverted scale on x + * @memberOf Panorama + * @instance + * @param {THREE.Object3D} object - The object to be added + */ + add: function ( object ) { + + let invertedObject; + + if ( arguments.length > 1 ) { + + for ( var i = 0; i < arguments.length; i ++ ) { + + this.add( arguments[ i ] ); + + } + + return this; + + } + + // In case of infospots + if ( object instanceof Infospot ) { + + invertedObject = object; + + if ( object.dispatchEvent ) { + + const { container } = this; + + if ( container ) { object.dispatchEvent( { type: 'panolens-container', container } ); } + + object.dispatchEvent( { type: 'panolens-infospot-focus', method: function ( vector, duration, easing ) { + + /** + * Infospot focus handler event + * @type {object} + * @event Panorama#panolens-viewer-handler + * @property {string} method - Viewer function name + * @property {*} data - The argument to be passed into the method + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'tweenControlCenter', data: [ vector, duration, easing ] } ); + + + }.bind( this ) } ); + } + + } else { + + // Counter scale.x = -1 effect + invertedObject = new Object3D(); + invertedObject.scale.x = -1; + invertedObject.scalePlaceHolder = true; + invertedObject.add( object ); + + } + + Object3D.prototype.add.call( this, invertedObject ); + + }, + + load: function () { + + this.onLoad(); + + }, + + /** + * Click event handler + * @param {object} event - Click event + * @memberOf Panorama + * @instance + * @fires Infospot#dismiss + */ + onClick: function ( event ) { + + if ( event.intersects && event.intersects.length === 0 ) { + + this.traverse( function ( object ) { + + /** + * Dimiss event + * @type {object} + * @event Infospot#dismiss + */ + object.dispatchEvent( { type: 'dismiss' } ); + + } ); + + } + + }, + + /** + * Set container of this panorama + * @param {HTMLElement|object} data - Data with container information + * @memberOf Panorama + * @instance + * @fires Infospot#panolens-container + */ + setContainer: function ( data ) { + + let container; + + if ( data instanceof HTMLElement ) { + + container = data; + + } else if ( data && data.container ) { + + container = data.container; + + } + + if ( container ) { + + this.children.forEach( function ( child ) { + + if ( child instanceof Infospot && child.dispatchEvent ) { + + /** + * Set container event + * @type {object} + * @event Infospot#panolens-container + * @property {HTMLElement} container - The container of this panorama + */ + child.dispatchEvent( { type: 'panolens-container', container: container } ); + + } + + } ); + + this.container = container; + + } + + }, + + /** + * This will be called when panorama is loaded + * @memberOf Panorama + * @instance + * @fires Panorama#load + */ + onLoad: function () { + + this.loaded = true; + + /** + * Load panorama event + * @type {object} + * @event Panorama#load + */ + this.dispatchEvent( { type: 'load' } ); + + }, + + /** + * This will be called when panorama is in progress + * @memberOf Panorama + * @instance + * @fires Panorama#progress + */ + onProgress: function ( progress ) { + + /** + * Loading panorama progress event + * @type {object} + * @event Panorama#progress + * @property {object} progress - The progress object containing loaded and total amount + */ + this.dispatchEvent( { type: 'progress', progress: progress } ); + + }, + + /** + * This will be called when panorama loading has error + * @memberOf Panorama + * @instance + * @fires Panorama#error + */ + onError: function () { + + /** + * Loading panorama error event + * @type {object} + * @event Panorama#error + */ + this.dispatchEvent( { type: 'error' } ); + + }, + + /** + * Get zoom level based on window width + * @memberOf Panorama + * @instance + * @return {number} zoom level indicating image quality + */ + getZoomLevel: function () { + + let zoomLevel; + + if ( window.innerWidth <= 800 ) { + + zoomLevel = this.ImageQualityFair; + + } else if ( window.innerWidth > 800 && window.innerWidth <= 1280 ) { + + zoomLevel = this.ImageQualityMedium; + + } else if ( window.innerWidth > 1280 && window.innerWidth <= 1920 ) { + + zoomLevel = this.ImageQualityHigh; + + } else if ( window.innerWidth > 1920 ) { + + zoomLevel = this.ImageQualitySuperHigh; + + } else { + + zoomLevel = this.ImageQualityLow; + + } + + return zoomLevel; + + }, + + /** + * Update texture of a panorama + * @memberOf Panorama + * @instance + * @param {THREE.Texture} texture - Texture to be updated + */ + updateTexture: function ( texture ) { + + this.material.map = texture; + this.material.needsUpdate = true; + + }, + + /** + * Toggle visibility of infospots in this panorama + * @param {boolean} isVisible - Visibility of infospots + * @param {number} delay - Delay in milliseconds to change visibility + * @memberOf Panorama + * @instance + * @fires Panorama#infospot-animation-complete + */ + toggleInfospotVisibility: function ( isVisible, delay ) { + + delay = ( delay !== undefined ) ? delay : 0; + + const visible = ( isVisible !== undefined ) ? isVisible : ( this.isInfospotVisible ? false : true ); + + this.traverse( function ( object ) { + + if ( object instanceof Infospot ) { + + if ( visible ) { + + object.show( delay ); + + } else { + + object.hide( delay ); + + } + + } + + } ); + + this.isInfospotVisible = visible; + + // Animation complete event + this.infospotAnimation.onComplete( function () { + + /** + * Complete toggling infospot visibility + * @event Panorama#infospot-animation-complete + * @type {object} + */ + this.dispatchEvent( { type: 'infospot-animation-complete', visible: visible } ); + + }.bind( this ) ).delay( delay ).start(); + + }, + + /** + * Set image of this panorama's linking infospot + * @memberOf Panorama + * @instance + * @param {string} url - Url to the image asset + * @param {number} scale - Scale factor of the infospot + */ + setLinkingImage: function ( url, scale ) { + + this.linkingImageURL = url; + this.linkingImageScale = scale; + + }, + + /** + * Link one-way panorama + * @param {Panorama} pano - The panorama to be linked to + * @param {THREE.Vector3} position - The position of infospot which navigates to the pano + * @param {number} [imageScale=300] - Image scale of linked infospot + * @param {string} [imageSrc=DataImage.Arrow] - The image source of linked infospot + * @memberOf Panorama + * @instance + */ + link: function ( pano, position, imageScale, imageSrc ) { + + let scale, img; + + this.visible = true; + + if ( !position ) { + + console.warn( 'Please specify infospot position for linking' ); + + return; + + } + + // Infospot scale + if ( imageScale !== undefined ) { + + scale = imageScale; + + } else if ( pano.linkingImageScale !== undefined ) { + + scale = pano.linkingImageScale; + + } else { + + scale = 300; + + } + + + // Infospot image + if ( imageSrc ) { + + img = imageSrc; + + } else if ( pano.linkingImageURL ) { + + img = pano.linkingImageURL; + + } else { + + img = DataImage.Arrow; + + } + + // Creates a new infospot + const spot = new Infospot( scale, img ); + spot.position.copy( position ); + spot.toPanorama = pano; + spot.addEventListener( 'click', function () { + + /** + * Viewer handler event + * @type {object} + * @event Panorama#panolens-viewer-handler + * @property {string} method - Viewer function name + * @property {*} data - The argument to be passed into the method + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'setPanorama', data: pano } ); + + }.bind( this ) ); + + this.linkedSpots.push( spot ); + + this.add( spot ); + + this.visible = false; + + }, + + reset: function () { + + this.children.length = 0; + + }, + + setupTransitions: function () { + + this.fadeInAnimation = new Tween.Tween( this.material ) + .easing( Tween.Easing.Quartic.Out ) + .onStart( function () { + + this.visible = true; + // this.material.visible = true; + + /** + * Enter panorama fade in start event + * @event Panorama#enter-fade-start + * @type {object} + */ + this.dispatchEvent( { type: 'enter-fade-start' } ); + + }.bind( this ) ); + + this.fadeOutAnimation = new Tween.Tween( this.material ) + .easing( Tween.Easing.Quartic.Out ) + .onComplete( function () { + + this.visible = false; + // this.material.visible = true; + + /** + * Leave panorama complete event + * @event Panorama#leave-complete + * @type {object} + */ + this.dispatchEvent( { type: 'leave-complete' } ); + + }.bind( this ) ); + + this.enterTransition = new Tween.Tween( this ) + .easing( Tween.Easing.Quartic.Out ) + .onComplete( function () { + + /** + * Enter panorama and animation complete event + * @event Panorama#enter-complete + * @type {object} + */ + this.dispatchEvent( { type: 'enter-complete' } ); + + }.bind ( this ) ) + .start(); + + this.leaveTransition = new Tween.Tween( this ) + .easing( Tween.Easing.Quartic.Out ); + + }, + + onFadeAnimationUpdate: function () { + + const alpha = this.material.opacity; + const { uniforms } = this.material; + + if ( uniforms && uniforms.opacity ) { + uniforms.opacity.value = alpha; + } + + }, + + /** + * Start fading in animation + * @memberOf Panorama + * @instance + * @fires Panorama#enter-fade-complete + */ + fadeIn: function ( duration ) { + + duration = duration >= 0 ? duration : this.animationDuration; + + this.fadeOutAnimation.stop(); + this.fadeInAnimation + .to( { opacity: 1 }, duration ) + .onUpdate( this.onFadeAnimationUpdate.bind( this ) ) + .onComplete( function () { + + this.toggleInfospotVisibility( true, duration / 2 ); + + /** + * Enter panorama fade complete event + * @event Panorama#enter-fade-complete + * @type {object} + */ + this.dispatchEvent( { type: 'enter-fade-complete' } ); + + }.bind( this ) ) + .start(); + + }, + + /** + * Start fading out animation + * @memberOf Panorama + * @instance + */ + fadeOut: function ( duration ) { + + duration = duration >= 0 ? duration : this.animationDuration; + + this.fadeInAnimation.stop(); + this.fadeOutAnimation + .to( { opacity: 0 }, duration ) + .onUpdate( this.onFadeAnimationUpdate.bind( this ) ) + .start(); + + }, + + /** + * This will be called when entering a panorama + * @memberOf Panorama + * @instance + * @fires Panorama#enter + * @fires Panorama#enter-start + */ + onEnter: function () { + + const duration = this.animationDuration; + + this.leaveTransition.stop(); + this.enterTransition + .to( {}, duration ) + .onStart( function () { + + /** + * Enter panorama and animation starting event + * @event Panorama#enter-start + * @type {object} + */ + this.dispatchEvent( { type: 'enter-start' } ); + + if ( this.loaded ) { + + this.fadeIn( duration ); + + } else { + + this.load(); + + } + + }.bind( this ) ) + .start(); + + /** + * Enter panorama event + * @event Panorama#enter + * @type {object} + */ + this.dispatchEvent( { type: 'enter' } ); + + this.children.forEach( child => { + + child.dispatchEvent( { type: 'panorama-enter' } ); + + } ); + + this.active = true; + + }, + + /** + * This will be called when leaving a panorama + * @memberOf Panorama + * @instance + * @fires Panorama#leave + */ + onLeave: function () { + + const duration = this.animationDuration; + + this.enterTransition.stop(); + this.leaveTransition + .to( {}, duration ) + .onStart( function () { + + /** + * Leave panorama and animation starting event + * @event Panorama#leave-start + * @type {object} + */ + this.dispatchEvent( { type: 'leave-start' } ); + + this.fadeOut( duration ); + this.toggleInfospotVisibility( false ); + + }.bind( this ) ) + .start(); + + /** + * Leave panorama event + * @event Panorama#leave + * @type {object} + */ + this.dispatchEvent( { type: 'leave' } ); + + this.children.forEach( child => { + + child.dispatchEvent( { type: 'panorama-leave' } ); + + } ); + + this.active = false; + + }, + + /** + * Dispose panorama + * @memberOf Panorama + * @instance + */ + dispose: function () { + + this.infospotAnimation.stop(); + this.fadeInAnimation.stop(); + this.fadeOutAnimation.stop(); + this.enterTransition.stop(); + this.leaveTransition.stop(); + + /** + * On panorama dispose handler + * @type {object} + * @event Panorama#panolens-viewer-handler + * @property {string} method - Viewer function name + * @property {*} data - The argument to be passed into the method + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onPanoramaDispose', data: this } ); + + // recursive disposal on 3d objects + function recursiveDispose ( object ) { + + const { geometry, material } = object; + + for ( var i = object.children.length - 1; i >= 0; i-- ) { + + recursiveDispose( object.children[i] ); + object.remove( object.children[i] ); + + } + + if ( object instanceof Infospot ) { + + object.dispose(); + + } + + if ( geometry ) { geometry.dispose(); object.geometry = null; } + if ( material ) { material.dispose(); object.material = null; } + + } + + recursiveDispose( this ); + + if ( this.parent ) { + + this.parent.remove( this ); + + } + + } + +} ); + +/** + * @classdesc Equirectangular based image panorama + * @constructor + * @param {string} image - Image url or HTMLImageElement + */ +function ImagePanorama ( image, _geometry, _material ) { + + const radius = 5000; + const geometry = _geometry || new SphereBufferGeometry( radius, 60, 40 ); + const material = _material || new MeshBasicMaterial( { opacity: 0, transparent: true } ); + + Panorama.call( this, geometry, material ); + + this.src = image; + this.radius = radius; + +} + +ImagePanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { + + constructor: ImagePanorama, + + /** + * Load image asset + * @param {*} src - Url or image element + * @memberOf ImagePanorama + * @instance + */ + load: function ( src ) { + + src = src || this.src; + + if ( !src ) { + + console.warn( 'Image source undefined' ); + + return; + + } else if ( typeof src === 'string' ) { + + TextureLoader.load( src, this.onLoad.bind( this ), this.onProgress.bind( this ), this.onError.bind( this ) ); + + } else if ( src instanceof HTMLImageElement ) { + + this.onLoad( new Texture( src ) ); + + } + + }, + + /** + * This will be called when image is loaded + * @param {THREE.Texture} texture - Texture to be updated + * @memberOf ImagePanorama + * @instance + */ + onLoad: function ( texture ) { + + texture.minFilter = texture.magFilter = LinearFilter; + texture.needsUpdate = true; + + this.updateTexture( texture ); + + window.requestAnimationFrame( Panorama.prototype.onLoad.bind( this ) ); + + }, + + /** + * Reset + * @memberOf ImagePanorama + * @instance + */ + reset: function () { + + Panorama.prototype.reset.call( this ); + + }, + + /** + * Dispose + * @memberOf ImagePanorama + * @instance + */ + dispose: function () { + + const { material: { map } } = this; + + // Release cached image + Cache.remove( this.src ); + + if ( map ) { map.dispose(); } + + Panorama.prototype.dispose.call( this ); + + } + +} ); + +/** + * @classdesc Empty panorama + * @constructor + */ +function EmptyPanorama () { + + const geometry = new BufferGeometry(); + const material = new MeshBasicMaterial( { color: 0x000000, opacity: 0, transparent: true } ); + + geometry.addAttribute( 'position', new BufferAttribute( new Float32Array(), 1 ) ); + + Panorama.call( this, geometry, material ); + +} + +EmptyPanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { + + constructor: EmptyPanorama + +} ); + +/** + * @classdesc Cubemap-based panorama + * @constructor + * @param {array} images - Array of 6 urls to images, one for each side of the CubeTexture. The urls should be specified in the following order: pos-x, neg-x, pos-y, neg-y, pos-z, neg-z + */ +function CubePanorama ( images = [] ){ + + const edgeLength = 10000; + const shader = Object.assign( {}, ShaderLib[ 'cube' ] ); + const geometry = new BoxBufferGeometry( edgeLength, edgeLength, edgeLength ); + const material = new ShaderMaterial( { + + fragmentShader: shader.fragmentShader, + vertexShader: shader.vertexShader, + uniforms: shader.uniforms, + side: BackSide, + transparent: true + + } ); + + Panorama.call( this, geometry, material ); + + this.images = images; + this.edgeLength = edgeLength; + this.material.uniforms.opacity.value = 0; + +} + +CubePanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { + + constructor: CubePanorama, + + /** + * Load 6 images and bind listeners + * @memberOf CubePanorama + * @instance + */ + load: function () { + + CubeTextureLoader.load( + + this.images, + + this.onLoad.bind( this ), + this.onProgress.bind( this ), + this.onError.bind( this ) + + ); + + }, + + /** + * This will be called when 6 textures are ready + * @param {THREE.CubeTexture} texture - Cube texture + * @memberOf CubePanorama + * @instance + */ + onLoad: function ( texture ) { + + this.material.uniforms[ 'tCube' ].value = texture; + + Panorama.prototype.onLoad.call( this ); + + }, + + /** + * Dispose + * @memberOf CubePanorama + * @instance + */ + dispose: function () { + + const { value } = this.material.uniforms.tCube; + + this.images.forEach( ( image ) => { Cache.remove( image ); } ); + + if ( value instanceof CubeTexture ) { + + value.dispose(); + + } + + Panorama.prototype.dispose.call( this ); + + } + +} ); + +/** + * @classdesc Basic panorama with 6 pre-defined grid images + * @constructor + */ +function BasicPanorama () { + + const images = []; + + for ( let i = 0; i < 6; i++ ) { + + images.push( DataImage.WhiteTile ); + + } + + CubePanorama.call( this, images ); + +} + +BasicPanorama.prototype = Object.assign( Object.create( CubePanorama.prototype ), { + + constructor: BasicPanorama + +} ); + +/** + * @classdesc Video Panorama + * @constructor + * @param {string} src - Equirectangular video url + * @param {object} [options] - Option for video settings + * @param {HTMLElement} [options.videoElement] - HTML5 video element contains the video + * @param {boolean} [options.loop=true] - Specify if the video should loop in the end + * @param {boolean} [options.muted=true] - Mute the video or not. Need to be true in order to autoplay on some browsers + * @param {boolean} [options.autoplay=false] - Specify if the video should auto play + * @param {boolean} [options.playsinline=true] - Specify if video should play inline for iOS. If you want it to auto play inline, set both autoplay and muted options to true + * @param {string} [options.crossOrigin="anonymous"] - Sets the cross-origin attribute for the video, which allows for cross-origin videos in some browsers (Firefox, Chrome). Set to either "anonymous" or "use-credentials". + * @param {number} [radius=5000] - The minimum radius for this panoram + */ +function VideoPanorama ( src, options = {} ) { + + const radius = 5000; + const geometry = new SphereBufferGeometry( radius, 60, 40 ); + const material = new MeshBasicMaterial( { opacity: 0, transparent: true } ); + + Panorama.call( this, geometry, material ); + + this.src = src; + + this.options = { + + videoElement: document.createElement( 'video' ), + loop: true, + muted: true, + autoplay: false, + playsinline: true, + crossOrigin: 'anonymous' + + }; + + Object.assign( this.options, options ); + + this.videoElement = this.options.videoElement; + this.videoProgress = 0; + this.radius = radius; + + this.addEventListener( 'leave', this.pauseVideo.bind( this ) ); + this.addEventListener( 'enter-fade-start', this.resumeVideoProgress.bind( this ) ); + this.addEventListener( 'video-toggle', this.toggleVideo.bind( this ) ); + this.addEventListener( 'video-time', this.setVideoCurrentTime.bind( this ) ); + +} +VideoPanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { + + constructor: VideoPanorama, + + isMobile: function () { + + let check = false; + (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})( window.navigator.userAgent || window.navigator.vendor || window.opera ); + return check; + + }, + + /** + * Load video panorama + * @memberOf VideoPanorama + * @instance + * @fires Panorama#panolens-viewer-handler + */ + load: function () { + + const { muted, loop, autoplay, playsinline, crossOrigin } = this.options; + const video = this.videoElement; + const material = this.material; + const onProgress = this.onProgress.bind( this ); + const onLoad = this.onLoad.bind( this ); + + video.loop = loop; + video.autoplay = autoplay; + video.playsinline = playsinline; + video.crossOrigin = crossOrigin; + video.muted = muted; + + if ( playsinline ) { + + video.setAttribute( 'playsinline', '' ); + video.setAttribute( 'webkit-playsinline', '' ); + + } + + const onloadeddata = function() { + + this.setVideoTexture( video ); + + if ( autoplay ) { + + /** + * Viewer handler event + * @type {object} + * @property {string} method - 'updateVideoPlayButton' + * @property {boolean} data - Pause video or not + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: false } ); + + } + + // For mobile silent autoplay + if ( this.isMobile() ) { + + video.pause(); + + if ( autoplay && muted ) { + + /** + * Viewer handler event + * @type {object} + * @property {string} method - 'updateVideoPlayButton' + * @property {boolean} data - Pause video or not + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: false } ); + + } else { + + /** + * Viewer handler event + * @type {object} + * @property {string} method - 'updateVideoPlayButton' + * @property {boolean} data - Pause video or not + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: true } ); + + } + + } + + const loaded = () => { + + // Fix for threejs r89 delayed update + material.map.needsUpdate = true; + + onProgress( { loaded: 1, total: 1 } ); + onLoad(); + + }; + + window.requestAnimationFrame( loaded ); + + }; + + /** + * Ready state of the audio/video element + * 0 = HAVE_NOTHING - no information whether or not the audio/video is ready + * 1 = HAVE_METADATA - metadata for the audio/video is ready + * 2 = HAVE_CURRENT_DATA - data for the current playback position is available, but not enough data to play next frame/millisecond + * 3 = HAVE_FUTURE_DATA - data for the current and at least the next frame is available + * 4 = HAVE_ENOUGH_DATA - enough data available to start playing + */ + if ( video.readyState > 2 ) { + + onloadeddata.call( this ); + + } else { + + if ( video.querySelectorAll( 'source' ).length === 0 ) { + + const source = document.createElement( 'source' ); + source.src = this.src; + video.appendChild( source ); + + } + + video.load(); + } + + video.addEventListener( 'loadeddata', onloadeddata.bind( this ) ); + + video.addEventListener( 'timeupdate', function () { + + this.videoProgress = video.duration >= 0 ? video.currentTime / video.duration : 0; + + /** + * Viewer handler event + * @type {object} + * @property {string} method - 'onVideoUpdate' + * @property {number} data - The percentage of video progress. Range from 0.0 to 1.0 + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onVideoUpdate', data: this.videoProgress } ); + + }.bind( this ) ); + + video.addEventListener( 'ended', function () { + + if ( !loop ) { + + this.resetVideo(); + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: true } ); + + } + + }.bind( this ), false ); + + }, + + /** + * Set video texture + * @memberOf VideoPanorama + * @instance + * @param {HTMLVideoElement} video - The html5 video element + * @fires Panorama#panolens-viewer-handler + */ + setVideoTexture: function ( video ) { + + if ( !video ) return; + + const videoTexture = new VideoTexture( video ); + videoTexture.minFilter = LinearFilter; + videoTexture.magFilter = LinearFilter; + videoTexture.format = RGBFormat; + + this.updateTexture( videoTexture ); + + }, + + /** + * Reset + * @memberOf VideoPanorama + * @instance + */ + reset: function () { + + this.videoElement = undefined; + + Panorama.prototype.reset.call( this ); + + }, + + /** + * Check if video is paused + * @memberOf VideoPanorama + * @instance + * @return {boolean} - is video paused or not + */ + isVideoPaused: function () { + + return this.videoElement.paused; + + }, + + /** + * Toggle video to play or pause + * @memberOf VideoPanorama + * @instance + */ + toggleVideo: function () { + + const video = this.videoElement; + + if ( !video ) { return; } + + video[ video.paused ? 'play' : 'pause' ](); + + }, + + /** + * Set video currentTime + * @memberOf VideoPanorama + * @instance + * @param {object} event - Event contains percentage. Range from 0.0 to 1.0 + */ + setVideoCurrentTime: function ( { percentage } ) { + + const video = this.videoElement; + + if ( video && !Number.isNaN( percentage ) && percentage !== 1 ) { + + video.currentTime = video.duration * percentage; + + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onVideoUpdate', data: percentage } ); + + } + + }, + + /** + * Play video + * @memberOf VideoPanorama + * @instance + * @fires VideoPanorama#play + * @fires VideoPanorama#play-error + */ + playVideo: function () { + + const video = this.videoElement; + const playVideo = this.playVideo.bind( this ); + const dispatchEvent = this.dispatchEvent.bind( this ); + const onSuccess = () => { + + /** + * Play event + * @type {object} + * @event VideoPanorama#play + * + */ + dispatchEvent( { type: 'play' } ); + + }; + const onError = ( error ) => { + + // Error playing video. Retry next frame. Possibly Waiting for user interaction + window.requestAnimationFrame( playVideo ); + + /** + * Play event + * @type {object} + * @event VideoPanorama#play-error + * + */ + dispatchEvent( { type: 'play-error', error } ); + + }; + + if ( video && video.paused ) { + + video.play().then( onSuccess ).catch( onError ); + + } + + }, + + /** + * Pause video + * @memberOf VideoPanorama + * @instance + * @fires VideoPanorama#pause + */ + pauseVideo: function () { + + const video = this.videoElement; + + if ( video && !video.paused ) { + + video.pause(); + + } + + /** + * Pause event + * @type {object} + * @event VideoPanorama#pause + * + */ + this.dispatchEvent( { type: 'pause' } ); + + }, + + /** + * Resume video + * @memberOf VideoPanorama + * @instance + */ + resumeVideoProgress: function () { + + const video = this.videoElement; + + if ( video.readyState >= 4 && video.autoplay && !this.isMobile() ) { + + this.playVideo(); + + /** + * Viewer handler event + * @type {object} + * @property {string} method - 'updateVideoPlayButton' + * @property {boolean} data - Pause video or not + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: false } ); + + } else { + + this.pauseVideo(); + + /** + * Viewer handler event + * @type {object} + * @property {string} method - 'updateVideoPlayButton' + * @property {boolean} data - Pause video or not + */ + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: true } ); + + } + + this.setVideoCurrentTime( { percentage: this.videoProgress } ); + + }, + + /** + * Reset video at stating point + * @memberOf VideoPanorama + * @instance + */ + resetVideo: function () { + + const video = this.videoElement; + + if ( video ) { + + this.setVideoCurrentTime( { percentage: 0 } ); + + } + + }, + + /** + * Check if video is muted + * @memberOf VideoPanorama + * @instance + * @return {boolean} - is video muted or not + */ + isVideoMuted: function () { + + return this.videoElement.muted; + + }, + + /** + * Mute video + * @memberOf VideoPanorama + * @instance + */ + muteVideo: function () { + + const video = this.videoElement; + + if ( video && !video.muted ) { + + video.muted = true; + + } + + this.dispatchEvent( { type: 'volumechange' } ); + + }, + + /** + * Unmute video + * @memberOf VideoPanorama + * @instance + */ + unmuteVideo: function () { + + const video = this.videoElement; + + if ( video && this.isVideoMuted() ) { + + video.muted = false; + + } + + this.dispatchEvent( { type: 'volumechange' } ); + + }, + + /** + * Returns the video element + * @memberOf VideoPanorama + * @instance + * @returns {HTMLElement} + */ + getVideoElement: function () { + + return this.videoElement; + + }, + + /** + * Dispose video panorama + * @memberOf VideoPanorama + * @instance + */ + dispose: function () { + + const { material: { map } } = this; + + this.pauseVideo(); + + this.removeEventListener( 'leave', this.pauseVideo.bind( this ) ); + this.removeEventListener( 'enter-fade-start', this.resumeVideoProgress.bind( this ) ); + this.removeEventListener( 'video-toggle', this.toggleVideo.bind( this ) ); + this.removeEventListener( 'video-time', this.setVideoCurrentTime.bind( this ) ); + + if ( map ) { map.dispose(); } + + Panorama.prototype.dispose.call( this ); + + } + +} ); + +/** + * @classdesc Google Street View Loader + * @constructor + * @param {object} parameters + */ +function GoogleStreetviewLoader ( parameters = {} ) { + + this._parameters = parameters; + this._zoom = null; + this._panoId = null; + this._panoClient = new google.maps.StreetViewService(); + this._count = 0; + this._total = 0; + this._canvas = []; + this._ctx = []; + this._wc = 0; + this._hc = 0; + this.result = null; + this.rotation = 0; + this.copyright = ''; + this.onSizeChange = null; + this.onPanoramaLoad = null; + + this.levelsW = [ 1, 2, 4, 7, 13, 26 ]; + this.levelsH = [ 1, 1, 2, 4, 7, 13 ]; + + this.widths = [ 416, 832, 1664, 3328, 6656, 13312 ]; + this.heights = [ 416, 416, 832, 1664, 3328, 6656 ]; + + this.maxW = 6656; + this.maxH = 6656; + + let gl; + + try { + + const canvas = document.createElement( 'canvas' ); + + gl = canvas.getContext( 'experimental-webgl' ); + + if( !gl ) { + + gl = canvas.getContext( 'webgl' ); + + } + + } + catch ( error ) { + + } + + this.maxW = Math.max( gl.getParameter( gl.MAX_TEXTURE_SIZE ), this.maxW ); + this.maxH = Math.max( gl.getParameter( gl.MAX_TEXTURE_SIZE ), this.maxH ); + +} + +Object.assign( GoogleStreetviewLoader.prototype, { + + constructor: GoogleStreetviewLoader, + + /** + * Set progress + * @param {number} loaded + * @param {number} total + * @memberOf GoogleStreetviewLoader + * @instance + */ + setProgress: function ( loaded, total ) { + + if ( this.onProgress ) { + + this.onProgress( { loaded: loaded, total: total } ); + + } + + }, + + /** + * Adapt texture to zoom + * @memberOf GoogleStreetviewLoader + * @instance + */ + adaptTextureToZoom: function () { + + const w = this.widths [ this._zoom ]; + const h = this.heights[ this._zoom ]; + + const maxW = this.maxW; + const maxH = this.maxH; + + this._wc = Math.ceil( w / maxW ); + this._hc = Math.ceil( h / maxH ); + + for( let y = 0; y < this._hc; y++ ) { + for( let x = 0; x < this._wc; x++ ) { + const c = document.createElement( 'canvas' ); + if( x < ( this._wc - 1 ) ) c.width = maxW; else c.width = w - ( maxW * x ); + if( y < ( this._hc - 1 ) ) c.height = maxH; else c.height = h - ( maxH * y ); + this._canvas.push( c ); + this._ctx.push( c.getContext( '2d' ) ); + } + } + + }, + + /** + * Compose from tile + * @param {number} x + * @param {number} y + * @param {*} texture + * @memberOf GoogleStreetviewLoader + * @instance + */ + composeFromTile: function ( x, y, texture ) { + + const maxW = this.maxW; + const maxH = this.maxH; + + x *= 512; + y *= 512; + + const px = Math.floor( x / maxW ); + const py = Math.floor( y / maxH ); + + x -= px * maxW; + y -= py * maxH; + + this._ctx[ py * this._wc + px ].drawImage( texture, 0, 0, texture.width, texture.height, x, y, 512, 512 ); + + this.progress(); + + }, + + /** + * Progress + * @memberOf GoogleStreetviewLoader + * @instance + */ + progress: function() { + + this._count++; + + this.setProgress( this._count, this._total ); + + if ( this._count === this._total) { + + this.canvas = this._canvas; + this.panoId = this._panoId; + this.zoom = this._zoom; + + if ( this.onPanoramaLoad ) { + + this.onPanoramaLoad( this._canvas[ 0 ] ); + + } + + } + }, + + /** + * Compose panorama + * @memberOf GoogleStreetviewLoader + * @instance + */ + composePanorama: function () { + + this.setProgress( 0, 1 ); + + const w = this.levelsW[ this._zoom ]; + const h = this.levelsH[ this._zoom ]; + const self = this; + + this._count = 0; + this._total = w * h; + + const { useWebGL } = this._parameters; + + for( let y = 0; y < h; y++ ) { + for( let x = 0; x < w; x++ ) { + const url = 'https://geo0.ggpht.com/cbk?cb_client=maps_sv.tactile&authuser=0&hl=en&output=tile&zoom=' + this._zoom + '&x=' + x + '&y=' + y + '&panoid=' + this._panoId + '&nbt&fover=2'; + ( function( x, y ) { + if( useWebGL ) { + const texture = TextureLoader.load( url, null, function() { + self.composeFromTile( x, y, texture ); + } ); + } else { + const img = new Image(); + img.addEventListener( 'load', function() { + self.composeFromTile( x, y, this ); + } ); + img.crossOrigin = ''; + img.src = url; + } + } )( x, y ); + } + } + + }, + + /** + * Load + * @param {string} panoid + * @memberOf GoogleStreetviewLoader + * @instance + */ + load: function ( panoid ) { + + this.loadPano( panoid ); + + }, + + /** + * Load panorama + * @param {string} id + * @memberOf GoogleStreetviewLoader + * @instance + */ + loadPano: function( id ) { + + const self = this; + this._panoClient.getPanoramaById( id, function (result, status) { + if (status === google.maps.StreetViewStatus.OK) { + self.result = result; + self.copyright = result.copyright; + self._panoId = result.location.pano; + self.composePanorama(); + } + }); + + }, + + /** + * Set zoom level + * @param {number} z + * @memberOf GoogleStreetviewLoader + * @instance + */ + setZoom: function( z ) { + + this._zoom = z; + this.adaptTextureToZoom(); + } + +} ); + +/** + * @classdesc Google streetview panorama + * @description [How to get Panorama ID]{@link http://stackoverflow.com/questions/29916149/google-maps-streetview-how-to-get-panorama-id} + * @constructor + * @param {string} panoId - Panorama id from Google Streetview + * @param {string} [apiKey] - Google Street View API Key + */ +function GoogleStreetviewPanorama ( panoId, apiKey ) { + + ImagePanorama.call( this ); + + this.panoId = panoId; + + this.gsvLoader = null; + + this.loadRequested = false; + + this.setupGoogleMapAPI( apiKey ); + +} + +GoogleStreetviewPanorama.prototype = Object.assign( Object.create( ImagePanorama.prototype ), { + + constructor: GoogleStreetviewPanorama, + + /** + * Load Google Street View by panorama id + * @param {string} panoId - Gogogle Street View panorama id + * @memberOf GoogleStreetviewPanorama + * @instance + */ + load: function ( panoId ) { + + this.loadRequested = true; + + panoId = ( panoId || this.panoId ) || {}; + + if ( panoId && this.gsvLoader ) { + + this.loadGSVLoader( panoId ); + + } + + }, + + /** + * Setup Google Map API + * @param {string} apiKey + * @memberOf GoogleStreetviewPanorama + * @instance + */ + setupGoogleMapAPI: function ( apiKey ) { + + const script = document.createElement( 'script' ); + script.src = 'https://maps.googleapis.com/maps/api/js?'; + script.src += apiKey ? 'key=' + apiKey : ''; + script.onreadystatechange = this.setGSVLoader.bind( this ); + script.onload = this.setGSVLoader.bind( this ); + + document.querySelector( 'head' ).appendChild( script ); + + }, + + /** + * Set GSV Loader + * @memberOf GoogleStreetviewPanorama + * @instance + */ + setGSVLoader: function () { + + this.gsvLoader = new GoogleStreetviewLoader(); + + if ( this.loadRequested ) { + + this.load(); + + } + + }, + + /** + * Get GSV Loader + * @memberOf GoogleStreetviewPanorama + * @instance + * @return {GoogleStreetviewLoader} GSV Loader instance + */ + getGSVLoader: function () { + + return this.gsvLoader; + + }, + + /** + * Load GSV Loader + * @param {string} panoId - Gogogle Street View panorama id + * @memberOf GoogleStreetviewPanorama + * @instance + */ + loadGSVLoader: function ( panoId ) { + + this.loadRequested = false; + + this.gsvLoader.onProgress = this.onProgress.bind( this ); + + this.gsvLoader.onPanoramaLoad = this.onLoad.bind( this ); + + this.gsvLoader.setZoom( this.getZoomLevel() ); + + this.gsvLoader.load( panoId ); + + this.gsvLoader.loaded = true; + }, + + /** + * This will be called when panorama is loaded + * @param {HTMLCanvasElement} canvas - Canvas where the tiles have been drawn + * @memberOf GoogleStreetviewPanorama + * @instance + */ + onLoad: function ( canvas ) { + + ImagePanorama.prototype.onLoad.call( this, new Texture( canvas ) ); + + }, + + /** + * Reset + * @memberOf GoogleStreetviewPanorama + * @instance + */ + reset: function () { + + this.gsvLoader = undefined; + + ImagePanorama.prototype.reset.call( this ); + + } + +} ); + +/** + * Stereographic projection shader + * based on http://notlion.github.io/streetview-stereographic + * @author pchen66 + */ + +/** + * @description Stereograhpic Shader + * @module StereographicShader + * @property {object} uniforms + * @property {THREE.Texture} uniforms.tDiffuse diffuse map + * @property {number} uniforms.resolution image resolution + * @property {THREE.Matrix4} uniforms.transform transformation matrix + * @property {number} uniforms.zoom image zoom factor + * @property {number} uniforms.opacity image opacity + * @property {string} vertexShader vertex shader + * @property {string} fragmentShader fragment shader + */ +const StereographicShader = { + + uniforms: { + + 'tDiffuse': { value: new Texture() }, + 'resolution': { value: 1.0 }, + 'transform': { value: new Matrix4() }, + 'zoom': { value: 1.0 }, + 'opacity': { value: 1.0 } + + }, + + vertexShader: [ + + 'varying vec2 vUv;', + + 'void main() {', + + 'vUv = uv;', + 'gl_Position = vec4( position, 1.0 );', + + '}' + + ].join( '\n' ), + + fragmentShader: [ + + 'uniform sampler2D tDiffuse;', + 'uniform float resolution;', + 'uniform mat4 transform;', + 'uniform float zoom;', + 'uniform float opacity;', + + 'varying vec2 vUv;', + + 'const float PI = 3.141592653589793;', + + 'void main(){', + + 'vec2 position = -1.0 + 2.0 * vUv;', + + 'position *= vec2( zoom * resolution, zoom * 0.5 );', + + 'float x2y2 = position.x * position.x + position.y * position.y;', + 'vec3 sphere_pnt = vec3( 2. * position, x2y2 - 1. ) / ( x2y2 + 1. );', + + 'sphere_pnt = vec3( transform * vec4( sphere_pnt, 1.0 ) );', + + 'vec2 sampleUV = vec2(', + '(atan(sphere_pnt.y, sphere_pnt.x) / PI + 1.0) * 0.5,', + '(asin(sphere_pnt.z) / PI + 0.5)', + ');', + + 'gl_FragColor = texture2D( tDiffuse, sampleUV );', + + 'gl_FragColor.a *= opacity;', + + '}' + + ].join( '\n' ) + +}; + +/** + * @classdesc Little Planet + * @constructor + * @param {string} type - Type of little planet basic class + * @param {string} source - URL for the image source + * @param {number} [size=10000] - Size of plane geometry + * @param {number} [ratio=0.5] - Ratio of plane geometry's height against width + */ +function LittlePlanet ( type = 'image', source, size = 10000, ratio = 0.5 ) { + + if ( type === 'image' ) { + + ImagePanorama.call( this, source, this.createGeometry( size, ratio ), this.createMaterial( size ) ); + + } + + this.size = size; + this.ratio = ratio; + this.EPS = 0.000001; + this.frameId = null; + + this.dragging = false; + this.userMouse = new Vector2(); + + this.quatA = new Quaternion(); + this.quatB = new Quaternion(); + this.quatCur = new Quaternion(); + this.quatSlerp = new Quaternion(); + + this.vectorX = new Vector3( 1, 0, 0 ); + this.vectorY = new Vector3( 0, 1, 0 ); + + this.addEventListener( 'window-resize', this.onWindowResize ); + +} + +LittlePlanet.prototype = Object.assign( Object.create( ImagePanorama.prototype ), { + + constructor: LittlePlanet, + + add: function ( object ) { + + if ( arguments.length > 1 ) { + + for ( let i = 0; i < arguments.length; i ++ ) { + + this.add( arguments[ i ] ); + + } + + return this; + + } + + if ( object instanceof Infospot ) { + + object.material.depthTest = false; + + } + + ImagePanorama.prototype.add.call( this, object ); + + }, + + createGeometry: function ( size, ratio ) { + + return new PlaneBufferGeometry( size, size * ratio ); + + }, + + createMaterial: function ( size ) { + + const shader = Object.assign( {}, StereographicShader ), uniforms = shader.uniforms; + + uniforms.zoom.value = size; + uniforms.opacity.value = 0.0; + + return new ShaderMaterial( { + + uniforms: uniforms, + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader, + side: BackSide, + transparent: true + + } ); + + }, + + registerMouseEvents: function () { + + this.container.addEventListener( 'mousedown', this.onMouseDown.bind( this ), { passive: true } ); + this.container.addEventListener( 'mousemove', this.onMouseMove.bind( this ), { passive: true } ); + this.container.addEventListener( 'mouseup', this.onMouseUp.bind( this ), { passive: true } ); + this.container.addEventListener( 'touchstart', this.onMouseDown.bind( this ), { passive: true } ); + this.container.addEventListener( 'touchmove', this.onMouseMove.bind( this ), { passive: true } ); + this.container.addEventListener( 'touchend', this.onMouseUp.bind( this ), { passive: true } ); + this.container.addEventListener( 'mousewheel', this.onMouseWheel.bind( this ), { passive: false } ); + this.container.addEventListener( 'DOMMouseScroll', this.onMouseWheel.bind( this ), { passive: false } ); + this.container.addEventListener( 'contextmenu', this.onContextMenu.bind( this ), { passive: true } ); + + }, + + unregisterMouseEvents: function () { + + this.container.removeEventListener( 'mousedown', this.onMouseDown.bind( this ), false ); + this.container.removeEventListener( 'mousemove', this.onMouseMove.bind( this ), false ); + this.container.removeEventListener( 'mouseup', this.onMouseUp.bind( this ), false ); + this.container.removeEventListener( 'touchstart', this.onMouseDown.bind( this ), false ); + this.container.removeEventListener( 'touchmove', this.onMouseMove.bind( this ), false ); + this.container.removeEventListener( 'touchend', this.onMouseUp.bind( this ), false ); + this.container.removeEventListener( 'mousewheel', this.onMouseWheel.bind( this ), false ); + this.container.removeEventListener( 'DOMMouseScroll', this.onMouseWheel.bind( this ), false ); + this.container.removeEventListener( 'contextmenu', this.onContextMenu.bind( this ), false ); + + }, + + onMouseDown: function ( event ) { + + const inputCount = ( event.touches && event.touches.length ) || 1 ; + + switch ( inputCount ) { + + case 1: + + const x = ( event.clientX >= 0 ) ? event.clientX : event.touches[ 0 ].clientX; + const y = ( event.clientY >= 0 ) ? event.clientY : event.touches[ 0 ].clientY; + + this.dragging = true; + this.userMouse.set( x, y ); + + break; + + case 2: + + const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + const distance = Math.sqrt( dx * dx + dy * dy ); + this.userMouse.pinchDistance = distance; + + break; + + default: + + break; + + } + + this.onUpdateCallback(); + + }, + + onMouseMove: function ( event ) { + + const inputCount = ( event.touches && event.touches.length ) || 1 ; + + switch ( inputCount ) { + + case 1: + + const x = ( event.clientX >= 0 ) ? event.clientX : event.touches[ 0 ].clientX; + const y = ( event.clientY >= 0 ) ? event.clientY : event.touches[ 0 ].clientY; + + const angleX = Math$1.degToRad( x - this.userMouse.x ) * 0.4; + const angleY = Math$1.degToRad( y - this.userMouse.y ) * 0.4; + + if ( this.dragging ) { + this.quatA.setFromAxisAngle( this.vectorY, angleX ); + this.quatB.setFromAxisAngle( this.vectorX, angleY ); + this.quatCur.multiply( this.quatA ).multiply( this.quatB ); + this.userMouse.set( x, y ); + } + + break; + + case 2: + + const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + const distance = Math.sqrt( dx * dx + dy * dy ); + + this.addZoomDelta( this.userMouse.pinchDistance - distance ); + + break; + + default: + + break; + + } + + }, + + onMouseUp: function () { + + this.dragging = false; + + }, + + onMouseWheel: function ( event ) { + + event.preventDefault(); + event.stopPropagation(); + + let delta = 0; + + if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 + + delta = event.wheelDelta; + + } else if ( event.detail !== undefined ) { // Firefox + + delta = - event.detail; + + } + + this.addZoomDelta( delta ); + this.onUpdateCallback(); + + }, + + addZoomDelta: function ( delta ) { + + const uniforms = this.material.uniforms; + const lowerBound = this.size * 0.1; + const upperBound = this.size * 10; + + uniforms.zoom.value += delta; + + if ( uniforms.zoom.value <= lowerBound ) { + + uniforms.zoom.value = lowerBound; + + } else if ( uniforms.zoom.value >= upperBound ) { + + uniforms.zoom.value = upperBound; + + } + + }, + + onUpdateCallback: function () { + + this.frameId = window.requestAnimationFrame( this.onUpdateCallback.bind( this ) ); + + this.quatSlerp.slerp( this.quatCur, 0.1 ); + + if ( this.material ) { + + this.material.uniforms.transform.value.makeRotationFromQuaternion( this.quatSlerp ); + + } + + if ( !this.dragging && 1.0 - this.quatSlerp.clone().dot( this.quatCur ) < this.EPS ) { + + window.cancelAnimationFrame( this.frameId ); + + } + + }, + + reset: function () { + + this.quatCur.set( 0, 0, 0, 1 ); + this.quatSlerp.set( 0, 0, 0, 1 ); + this.onUpdateCallback(); + + }, + + onLoad: function ( texture ) { + + this.material.uniforms.resolution.value = this.container.clientWidth / this.container.clientHeight; + + this.registerMouseEvents(); + this.onUpdateCallback(); + + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'disableControl' } ); + + ImagePanorama.prototype.onLoad.call( this, texture ); + + }, + + onLeave: function () { + + this.unregisterMouseEvents(); + + this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'enableControl', data: CONTROLS.ORBIT } ); + + window.cancelAnimationFrame( this.frameId ); + + ImagePanorama.prototype.onLeave.call( this ); + + }, + + onWindowResize: function () { + + this.material.uniforms.resolution.value = this.container.clientWidth / this.container.clientHeight; + + }, + + onContextMenu: function () { + + this.dragging = false; + + }, + + dispose: function () { + + this.unregisterMouseEvents(); + + ImagePanorama.prototype.dispose.call( this ); + + } + +}); + +/** + * @classdesc Image Little Planet + * @constructor + * @param {string} source - URL for the image source + * @param {number} [size=10000] - Size of plane geometry + * @param {number} [ratio=0.5] - Ratio of plane geometry's height against width + */ +function ImageLittlePlanet ( source, size, ratio ) { + + LittlePlanet.call( this, 'image', source, size, ratio ); + +} + +ImageLittlePlanet.prototype = Object.assign( Object.create( LittlePlanet.prototype ), { + + constructor: ImageLittlePlanet, + + /** + * On loaded with texture + * @param {THREE.Texture} texture + * @memberOf ImageLittlePlanet + * @instance + */ + onLoad: function ( texture ) { + + this.updateTexture( texture ); + + LittlePlanet.prototype.onLoad.call( this, texture ); + + }, + + /** + * Update texture + * @param {THREE.Texture} texture + * @memberOf ImageLittlePlanet + * @instance + */ + updateTexture: function ( texture ) { + + texture.minFilter = texture.magFilter = LinearFilter; + + this.material.uniforms[ 'tDiffuse' ].value = texture; + + }, + + /** + * Dispose + * @memberOf ImageLittlePlanet + * @instance + */ + dispose: function () { + + const tDiffuse = this.material.uniforms[ 'tDiffuse' ]; + + if ( tDiffuse && tDiffuse.value ) { + + tDiffuse.value.dispose(); + + } + + LittlePlanet.prototype.dispose.call( this ); + + } + +} ); + +/** + * @classdesc Camera panorama + * @description See {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints|MediaStreamConstraints} for constraints + * @param {object} - camera constraints + * @constructor + */ +function CameraPanorama ( constraints ) { + + const radius = 5000; + const geometry = new SphereBufferGeometry( radius, 60, 40 ); + const material = new MeshBasicMaterial( { visible: false }); + + Panorama.call( this, geometry, material ); + + this.media = new Media( constraints ); + this.radius = radius; + + this.addEventListener( 'enter', this.start.bind( this ) ); + this.addEventListener( 'leave', this.stop.bind( this ) ); + this.addEventListener( 'panolens-container', this.onPanolensContainer.bind( this ) ); + this.addEventListener( 'panolens-scene', this.onPanolensScene.bind( this ) ); + +} + +CameraPanorama.prototype = Object.assign( Object.create( Panorama.prototype ), { + + constructor: CameraPanorama, + + /** + * On container event + * @param {object} event + * @memberOf CameraPanorama + * @instance + */ + onPanolensContainer: function ( { container } ) { + + this.media.setContainer( container ); + + }, + + /** + * On scene event + * @param {object} event + * @memberOf CameraPanorama + * @instance + */ + onPanolensScene: function ( { scene } ) { + + this.media.setScene( scene ); + + }, + + /** + * Start camera streaming + * @memberOf CameraPanorama + * @instance + * @returns {Promise} + */ + start: function () { + + return this.media.start(); + + }, + + /** + * Stop camera streaming + * @memberOf CameraPanorama + * @instance + */ + stop: function () { + + this.media.stop(); + + }, + +} ); + +/** + * @classdesc Orbit Controls + * @constructor + * @external OrbitControls + * @param {THREE.Object} object + * @param {HTMLElement} domElement + */ +function OrbitControls ( object, domElement ) { + + this.object = object; + this.domElement = ( domElement !== undefined ) ? domElement : document; + this.frameId = null; + + // API + + // Set to false to disable this control + this.enabled = true; + + /* + * "target" sets the location of focus, where the control orbits around + * and where it pans with respect to. + */ + this.target = new Vector3(); + + // center is old, deprecated; use "target" instead + this.center = this.target; + + /* + * This option actually enables dollying in and out; left as "zoom" for + * backwards compatibility + */ + this.noZoom = false; + this.zoomSpeed = 1.0; + + // Limits to how far you can dolly in and out ( PerspectiveCamera only ) + this.minDistance = 0; + this.maxDistance = Infinity; + + // Limits to how far you can zoom in and out ( OrthographicCamera only ) + this.minZoom = 0; + this.maxZoom = Infinity; + + // Set to true to disable this control + this.noRotate = false; + this.rotateSpeed = -0.15; + + // Set to true to disable this control + this.noPan = true; + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + // Set to true to automatically rotate around the target + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 + + /* + * How far you can orbit vertically, upper and lower limits. + * Range is 0 to Math.PI radians. + */ + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + // Momentum + this.momentumDampingFactor = 0.90; + this.momentumScalingFactor = -0.005; + this.momentumKeydownFactor = 20; + + // Fov + this.minFov = 30; + this.maxFov = 120; + + /* + * How far you can orbit horizontally, upper and lower limits. + * If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. + */ + this.minAzimuthAngle = - Infinity; // radians + this.maxAzimuthAngle = Infinity; // radians + + // Set to true to disable use of the keys + this.noKeys = false; + + // The four arrow keys + this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; + + // Mouse buttons + this.mouseButtons = { ORBIT: MOUSE.LEFT, ZOOM: MOUSE.MIDDLE, PAN: MOUSE.RIGHT }; + + /* + * ////////// + * internals + */ + + var scope = this; + + var EPS = 10e-8; + var MEPS = 10e-5; + + var rotateStart = new Vector2(); + var rotateEnd = new Vector2(); + var rotateDelta = new Vector2(); + + var panStart = new Vector2(); + var panEnd = new Vector2(); + var panDelta = new Vector2(); + var panOffset = new Vector3(); + + var offset = new Vector3(); + + var dollyStart = new Vector2(); + var dollyEnd = new Vector2(); + var dollyDelta = new Vector2(); + + var theta = 0; + var phi = 0; + var phiDelta = 0; + var thetaDelta = 0; + var scale = 1; + var pan = new Vector3(); + + var lastPosition = new Vector3(); + var lastQuaternion = new Quaternion(); + + var momentumLeft = 0, momentumUp = 0; + var eventPrevious; + var momentumOn = false; + + var keyUp, keyBottom, keyLeft, keyRight; + + var STATE = { NONE: -1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 }; + + var state = STATE.NONE; + + // for reset + + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.zoom0 = this.object.zoom; + + // so camera.up is the orbit axis + + var quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); + var quatInverse = quat.clone().inverse(); + + // events + + var changeEvent = { type: 'change' }; + var startEvent = { type: 'start' }; + var endEvent = { type: 'end' }; + + this.setLastQuaternion = function ( quaternion ) { + lastQuaternion.copy( quaternion ); + scope.object.quaternion.copy( quaternion ); + }; + + this.getLastPosition = function () { + return lastPosition; + }; + + this.rotateLeft = function ( angle ) { + + if ( angle === undefined ) { + + angle = getAutoRotationAngle(); + + } + + thetaDelta -= angle; + + + }; + + this.rotateUp = function ( angle ) { + + if ( angle === undefined ) { + + angle = getAutoRotationAngle(); + + } + + phiDelta -= angle; + + }; + + // pass in distance in world space to move left + this.panLeft = function ( distance ) { + + var te = this.object.matrix.elements; + + // get X column of matrix + panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] ); + panOffset.multiplyScalar( - distance ); + + pan.add( panOffset ); + + }; + + // pass in distance in world space to move up + this.panUp = function ( distance ) { + + var te = this.object.matrix.elements; + + // get Y column of matrix + panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] ); + panOffset.multiplyScalar( distance ); + + pan.add( panOffset ); + + }; + + /* + * pass in x,y of change desired in pixel space, + * right and down are positive + */ + this.pan = function ( deltaX, deltaY ) { + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + if ( scope.object instanceof PerspectiveCamera ) { + + // perspective + var position = scope.object.position; + var offset = position.clone().sub( scope.target ); + var targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + + // we actually don't use screenWidth, since perspective camera is fixed to screen height + scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight ); + scope.panUp( 2 * deltaY * targetDistance / element.clientHeight ); + + } else if ( scope.object instanceof OrthographicCamera ) { + + // orthographic + scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth ); + scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight ); + + } else { + + // camera neither orthographic or perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + + } + + }; + + this.momentum = function(){ + + if ( !momentumOn ) return; + + if ( Math.abs( momentumLeft ) < MEPS && Math.abs( momentumUp ) < MEPS ) { + + momentumOn = false; + return; + } + + momentumUp *= this.momentumDampingFactor; + momentumLeft *= this.momentumDampingFactor; + + thetaDelta -= this.momentumScalingFactor * momentumLeft; + phiDelta -= this.momentumScalingFactor * momentumUp; + + }; + + this.dollyIn = function ( dollyScale ) { + + if ( dollyScale === undefined ) { + + dollyScale = getZoomScale(); + + } + + if ( scope.object instanceof PerspectiveCamera ) { + + scale /= dollyScale; + + } else if ( scope.object instanceof OrthographicCamera ) { + + scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( changeEvent ); + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + + } + + }; + + this.dollyOut = function ( dollyScale ) { + + if ( dollyScale === undefined ) { + + dollyScale = getZoomScale(); + + } + + if ( scope.object instanceof PerspectiveCamera ) { + + scale *= dollyScale; + + } else if ( scope.object instanceof OrthographicCamera ) { + + scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( changeEvent ); + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + + } + + }; + + this.update = function ( ignoreUpdate ) { + + var position = this.object.position; + + offset.copy( position ).sub( this.target ); + + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); + + // angle from z-axis around y-axis + + theta = Math.atan2( offset.x, offset.z ); + + // angle from y-axis + + phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); + + if ( this.autoRotate && state === STATE.NONE ) { + + this.rotateLeft( getAutoRotationAngle() ); + + } + + this.momentum(); + + theta += thetaDelta; + phi += phiDelta; + + // restrict theta to be between desired limits + theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) ); + + // restrict phi to be between desired limits + phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); + + // restrict phi to be betwee EPS and PI-EPS + phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); + + var radius = offset.length() * scale; + + // restrict radius to be between desired limits + radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); + + // move target to panned location + this.target.add( pan ); + + offset.x = radius * Math.sin( phi ) * Math.sin( theta ); + offset.y = radius * Math.cos( phi ); + offset.z = radius * Math.sin( phi ) * Math.cos( theta ); + + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); + + position.copy( this.target ).add( offset ); + + this.object.lookAt( this.target ); + + thetaDelta = 0; + phiDelta = 0; + scale = 1; + pan.set( 0, 0, 0 ); + + /* + * update condition is: + * min(camera displacement, camera rotation in radians)^2 > EPS + * using small-angle approximation cos(x/2) = 1 - x^2 / 8 + */ + if ( lastPosition.distanceToSquared( this.object.position ) > EPS + || 8 * (1 - lastQuaternion.dot(this.object.quaternion)) > EPS ) { + + if ( ignoreUpdate !== true ) { this.dispatchEvent( changeEvent ); } + + lastPosition.copy( this.object.position ); + lastQuaternion.copy (this.object.quaternion ); + + } + + }; + + + this.reset = function () { + + state = STATE.NONE; + + this.target.copy( this.target0 ); + this.object.position.copy( this.position0 ); + this.object.zoom = this.zoom0; + + this.object.updateProjectionMatrix(); + this.dispatchEvent( changeEvent ); + + this.update(); + + }; + + this.getPolarAngle = function () { + + return phi; + + }; + + this.getAzimuthalAngle = function () { + + return theta; + + }; + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function onMouseDown( event ) { + + momentumOn = false; + + momentumLeft = momentumUp = 0; + + if ( scope.enabled === false ) return; + event.preventDefault(); + + if ( event.button === scope.mouseButtons.ORBIT ) { + if ( scope.noRotate === true ) return; + + state = STATE.ROTATE; + + rotateStart.set( event.clientX, event.clientY ); + + } else if ( event.button === scope.mouseButtons.ZOOM ) { + if ( scope.noZoom === true ) return; + + state = STATE.DOLLY; + + dollyStart.set( event.clientX, event.clientY ); + + } else if ( event.button === scope.mouseButtons.PAN ) { + if ( scope.noPan === true ) return; + + state = STATE.PAN; + + panStart.set( event.clientX, event.clientY ); + + } + + if ( state !== STATE.NONE ) { + document.addEventListener( 'mousemove', onMouseMove, false ); + document.addEventListener( 'mouseup', onMouseUp, false ); + scope.dispatchEvent( startEvent ); + } + + scope.update(); + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + if ( state === STATE.ROTATE ) { + + if ( scope.noRotate === true ) return; + + rotateEnd.set( event.clientX, event.clientY ); + rotateDelta.subVectors( rotateEnd, rotateStart ); + + // rotating across whole screen goes 360 degrees around + scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); + + // rotating up and down along whole screen attempts to go 360, but limited to 180 + scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); + + rotateStart.copy( rotateEnd ); + + if( eventPrevious ){ + momentumLeft = event.clientX - eventPrevious.clientX; + momentumUp = event.clientY - eventPrevious.clientY; + } + + eventPrevious = event; + + } else if ( state === STATE.DOLLY ) { + + if ( scope.noZoom === true ) return; + + dollyEnd.set( event.clientX, event.clientY ); + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + scope.dollyIn(); + + } else if ( dollyDelta.y < 0 ) { + + scope.dollyOut(); + + } + + dollyStart.copy( dollyEnd ); + + } else if ( state === STATE.PAN ) { + + if ( scope.noPan === true ) return; + + panEnd.set( event.clientX, event.clientY ); + panDelta.subVectors( panEnd, panStart ); + + scope.pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + } + + if ( state !== STATE.NONE ) scope.update(); + + } + + function onMouseUp( /* event */ ) { + + momentumOn = true; + + eventPrevious = undefined; + + if ( scope.enabled === false ) return; + + document.removeEventListener( 'mousemove', onMouseMove, false ); + document.removeEventListener( 'mouseup', onMouseUp, false ); + scope.dispatchEvent( endEvent ); + state = STATE.NONE; + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.noZoom === true || state !== STATE.NONE ) return; + + event.preventDefault(); + event.stopPropagation(); + + var delta = 0; + + if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 + + delta = event.wheelDelta; + + } else if ( event.detail !== undefined ) { // Firefox + + delta = - event.detail; + + } + + if ( delta > 0 ) { + + // scope.dollyOut(); + scope.object.fov = ( scope.object.fov < scope.maxFov ) + ? scope.object.fov + 1 + : scope.maxFov; + scope.object.updateProjectionMatrix(); + + } else if ( delta < 0 ) { + + // scope.dollyIn(); + scope.object.fov = ( scope.object.fov > scope.minFov ) + ? scope.object.fov - 1 + : scope.minFov; + scope.object.updateProjectionMatrix(); + + } + + scope.update(); + scope.dispatchEvent( changeEvent ); + scope.dispatchEvent( startEvent ); + scope.dispatchEvent( endEvent ); + + } + + function onKeyUp ( event ) { + + switch ( event.keyCode ) { + + case scope.keys.UP: + keyUp = false; + break; + + case scope.keys.BOTTOM: + keyBottom = false; + break; + + case scope.keys.LEFT: + keyLeft = false; + break; + + case scope.keys.RIGHT: + keyRight = false; + break; + + } + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.noKeys === true || scope.noRotate === true ) return; + + switch ( event.keyCode ) { + + case scope.keys.UP: + keyUp = true; + break; + + case scope.keys.BOTTOM: + keyBottom = true; + break; + + case scope.keys.LEFT: + keyLeft = true; + break; + + case scope.keys.RIGHT: + keyRight = true; + break; + + } + + if (keyUp || keyBottom || keyLeft || keyRight) { + + momentumOn = true; + + if (keyUp) momentumUp = - scope.rotateSpeed * scope.momentumKeydownFactor; + if (keyBottom) momentumUp = scope.rotateSpeed * scope.momentumKeydownFactor; + if (keyLeft) momentumLeft = - scope.rotateSpeed * scope.momentumKeydownFactor; + if (keyRight) momentumLeft = scope.rotateSpeed * scope.momentumKeydownFactor; + + } + + } + + function touchstart( event ) { + + momentumOn = false; + + momentumLeft = momentumUp = 0; + + if ( scope.enabled === false ) return; + + switch ( event.touches.length ) { + + case 1: // one-fingered touch: rotate + + if ( scope.noRotate === true ) return; + + state = STATE.TOUCH_ROTATE; + + rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + break; + + case 2: // two-fingered touch: dolly + + if ( scope.noZoom === true ) return; + + state = STATE.TOUCH_DOLLY; + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyStart.set( 0, distance ); + + break; + + case 3: // three-fingered touch: pan + + if ( scope.noPan === true ) return; + + state = STATE.TOUCH_PAN; + + panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) scope.dispatchEvent( startEvent ); + + } + + function touchmove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + switch ( event.touches.length ) { + + case 1: // one-fingered touch: rotate + + if ( scope.noRotate === true ) return; + if ( state !== STATE.TOUCH_ROTATE ) return; + + rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + rotateDelta.subVectors( rotateEnd, rotateStart ); + + // rotating across whole screen goes 360 degrees around + scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); + // rotating up and down along whole screen attempts to go 360, but limited to 180 + scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); + + rotateStart.copy( rotateEnd ); + + if( eventPrevious ){ + momentumLeft = event.touches[ 0 ].pageX - eventPrevious.pageX; + momentumUp = event.touches[ 0 ].pageY - eventPrevious.pageY; + } + + eventPrevious = { + pageX: event.touches[ 0 ].pageX, + pageY: event.touches[ 0 ].pageY, + }; + + scope.update(); + break; + + case 2: // two-fingered touch: dolly + + if ( scope.noZoom === true ) return; + if ( state !== STATE.TOUCH_DOLLY ) return; + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyEnd.set( 0, distance ); + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y < 0 ) { + + scope.object.fov = ( scope.object.fov < scope.maxFov ) + ? scope.object.fov + 1 + : scope.maxFov; + scope.object.updateProjectionMatrix(); + + } else if ( dollyDelta.y > 0 ) { + + scope.object.fov = ( scope.object.fov > scope.minFov ) + ? scope.object.fov - 1 + : scope.minFov; + scope.object.updateProjectionMatrix(); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + scope.dispatchEvent( changeEvent ); + break; + + case 3: // three-fingered touch: pan + + if ( scope.noPan === true ) return; + if ( state !== STATE.TOUCH_PAN ) return; + + panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + panDelta.subVectors( panEnd, panStart ); + + scope.pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + break; + + default: + + state = STATE.NONE; + + } + + } + + function touchend( /* event */ ) { + + momentumOn = true; + + eventPrevious = undefined; + + if ( scope.enabled === false ) return; + + scope.dispatchEvent( endEvent ); + state = STATE.NONE; + + } + + this.dispose = function() { + + this.domElement.removeEventListener( 'mousedown', onMouseDown ); + this.domElement.removeEventListener( 'mousewheel', onMouseWheel ); + this.domElement.removeEventListener( 'DOMMouseScroll', onMouseWheel ); + + this.domElement.removeEventListener( 'touchstart', touchstart ); + this.domElement.removeEventListener( 'touchend', touchend ); + this.domElement.removeEventListener( 'touchmove', touchmove ); + + window.removeEventListener( 'keyup', onKeyUp ); + window.removeEventListener( 'keydown', onKeyDown ); + + }; + + // this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); + this.domElement.addEventListener( 'mousedown', onMouseDown, { passive: false } ); + this.domElement.addEventListener( 'mousewheel', onMouseWheel, { passive: false } ); + this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, { passive: false } ); // firefox + + this.domElement.addEventListener( 'touchstart', touchstart, { passive: false } ); + this.domElement.addEventListener( 'touchend', touchend, { passive: false } ); + this.domElement.addEventListener( 'touchmove', touchmove, { passive: false } ); + + window.addEventListener( 'keyup', onKeyUp, { passive: false } ); + window.addEventListener( 'keydown', onKeyDown, { passive: false } ); + + // force an update at start + this.update(); + +} +OrbitControls.prototype = Object.assign( Object.create( EventDispatcher.prototype ), { + + constructor: OrbitControls + +} ); + +/** + * @classdesc Device Orientation Control + * @constructor + * @external DeviceOrientationControls + * @param {THREE.Camera} camera + * @param {HTMLElement} domElement + */ +function DeviceOrientationControls ( camera, domElement ) { + + var scope = this; + var changeEvent = { type: 'change' }; + + var rotY = 0; + var rotX = 0; + var tempX = 0; + var tempY = 0; + + this.camera = camera; + this.camera.rotation.reorder( 'YXZ' ); + this.domElement = ( domElement !== undefined ) ? domElement : document; + + this.enabled = true; + + this.deviceOrientation = {}; + this.screenOrientation = 0; + + this.alpha = 0; + this.alphaOffsetAngle = 0; + + + var onDeviceOrientationChangeEvent = function( event ) { + + scope.deviceOrientation = event; + + }; + + var onScreenOrientationChangeEvent = function() { + + scope.screenOrientation = window.orientation || 0; + + }; + + var onTouchStartEvent = function (event) { + + event.preventDefault(); + event.stopPropagation(); + + tempX = event.touches[ 0 ].pageX; + tempY = event.touches[ 0 ].pageY; + + }; + + var onTouchMoveEvent = function (event) { + + event.preventDefault(); + event.stopPropagation(); + + rotY += Math$1.degToRad( ( event.touches[ 0 ].pageX - tempX ) / 4 ); + rotX += Math$1.degToRad( ( tempY - event.touches[ 0 ].pageY ) / 4 ); + + scope.updateAlphaOffsetAngle( rotY ); + + tempX = event.touches[ 0 ].pageX; + tempY = event.touches[ 0 ].pageY; + + }; + + // The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y'' + + var setCameraQuaternion = function( quaternion, alpha, beta, gamma, orient ) { + + var zee = new Vector3( 0, 0, 1 ); + + var euler = new Euler(); + + var q0 = new Quaternion(); + + var q1 = new Quaternion( - Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis + + var vectorFingerY; + var fingerQY = new Quaternion(); + var fingerQX = new Quaternion(); + + if ( scope.screenOrientation == 0 ) { + + vectorFingerY = new Vector3( 1, 0, 0 ); + fingerQY.setFromAxisAngle( vectorFingerY, -rotX ); + + } else if ( scope.screenOrientation == 180 ) { + + vectorFingerY = new Vector3( 1, 0, 0 ); + fingerQY.setFromAxisAngle( vectorFingerY, rotX ); + + } else if ( scope.screenOrientation == 90 ) { + + vectorFingerY = new Vector3( 0, 1, 0 ); + fingerQY.setFromAxisAngle( vectorFingerY, rotX ); + + } else if ( scope.screenOrientation == - 90) { + + vectorFingerY = new Vector3( 0, 1, 0 ); + fingerQY.setFromAxisAngle( vectorFingerY, -rotX ); + + } + + q1.multiply( fingerQY ); + q1.multiply( fingerQX ); + + euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us + + quaternion.setFromEuler( euler ); // orient the device + + quaternion.multiply( q1 ); // camera looks out the back of the device, not the top + + quaternion.multiply( q0.setFromAxisAngle( zee, - orient ) ); // adjust for screen orientation + + }; + + this.connect = function() { + + onScreenOrientationChangeEvent(); // run once on load + + window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent, { passive: true } ); + window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, { passive: true } ); + window.addEventListener( 'deviceorientation', this.update.bind( this ), { passive: true } ); + + scope.domElement.addEventListener( 'touchstart', onTouchStartEvent, { passive: false } ); + scope.domElement.addEventListener( 'touchmove', onTouchMoveEvent, { passive: false } ); + + scope.enabled = true; + + }; + + this.disconnect = function() { + + window.removeEventListener( 'orientationchange', onScreenOrientationChangeEvent, false ); + window.removeEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false ); + window.removeEventListener( 'deviceorientation', this.update.bind( this ), false ); + + scope.domElement.removeEventListener( 'touchstart', onTouchStartEvent, false ); + scope.domElement.removeEventListener( 'touchmove', onTouchMoveEvent, false ); + + scope.enabled = false; + + }; + + this.update = function( ignoreUpdate ) { + + if ( scope.enabled === false ) return; + + var alpha = scope.deviceOrientation.alpha ? Math$1.degToRad( scope.deviceOrientation.alpha ) + scope.alphaOffsetAngle : 0; // Z + var beta = scope.deviceOrientation.beta ? Math$1.degToRad( scope.deviceOrientation.beta ) : 0; // X' + var gamma = scope.deviceOrientation.gamma ? Math$1.degToRad( scope.deviceOrientation.gamma ) : 0; // Y'' + var orient = scope.screenOrientation ? Math$1.degToRad( scope.screenOrientation ) : 0; // O + + setCameraQuaternion( scope.camera.quaternion, alpha, beta, gamma, orient ); + scope.alpha = alpha; + + if ( ignoreUpdate !== true ) { scope.dispatchEvent( changeEvent ); } + + }; + + this.updateAlphaOffsetAngle = function( angle ) { + + this.alphaOffsetAngle = angle; + this.update(); + + }; + + this.dispose = function() { + + this.disconnect(); + + }; + + this.connect(); + +} +DeviceOrientationControls.prototype = Object.assign( Object.create( EventDispatcher.prototype), { + + constructor: DeviceOrientationControls + +} ); + +/** + * @classdesc Google Cardboard Effect Composer + * @constructor + * @external CardboardEffect + * @param {THREE.WebGLRenderer} renderer + */ +function CardboardEffect ( renderer ) { + + var _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); + + var _scene = new Scene(); + + var _stereo = new StereoCamera(); + _stereo.aspect = 0.5; + + var _params = { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat }; + + var _renderTarget = new WebGLRenderTarget( 512, 512, _params ); + _renderTarget.scissorTest = true; + _renderTarget.texture.generateMipmaps = false; + + /* + * Distortion Mesh ported from: + * https://github.com/borismus/webvr-boilerplate/blob/master/src/distortion/barrel-distortion-fragment.js + */ + + var distortion = new Vector2( 0.441, 0.156 ); + + var geometry = new PlaneBufferGeometry( 1, 1, 10, 20 ).removeAttribute( 'normal' ).toNonIndexed(); + + var positions = geometry.attributes.position.array; + var uvs = geometry.attributes.uv.array; + + // duplicate + geometry.attributes.position.count *= 2; + geometry.attributes.uv.count *= 2; + + var positions2 = new Float32Array( positions.length * 2 ); + positions2.set( positions ); + positions2.set( positions, positions.length ); + + var uvs2 = new Float32Array( uvs.length * 2 ); + uvs2.set( uvs ); + uvs2.set( uvs, uvs.length ); + + var vector = new Vector2(); + var length = positions.length / 3; + + for ( var i = 0, l = positions2.length / 3; i < l; i ++ ) { + + vector.x = positions2[ i * 3 + 0 ]; + vector.y = positions2[ i * 3 + 1 ]; + + var dot = vector.dot( vector ); + var scalar = 1.5 + ( distortion.x + distortion.y * dot ) * dot; + + var offset = i < length ? 0 : 1; + + positions2[ i * 3 + 0 ] = ( vector.x / scalar ) * 1.5 - 0.5 + offset; + positions2[ i * 3 + 1 ] = ( vector.y / scalar ) * 3.0; + + uvs2[ i * 2 ] = ( uvs2[ i * 2 ] + offset ) * 0.5; + + } + + geometry.attributes.position.array = positions2; + geometry.attributes.uv.array = uvs2; + + // + + var material = new MeshBasicMaterial( { map: _renderTarget.texture } ); + var mesh = new Mesh( geometry, material ); + _scene.add( mesh ); + + // + + this.setSize = function ( width, height ) { + + renderer.setSize( width, height ); + + var pixelRatio = renderer.getPixelRatio(); + + _renderTarget.setSize( width * pixelRatio, height * pixelRatio ); + + }; + + this.render = function ( scene, camera ) { + + scene.updateMatrixWorld(); + + if ( camera.parent === null ) camera.updateMatrixWorld(); + + _stereo.update( camera ); + + var width = _renderTarget.width / 2; + var height = _renderTarget.height; + + if ( renderer.autoClear ) renderer.clear(); + + _renderTarget.scissor.set( 0, 0, width, height ); + _renderTarget.viewport.set( 0, 0, width, height ); + renderer.setRenderTarget( _renderTarget ); + renderer.render( scene, _stereo.cameraL ); + + renderer.clearDepth(); + + _renderTarget.scissor.set( width, 0, width, height ); + _renderTarget.viewport.set( width, 0, width, height ); + renderer.setRenderTarget( _renderTarget ); + renderer.render( scene, _stereo.cameraR ); + + renderer.clearDepth(); + + renderer.setRenderTarget( null ); + renderer.render( _scene, _camera ); + }; + +} + +/** + * @classdesc Stereo Effect Composer + * @constructor + * @external StereoEffect + * @param {THREE.WebGLRenderer} renderer + */ +const StereoEffect = function ( renderer ) { + + var _stereo = new StereoCamera(); + _stereo.aspect = 0.5; + var size = new Vector2(); + + this.setEyeSeparation = function ( eyeSep ) { + + _stereo.eyeSep = eyeSep; + + }; + + this.setSize = function ( width, height ) { + + renderer.setSize( width, height ); + + }; + + this.render = function ( scene, camera ) { + + scene.updateMatrixWorld(); + + if ( camera.parent === null ) camera.updateMatrixWorld(); + + _stereo.update( camera ); + + renderer.getSize( size ); + + if ( renderer.autoClear ) renderer.clear(); + renderer.setScissorTest( true ); + + renderer.setScissor( 0, 0, size.width / 2, size.height ); + renderer.setViewport( 0, 0, size.width / 2, size.height ); + renderer.render( scene, _stereo.cameraL ); + + renderer.setScissor( size.width / 2, 0, size.width / 2, size.height ); + renderer.setViewport( size.width / 2, 0, size.width / 2, size.height ); + renderer.render( scene, _stereo.cameraR ); + + renderer.setScissorTest( false ); + + }; + +}; + +/** + * @classdesc Viewer contains pre-defined scene, camera and renderer + * @constructor + * @param {object} [options] - Use custom or default config options + * @param {HTMLElement} [options.container] - A HTMLElement to host the canvas + * @param {THREE.Scene} [options.scene=THREE.Scene] - A THREE.Scene which contains panorama and 3D objects + * @param {THREE.Camera} [options.camera=THREE.PerspectiveCamera] - A THREE.Camera to view the scene + * @param {THREE.WebGLRenderer} [options.renderer=THREE.WebGLRenderer] - A THREE.WebGLRenderer to render canvas + * @param {boolean} [options.controlBar=true] - Show/hide control bar on the bottom of the container + * @param {array} [options.controlButtons=[]] - Button names to mount on controlBar if controlBar exists, Defaults to ['fullscreen', 'setting', 'video'] + * @param {boolean} [options.autoHideControlBar=false] - Auto hide control bar when click on non-active area + * @param {boolean} [options.autoHideInfospot=true] - Auto hide infospots when click on non-active area + * @param {boolean} [options.horizontalView=false] - Allow only horizontal camera control + * @param {number} [options.clickTolerance=10] - Distance tolerance to tigger click / tap event + * @param {number} [options.cameraFov=60] - Camera field of view value + * @param {boolean} [options.reverseDragging=false] - Reverse dragging direction + * @param {boolean} [options.enableReticle=false] - Enable reticle for mouseless interaction other than VR mode + * @param {number} [options.dwellTime=1500] - Dwell time for reticle selection in ms + * @param {boolean} [options.autoReticleSelect=true] - Auto select a clickable target after dwellTime + * @param {boolean} [options.viewIndicator=false] - Adds an angle view indicator in upper left corner + * @param {number} [options.indicatorSize=30] - Size of View Indicator + * @param {string} [options.output='none'] - Whether and where to output raycast position. Could be 'event', 'console' or 'overlay'. + * @param {boolean} [options.autoRotate=false] - Auto rotate + * @param {number} [options.autoRotateSpeed=2.0] - Auto rotate speed as in degree per second. Positive is counter-clockwise and negative is clockwise. + * @param {number} [options.autoRotateActivationDuration=5000] - Duration before auto rotatation when no user interactivity in ms + */ +function Viewer ( options ) { + + let container; + + options = options || {}; + options.controlBar = options.controlBar !== undefined ? options.controlBar : true; + options.controlButtons = options.controlButtons || [ 'fullscreen', 'setting', 'video' ]; + options.autoHideControlBar = options.autoHideControlBar !== undefined ? options.autoHideControlBar : false; + options.autoHideInfospot = options.autoHideInfospot !== undefined ? options.autoHideInfospot : true; + options.horizontalView = options.horizontalView !== undefined ? options.horizontalView : false; + options.clickTolerance = options.clickTolerance || 10; + options.cameraFov = options.cameraFov || 60; + options.reverseDragging = options.reverseDragging || false; + options.enableReticle = options.enableReticle || false; + options.dwellTime = options.dwellTime || 1500; + options.autoReticleSelect = options.autoReticleSelect !== undefined ? options.autoReticleSelect : true; + options.viewIndicator = options.viewIndicator !== undefined ? options.viewIndicator : false; + options.indicatorSize = options.indicatorSize || 30; + options.output = options.output ? options.output : 'none'; + options.autoRotate = options.autoRotate || false; + options.autoRotateSpeed = options.autoRotateSpeed || 2.0; + options.autoRotateActivationDuration = options.autoRotateActivationDuration || 5000; + + this.options = options; + + /* + * CSS Icon + * const styleLoader = new StyleLoader(); + * styleLoader.inject( 'icono' ); + */ + + // Container + if ( options.container ) { + + container = options.container; + container._width = container.clientWidth; + container._height = container.clientHeight; + + } else { + + container = document.createElement( 'div' ); + container.classList.add( 'panolens-container' ); + container.style.width = '100%'; + container.style.height = '100%'; + container._width = window.innerWidth; + container._height = window.innerHeight; + document.body.appendChild( container ); + + } + + this.container = container; + + this.camera = options.camera || new PerspectiveCamera( this.options.cameraFov, this.container.clientWidth / this.container.clientHeight, 1, 10000 ); + this.scene = options.scene || new Scene(); + this.renderer = options.renderer || new WebGLRenderer( { alpha: true, antialias: false } ); + this.sceneReticle = new Scene(); + + this.viewIndicatorSize = this.options.indicatorSize; + + this.reticle = {}; + this.tempEnableReticle = this.options.enableReticle; + + this.mode = MODES.NORMAL; + + this.panorama = null; + this.widget = null; + + this.hoverObject = null; + this.infospot = null; + this.pressEntityObject = null; + this.pressObject = null; + + this.raycaster = new Raycaster(); + this.raycasterPoint = new Vector2(); + this.userMouse = new Vector2(); + this.updateCallbacks = []; + this.requestAnimationId = null; + + this.cameraFrustum = new Frustum(); + this.cameraViewProjectionMatrix = new Matrix4(); + + this.autoRotateRequestId = null; + + this.outputDivElement = null; + + this.touchSupported = 'ontouchstart' in window || window.DocumentTouch && document instanceof DocumentTouch; + + // Handler references + this.HANDLER_MOUSE_DOWN = this.onMouseDown.bind( this ); + this.HANDLER_MOUSE_UP = this.onMouseUp.bind( this ); + this.HANDLER_MOUSE_MOVE = this.onMouseMove.bind( this ); + this.HANDLER_WINDOW_RESIZE = this.onWindowResize.bind( this ); + this.HANDLER_KEY_DOWN = this.onKeyDown.bind( this ); + this.HANDLER_KEY_UP = this.onKeyUp.bind( this ); + this.HANDLER_TAP = this.onTap.bind( this, { + clientX: this.container.clientWidth / 2, + clientY: this.container.clientHeight / 2 + } ); + + // Flag for infospot output + this.OUTPUT_INFOSPOT = false; + + // Animations + this.tweenLeftAnimation = new Tween.Tween(); + this.tweenUpAnimation = new Tween.Tween(); + + // Renderer + this.renderer.setPixelRatio( window.devicePixelRatio ); + this.renderer.setSize( this.container.clientWidth, this.container.clientHeight ); + this.renderer.setClearColor( 0x000000, 0 ); + this.renderer.autoClear = false; + + // Append Renderer Element to container + this.renderer.domElement.classList.add( 'panolens-canvas' ); + this.renderer.domElement.style.display = 'block'; + this.container.style.backgroundColor = '#000'; + this.container.appendChild( this.renderer.domElement ); + + // Camera Controls + this.OrbitControls = new OrbitControls( this.camera, this.container ); + this.OrbitControls.id = 'orbit'; + this.OrbitControls.minDistance = 1; + this.OrbitControls.noPan = true; + this.OrbitControls.autoRotate = this.options.autoRotate; + this.OrbitControls.autoRotateSpeed = this.options.autoRotateSpeed; + + this.DeviceOrientationControls = new DeviceOrientationControls( this.camera, this.container ); + this.DeviceOrientationControls.id = 'device-orientation'; + this.DeviceOrientationControls.enabled = false; + this.camera.position.z = 1; + + // Register change event if passiveRenering + if ( this.options.passiveRendering ) { + + console.warn( 'passiveRendering is now deprecated' ); + + } + + // Controls + this.controls = [ this.OrbitControls, this.DeviceOrientationControls ]; + this.control = this.OrbitControls; + + // Cardboard effect + this.CardboardEffect = new CardboardEffect( this.renderer ); + this.CardboardEffect.setSize( this.container.clientWidth, this.container.clientHeight ); + + // Stereo effect + this.StereoEffect = new StereoEffect( this.renderer ); + this.StereoEffect.setSize( this.container.clientWidth, this.container.clientHeight ); + + this.effect = this.CardboardEffect; + + // Add default hidden reticle + this.addReticle(); + + // Lock horizontal view + if ( this.options.horizontalView ) { + this.OrbitControls.minPolarAngle = Math.PI / 2; + this.OrbitControls.maxPolarAngle = Math.PI / 2; + } + + // Add Control UI + if ( this.options.controlBar !== false ) { + this.addDefaultControlBar( this.options.controlButtons ); + } + + // Add View Indicator + if ( this.options.viewIndicator ) { + this.addViewIndicator(); + } + + // Reverse dragging direction + if ( this.options.reverseDragging ) { + this.reverseDraggingDirection(); + } + + // Register event if reticle is enabled, otherwise defaults to mouse + if ( this.options.enableReticle ) { + this.enableReticleControl(); + } else { + this.registerMouseAndTouchEvents(); + } + + // Output infospot position to an overlay container if specified + if ( this.options.output === 'overlay' ) { + this.addOutputElement(); + } + + // Register dom event listeners + this.registerEventListeners(); + + // Animate + this.animate.call( this ); + +} +Viewer.prototype = Object.assign( Object.create( EventDispatcher.prototype ), { + + constructor: Viewer, + + /** + * Add an object to the scene + * Automatically hookup with panolens-viewer-handler listener + * to communicate with viewer method + * @param {THREE.Object3D} object - The object to be added + * @memberOf Viewer + * @instance + */ + add: function ( object ) { + + if ( arguments.length > 1 ) { + + for ( let i = 0; i < arguments.length; i ++ ) { + + this.add( arguments[ i ] ); + + } + + return this; + + } + + this.scene.add( object ); + + // All object added to scene has 'panolens-viewer-handler' event to handle viewer communication + if ( object.addEventListener ) { + + object.addEventListener( 'panolens-viewer-handler', this.eventHandler.bind( this ) ); + + } + + // All object added to scene being passed with container + if ( object instanceof Panorama && object.dispatchEvent ) { + + object.dispatchEvent( { type: 'panolens-container', container: this.container } ); + + } + + if ( object instanceof CameraPanorama ) { + + object.dispatchEvent( { type: 'panolens-scene', scene: this.scene } ); + + } + + // Hookup default panorama event listeners + if ( object.type === 'panorama' ) { + + this.addPanoramaEventListener( object ); + + if ( !this.panorama ) { + + this.setPanorama( object ); + + } + + } + + }, + + /** + * Remove an object from the scene + * @param {THREE.Object3D} object - Object to be removed + * @memberOf Viewer + * @instance + */ + remove: function ( object ) { + + if ( object.removeEventListener ) { + + object.removeEventListener( 'panolens-viewer-handler', this.eventHandler.bind( this ) ); + + } + + this.scene.remove( object ); + + }, + + /** + * Add default control bar + * @param {array} array - The control buttons array + * @memberOf Viewer + * @instance + */ + addDefaultControlBar: function ( array ) { + + if ( this.widget ) { + + console.warn( 'Default control bar exists' ); + return; + + } + + const widget = new Widget( this.container ); + widget.addEventListener( 'panolens-viewer-handler', this.eventHandler.bind( this ) ); + widget.addControlBar(); + array.forEach( buttonName => { + + widget.addControlButton( buttonName ); + + } ); + + this.widget = widget; + + }, + + /** + * Set a panorama to be the current one + * @param {Panorama} pano - Panorama to be set + * @memberOf Viewer + * @instance + */ + setPanorama: function ( pano ) { + + const leavingPanorama = this.panorama; + + if ( pano.type === 'panorama' && leavingPanorama !== pano ) { + + // Clear exisiting infospot + this.hideInfospot(); + + const afterEnterComplete = function () { + + if ( leavingPanorama ) { leavingPanorama.onLeave(); } + pano.removeEventListener( 'enter-fade-start', afterEnterComplete ); + + }; + + pano.addEventListener( 'enter-fade-start', afterEnterComplete ); + + // Assign and enter panorama + (this.panorama = pano).onEnter(); + + } + + }, + + /** + * Event handler to execute commands from child objects + * @param {object} event - The dispatched event with method as function name and data as an argument + * @memberOf Viewer + * @instance + */ + eventHandler: function ( event ) { + + if ( event.method && this[ event.method ] ) { + + this[ event.method ]( event.data ); + + } + + }, + + /** + * Dispatch event to all descendants + * @param {object} event - Event to be passed along + * @memberOf Viewer + * @instance + */ + dispatchEventToChildren: function ( event ) { + + this.scene.traverse( function ( object ) { + + if ( object.dispatchEvent ) { + + object.dispatchEvent( event ); + + } + + }); + + }, + + /** + * Set widget content + * @method activateWidgetItem + * @param {integer} controlIndex - Control index + * @param {integer} mode - Modes for effects + * @memberOf Viewer + * @instance + */ + activateWidgetItem: function ( controlIndex, mode ) { + + const mainMenu = this.widget.mainMenu; + const ControlMenuItem = mainMenu.children[ 0 ]; + const ModeMenuItem = mainMenu.children[ 1 ]; + + let item; + + if ( controlIndex !== undefined ) { + + switch ( controlIndex ) { + + case 0: + + item = ControlMenuItem.subMenu.children[ 1 ]; + + break; + + case 1: + + item = ControlMenuItem.subMenu.children[ 2 ]; + + break; + + default: + + item = ControlMenuItem.subMenu.children[ 1 ]; + + break; + + } + + ControlMenuItem.subMenu.setActiveItem( item ); + ControlMenuItem.setSelectionTitle( item.textContent ); + + } + + if ( mode !== undefined ) { + + switch( mode ) { + + case MODES.CARDBOARD: + + item = ModeMenuItem.subMenu.children[ 2 ]; + + break; + + case MODES.STEREO: + + item = ModeMenuItem.subMenu.children[ 3 ]; + + break; + + default: + + item = ModeMenuItem.subMenu.children[ 1 ]; + + break; + } + + ModeMenuItem.subMenu.setActiveItem( item ); + ModeMenuItem.setSelectionTitle( item.textContent ); + + } + + }, + + /** + * Enable rendering effect + * @param {MODES} mode - Modes for effects + * @memberOf Viewer + * @instance + */ + enableEffect: function ( mode ) { + + if ( this.mode === mode ) { return; } + if ( mode === MODES.NORMAL ) { this.disableEffect(); return; } + else { this.mode = mode; } + + const fov = this.camera.fov; + + switch( mode ) { + + case MODES.CARDBOARD: + + this.effect = this.CardboardEffect; + this.enableReticleControl(); + + break; + + case MODES.STEREO: + + this.effect = this.StereoEffect; + this.enableReticleControl(); + + break; + + default: + + this.effect = null; + this.disableReticleControl(); + + break; + + } + + this.activateWidgetItem( undefined, this.mode ); + + /** + * Dual eye effect event + * @type {object} + * @event Infospot#panolens-dual-eye-effect + * @property {MODES} mode - Current display mode + */ + this.dispatchEventToChildren( { type: 'panolens-dual-eye-effect', mode: this.mode } ); + + // Force effect stereo camera to update by refreshing fov + this.camera.fov = fov + 10e-3; + this.effect.setSize( this.container.clientWidth, this.container.clientHeight ); + this.render(); + this.camera.fov = fov; + + /** + * Dispatch mode change event + * @type {object} + * @event Viewer#mode-change + * @property {MODES} mode - Current display mode + */ + this.dispatchEvent( { type: 'mode-change', mode: this.mode } ); + + }, + + /** + * Disable additional rendering effect + * @memberOf Viewer + * @instance + */ + disableEffect: function () { + + if ( this.mode === MODES.NORMAL ) { return; } + + this.mode = MODES.NORMAL; + this.disableReticleControl(); + + this.activateWidgetItem( undefined, this.mode ); + + /** + * Dual eye effect event + * @type {object} + * @event Infospot#panolens-dual-eye-effect + * @property {MODES} mode - Current display mode + */ + this.dispatchEventToChildren( { type: 'panolens-dual-eye-effect', mode: this.mode } ); + + this.renderer.setSize( this.container.clientWidth, this.container.clientHeight ); + this.render(); + + /** + * Dispatch mode change event + * @type {object} + * @event Viewer#mode-change + * @property {MODES} mode - Current display mode + */ + this.dispatchEvent( { type: 'mode-change', mode: this.mode } ); + }, + + /** + * Enable reticle control + * @memberOf Viewer + * @instance + */ + enableReticleControl: function () { + + if ( this.reticle.visible ) { return; } + + this.tempEnableReticle = true; + + // Register reticle event and unregister mouse event + this.unregisterMouseAndTouchEvents(); + this.reticle.show(); + this.registerReticleEvent(); + this.updateReticleEvent(); + + }, + + /** + * Disable reticle control + * @memberOf Viewer + * @instance + */ + disableReticleControl: function () { + + this.tempEnableReticle = false; + + // Register mouse event and unregister reticle event + if ( !this.options.enableReticle ) { + + this.reticle.hide(); + this.unregisterReticleEvent(); + this.registerMouseAndTouchEvents(); + + } else { + + this.updateReticleEvent(); + + } + + }, + + /** + * Enable auto rotation + * @memberOf Viewer + * @instance + */ + enableAutoRate: function () { + + this.options.autoRotate = true; + this.OrbitControls.autoRotate = true; + + }, + + /** + * Disable auto rotation + * @memberOf Viewer + * @instance + */ + disableAutoRate: function () { + + clearTimeout( this.autoRotateRequestId ); + this.options.autoRotate = false; + this.OrbitControls.autoRotate = false; + + }, + + /** + * Toggle video play or stop + * @param {boolean} pause + * @memberOf Viewer + * @instance + * @fires Viewer#video-toggle + */ + toggleVideoPlay: function ( pause ) { + + if ( this.panorama instanceof VideoPanorama ) { + + /** + * Toggle video event + * @type {object} + * @event Viewer#video-toggle + */ + this.panorama.dispatchEvent( { type: 'video-toggle', pause: pause } ); + + } + + }, + + /** + * Set currentTime in a video + * @param {number} percentage - Percentage of a video. Range from 0.0 to 1.0 + * @memberOf Viewer + * @instance + * @fires Viewer#video-time + */ + setVideoCurrentTime: function ( percentage ) { + + if ( this.panorama instanceof VideoPanorama ) { + + /** + * Setting video time event + * @type {object} + * @event Viewer#video-time + * @property {number} percentage - Percentage of a video. Range from 0.0 to 1.0 + */ + this.panorama.dispatchEvent( { type: 'video-time', percentage: percentage } ); + + } + + }, + + /** + * This will be called when video updates if an widget is present + * @param {number} percentage - Percentage of a video. Range from 0.0 to 1.0 + * @memberOf Viewer + * @instance + * @fires Viewer#video-update + */ + onVideoUpdate: function ( percentage ) { + + const { widget } = this; + + /** + * Video update event + * @type {object} + * @event Viewer#video-update + * @property {number} percentage - Percentage of a video. Range from 0.0 to 1.0 + */ + if( widget ) { widget.dispatchEvent( { type: 'video-update', percentage: percentage } ); } + + }, + + /** + * Add update callback to be called every animation frame + * @param {function} callback + * @memberOf Viewer + * @instance + */ + addUpdateCallback: function ( fn ) { + + if ( fn ) { + + this.updateCallbacks.push( fn ); + + } + + }, + + /** + * Remove update callback + * @param {function} fn - The function to be removed + * @memberOf Viewer + * @instance + */ + removeUpdateCallback: function ( fn ) { + + const index = this.updateCallbacks.indexOf( fn ); + + if ( fn && index >= 0 ) { + + this.updateCallbacks.splice( index, 1 ); + + } + + }, + + /** + * Show video widget + * @memberOf Viewer + * @instance + */ + showVideoWidget: function () { + + const { widget } = this; + + /** + * Show video widget event + * @type {object} + * @event Viewer#video-control-show + */ + if( widget ) { widget.dispatchEvent( { type: 'video-control-show' } ); } + + }, + + /** + * Hide video widget + * @memberOf Viewer + * @instance + */ + hideVideoWidget: function () { + + const { widget } = this; + + /** + * Hide video widget + * @type {object} + * @event Viewer#video-control-hide + */ + if( widget ) { widget.dispatchEvent( { type: 'video-control-hide' } ); } + + }, + + /** + * Update video play button + * @param {boolean} paused + * @memberOf Viewer + * @instance + */ + updateVideoPlayButton: function ( paused ) { + + const { widget } = this; + + if ( widget && widget.videoElement && widget.videoElement.controlButton ) { + + widget.videoElement.controlButton.update( paused ); + + } + + }, + + /** + * Add default panorama event listeners + * @param {Panorama} pano - The panorama to be added with event listener + * @memberOf Viewer + * @instance + */ + addPanoramaEventListener: function ( pano ) { + + // Set camera control on every panorama + pano.addEventListener( 'enter-fade-start', this.setCameraControl.bind( this ) ); + + // Show and hide widget event only when it's VideoPanorama + if ( pano instanceof VideoPanorama ) { + + pano.addEventListener( 'enter-fade-start', this.showVideoWidget.bind( this ) ); + pano.addEventListener( 'leave', function () { + + if ( !(this.panorama instanceof VideoPanorama) ) { + + this.hideVideoWidget.call( this ); + + } + + }.bind( this ) ); + + } + + }, + + /** + * Set camera control + * @memberOf Viewer + * @instance + */ + setCameraControl: function () { + + this.OrbitControls.target.copy( this.panorama.position ); + + }, + + /** + * Get current camera control + * @return {object} - Current navigation control + * @memberOf Viewer + * @instance + * @returns {THREE.OrbitControls|THREE.DeviceOrientationControls} + */ + getControl: function () { + + return this.control; + + }, + + /** + * Get scene + * @memberOf Viewer + * @instance + * @return {THREE.Scene} - Current scene which the viewer is built on + */ + getScene: function () { + + return this.scene; + + }, + + /** + * Get camera + * @memberOf Viewer + * @instance + * @return {THREE.Camera} - The scene camera + */ + getCamera: function () { + + return this.camera; + + }, + + /** + * Get renderer + * @memberOf Viewer + * @instance + * @return {THREE.WebGLRenderer} - The renderer using webgl + */ + getRenderer: function () { + + return this.renderer; + + }, + + /** + * Get container + * @memberOf Viewer + * @instance + * @return {HTMLElement} - The container holds rendererd canvas + */ + getContainer: function () { + + return this.container; + + }, + + /** + * Get control id + * @memberOf Viewer + * @instance + * @return {string} - Control id. 'orbit' or 'device-orientation' + */ + getControlId: function () { + + return this.control.id; + + }, + + /** + * Get next navigation control id + * @memberOf Viewer + * @instance + * @return {string} - Next control id + */ + getNextControlId: function () { + + return this.controls[ this.getNextControlIndex() ].id; + + }, + + /** + * Get next navigation control index + * @memberOf Viewer + * @instance + * @return {number} - Next control index + */ + getNextControlIndex: function () { + + const controls = this.controls; + const control = this.control; + const nextIndex = controls.indexOf( control ) + 1; + + return ( nextIndex >= controls.length ) ? 0 : nextIndex; + + }, + + /** + * Set field of view of camera + * @param {number} fov + * @memberOf Viewer + * @instance + */ + setCameraFov: function ( fov ) { + + this.camera.fov = fov; + this.camera.updateProjectionMatrix(); + + }, + + /** + * Enable control by index + * @param {CONTROLS} index - Index of camera control + * @memberOf Viewer + * @instance + */ + enableControl: function ( index ) { + + index = ( index >= 0 && index < this.controls.length ) ? index : 0; + + this.control.enabled = false; + + this.control = this.controls[ index ]; + + this.control.enabled = true; + + switch ( index ) { + + case CONTROLS.ORBIT: + + this.camera.position.copy( this.panorama.position ); + this.camera.position.z += 1; + + break; + + case CONTROLS.DEVICEORIENTATION: + + this.camera.position.copy( this.panorama.position ); + + break; + + default: + + break; + } + + this.control.update(); + + this.activateWidgetItem( index, undefined ); + + }, + + /** + * Disable current control + * @memberOf Viewer + * @instance + */ + disableControl: function () { + + this.control.enabled = false; + + }, + + /** + * Toggle next control + * @memberOf Viewer + * @instance + */ + toggleNextControl: function () { + + this.enableControl( this.getNextControlIndex() ); + + }, + + /** + * Screen Space Projection + * @memberOf Viewer + * @instance + */ + getScreenVector: function ( worldVector ) { + + const vector = worldVector.clone(); + const widthHalf = ( this.container.clientWidth ) / 2; + const heightHalf = this.container.clientHeight / 2; + + vector.project( this.camera ); + + vector.x = ( vector.x * widthHalf ) + widthHalf; + vector.y = - ( vector.y * heightHalf ) + heightHalf; + vector.z = 0; + + return vector; + + }, + + /** + * Check Sprite in Viewport + * @memberOf Viewer + * @instance + */ + checkSpriteInViewport: function ( sprite ) { + + this.camera.matrixWorldInverse.getInverse( this.camera.matrixWorld ); + this.cameraViewProjectionMatrix.multiplyMatrices( this.camera.projectionMatrix, this.camera.matrixWorldInverse ); + this.cameraFrustum.setFromMatrix( this.cameraViewProjectionMatrix ); + + return sprite.visible && this.cameraFrustum.intersectsSprite( sprite ); + + }, + + /** + * Reverse dragging direction + * @memberOf Viewer + * @instance + */ + reverseDraggingDirection: function () { + + this.OrbitControls.rotateSpeed *= -1; + this.OrbitControls.momentumScalingFactor *= -1; + + }, + + /** + * Add reticle + * @memberOf Viewer + * @instance + */ + addReticle: function () { + + this.reticle = new Reticle( 0xffffff, true, this.options.dwellTime ); + this.reticle.hide(); + this.camera.add( this.reticle ); + this.sceneReticle.add( this.camera ); + + }, + + /** + * Tween control looking center + * @param {THREE.Vector3} vector - Vector to be looked at the center + * @param {number} [duration=1000] - Duration to tween + * @param {function} [easing=TWEEN.Easing.Exponential.Out] - Easing function + * @memberOf Viewer + * @instance + */ + tweenControlCenter: function ( vector, duration, easing ) { + + if ( this.control !== this.OrbitControls ) { + + return; + + } + + // Pass in arguments as array + if ( vector instanceof Array ) { + + duration = vector[ 1 ]; + easing = vector[ 2 ]; + vector = vector[ 0 ]; + + } + + duration = duration !== undefined ? duration : 1000; + easing = easing || Tween.Easing.Exponential.Out; + + let scope, ha, va, chv, cvv, hv, vv, vptc, ov, nv; + + scope = this; + + chv = this.camera.getWorldDirection( new Vector3() ); + cvv = chv.clone(); + + vptc = this.panorama.getWorldPosition( new Vector3() ).sub( this.camera.getWorldPosition( new Vector3() ) ); + + hv = vector.clone(); + // Scale effect + hv.x *= -1; + hv.add( vptc ).normalize(); + vv = hv.clone(); + + chv.y = 0; + hv.y = 0; + + ha = Math.atan2( hv.z, hv.x ) - Math.atan2( chv.z, chv.x ); + ha = ha > Math.PI ? ha - 2 * Math.PI : ha; + ha = ha < -Math.PI ? ha + 2 * Math.PI : ha; + va = Math.abs( cvv.angleTo( chv ) + ( cvv.y * vv.y <= 0 ? vv.angleTo( hv ) : -vv.angleTo( hv ) ) ); + va *= vv.y < cvv.y ? 1 : -1; + + ov = { left: 0, up: 0 }; + nv = { left: 0, up: 0 }; + + this.tweenLeftAnimation.stop(); + this.tweenUpAnimation.stop(); + + this.tweenLeftAnimation = new Tween.Tween( ov ) + .to( { left: ha }, duration ) + .easing( easing ) + .onUpdate(function(ov){ + scope.control.rotateLeft( ov.left - nv.left ); + nv.left = ov.left; + }) + .start(); + + this.tweenUpAnimation = new Tween.Tween( ov ) + .to( { up: va }, duration ) + .easing( easing ) + .onUpdate(function(ov){ + scope.control.rotateUp( ov.up - nv.up ); + nv.up = ov.up; + }) + .start(); + + }, + + /** + * Tween control looking center by object + * @param {THREE.Object3D} object - Object to be looked at the center + * @param {number} [duration=1000] - Duration to tween + * @param {function} [easing=TWEEN.Easing.Exponential.Out] - Easing function + * @memberOf Viewer + * @instance + */ + tweenControlCenterByObject: function ( object, duration, easing ) { + + let isUnderScalePlaceHolder = false; + + object.traverseAncestors( function ( ancestor ) { + + if ( ancestor.scalePlaceHolder ) { + + isUnderScalePlaceHolder = true; + + } + } ); + + if ( isUnderScalePlaceHolder ) { + + const invertXVector = new Vector3( -1, 1, 1 ); + + this.tweenControlCenter( object.getWorldPosition( new Vector3() ).multiply( invertXVector ), duration, easing ); + + } else { + + this.tweenControlCenter( object.getWorldPosition( new Vector3() ), duration, easing ); + + } + + }, + + /** + * This is called when window size is changed + * @fires Viewer#window-resize + * @param {number} [windowWidth] - Specify if custom element has changed width + * @param {number} [windowHeight] - Specify if custom element has changed height + * @memberOf Viewer + * @instance + */ + onWindowResize: function ( windowWidth, windowHeight ) { + + let width, height; + + const expand = this.container.classList.contains( 'panolens-container' ) || this.container.isFullscreen; + + if ( windowWidth !== undefined && windowHeight !== undefined ) { + + width = windowWidth; + height = windowHeight; + this.container._width = windowWidth; + this.container._height = windowHeight; + + } else { + + const isAndroid = /(android)/i.test(window.navigator.userAgent); + + const adjustWidth = isAndroid + ? Math.min(document.documentElement.clientWidth, window.innerWidth || 0) + : Math.max(document.documentElement.clientWidth, window.innerWidth || 0); + + const adjustHeight = isAndroid + ? Math.min(document.documentElement.clientHeight, window.innerHeight || 0) + : Math.max(document.documentElement.clientHeight, window.innerHeight || 0); + + width = expand ? adjustWidth : this.container.clientWidth; + height = expand ? adjustHeight : this.container.clientHeight; + + this.container._width = width; + this.container._height = height; + + } + + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + + this.renderer.setSize( width, height ); + + // Update reticle + if ( this.options.enableReticle || this.tempEnableReticle ) { + + this.updateReticleEvent(); + + } + + /** + * Window resizing event + * @type {object} + * @event Viewer#window-resize + * @property {number} width - Width of the window + * @property {number} height - Height of the window + */ + this.dispatchEvent( { type: 'window-resize', width: width, height: height }); + this.scene.traverse( function ( object ) { + + if ( object.dispatchEvent ) { + + object.dispatchEvent( { type: 'window-resize', width: width, height: height }); + + } + + } ); + + }, + + /** + * Add output element + * @memberOf Viewer + * @instance + */ + addOutputElement: function () { + + const element = document.createElement( 'div' ); + element.style.position = 'absolute'; + element.style.right = '10px'; + element.style.top = '10px'; + element.style.color = '#fff'; + this.container.appendChild( element ); + this.outputDivElement = element; + + }, + + /** + * Output position in developer console by holding down Ctrl button + * @memberOf Viewer + * @instance + */ + outputPosition: function () { + + const intersects = this.raycaster.intersectObject( this.panorama, true ); + + if ( intersects.length > 0 ) { + + const point = intersects[ 0 ].point.clone(); + const converter = new Vector3( -1, 1, 1 ); + const world = this.panorama.getWorldPosition( new Vector3() ); + point.sub( world ).multiply( converter ); + + const position = { + x: point.x.toFixed(2), + y: point.y.toFixed(2), + z: point.z.toFixed(2), + }; + + const message = `${position.x}, ${position.y}, ${position.z}`; + + if ( point.length() === 0 ) { return; } + + switch ( this.options.output ) { + + case 'event': + /** + * Dispatch raycast position as event + * @type {object} + * @event Viewer#position-output + */ + this.dispatchEvent( { type: 'position-output', position: position } ); + break; + + case 'console': + console.info( message ); + break; + + case 'overlay': + this.outputDivElement.textContent = message; + break; + + default: + break; + + } + + } + + }, + + /** + * On mouse down + * @param {MouseEvent} event + * @memberOf Viewer + * @instance + */ + onMouseDown: function ( event ) { + + event.preventDefault(); + + this.userMouse.x = ( event.clientX >= 0 ) ? event.clientX : event.touches[0].clientX; + this.userMouse.y = ( event.clientY >= 0 ) ? event.clientY : event.touches[0].clientY; + this.userMouse.type = 'mousedown'; + this.onTap( event ); + + }, + + /** + * On mouse move + * @param {MouseEvent} event + * @memberOf Viewer + * @instance + */ + onMouseMove: function ( event ) { + + event.preventDefault(); + this.userMouse.type = 'mousemove'; + this.onTap( event ); + + }, + + /** + * On mouse up + * @param {MouseEvent} event + * @memberOf Viewer + * @instance + */ + onMouseUp: function ( event ) { + + let onTarget = false; + + this.userMouse.type = 'mouseup'; + + const type = ( this.userMouse.x >= event.clientX - this.options.clickTolerance + && this.userMouse.x <= event.clientX + this.options.clickTolerance + && this.userMouse.y >= event.clientY - this.options.clickTolerance + && this.userMouse.y <= event.clientY + this.options.clickTolerance ) + || ( event.changedTouches + && this.userMouse.x >= event.changedTouches[0].clientX - this.options.clickTolerance + && this.userMouse.x <= event.changedTouches[0].clientX + this.options.clickTolerance + && this.userMouse.y >= event.changedTouches[0].clientY - this.options.clickTolerance + && this.userMouse.y <= event.changedTouches[0].clientY + this.options.clickTolerance ) + ? 'click' : undefined; + + // Event should happen on canvas + if ( event && event.target && !event.target.classList.contains( 'panolens-canvas' ) ) { return; } + + event.preventDefault(); + + if ( event.changedTouches && event.changedTouches.length === 1 ) { + + onTarget = this.onTap( { clientX: event.changedTouches[0].clientX, clientY: event.changedTouches[0].clientY }, type ); + + } else { + + onTarget = this.onTap( event, type ); + + } + + this.userMouse.type = 'none'; + + if ( onTarget ) { + + return; + + } + + if ( type === 'click' ) { + + const { options: { autoHideInfospot, autoHideControlBar }, panorama, toggleControlBar } = this; + + if ( autoHideInfospot && panorama ) { + + panorama.toggleInfospotVisibility(); + + } + + if ( autoHideControlBar ) { + + toggleControlBar(); + + } + + } + + }, + + /** + * On tap eveny frame + * @param {MouseEvent} event + * @param {string} type + * @memberOf Viewer + * @instance + */ + onTap: function ( event, type ) { + + const { left, top } = this.container.getBoundingClientRect(); + const { clientWidth, clientHeight } = this.container; + + this.raycasterPoint.x = ( ( event.clientX - left ) / clientWidth ) * 2 - 1; + this.raycasterPoint.y = - ( ( event.clientY - top ) / clientHeight ) * 2 + 1; + + this.raycaster.setFromCamera( this.raycasterPoint, this.camera ); + + // Return if no panorama + if ( !this.panorama ) { + + return; + + } + + // output infospot information + if ( event.type !== 'mousedown' && this.touchSupported || this.OUTPUT_INFOSPOT ) { + + this.outputPosition(); + + } + + + const intersects = this.raycaster.intersectObjects( this.panorama.children, true ); + const intersect_entity = this.getConvertedIntersect( intersects ); + const intersect = ( intersects.length > 0 ) ? intersects[0].object : undefined; + + if ( this.userMouse.type === 'mouseup' ) { + + if ( intersect_entity && this.pressEntityObject === intersect_entity && this.pressEntityObject.dispatchEvent ) { + + this.pressEntityObject.dispatchEvent( { type: 'pressstop-entity', mouseEvent: event } ); + + } + + this.pressEntityObject = undefined; + + } + + if ( this.userMouse.type === 'mouseup' ) { + + if ( intersect && this.pressObject === intersect && this.pressObject.dispatchEvent ) { + + this.pressObject.dispatchEvent( { type: 'pressstop', mouseEvent: event } ); + + } + + this.pressObject = undefined; + + } + + if ( type === 'click' ) { + + this.panorama.dispatchEvent( { type: 'click', intersects: intersects, mouseEvent: event } ); + + if ( intersect_entity && intersect_entity.dispatchEvent ) { + + intersect_entity.dispatchEvent( { type: 'click-entity', mouseEvent: event } ); + + } + + if ( intersect && intersect.dispatchEvent ) { + + intersect.dispatchEvent( { type: 'click', mouseEvent: event } ); + + } + + } else { + + this.panorama.dispatchEvent( { type: 'hover', intersects: intersects, mouseEvent: event } ); + + if ( ( this.hoverObject && intersects.length > 0 && this.hoverObject !== intersect_entity ) + || ( this.hoverObject && intersects.length === 0 ) ){ + + if ( this.hoverObject.dispatchEvent ) { + + this.hoverObject.dispatchEvent( { type: 'hoverleave', mouseEvent: event } ); + + this.reticle.end(); + + } + + this.hoverObject = undefined; + + } + + if ( intersect_entity && intersects.length > 0 ) { + + if ( this.hoverObject !== intersect_entity ) { + + this.hoverObject = intersect_entity; + + if ( this.hoverObject.dispatchEvent ) { + + this.hoverObject.dispatchEvent( { type: 'hoverenter', mouseEvent: event } ); + + // Start reticle timer + if ( this.options.autoReticleSelect && this.options.enableReticle || this.tempEnableReticle ) { + this.reticle.start( this.onTap.bind( this, event, 'click' ) ); + } + + } + + } + + if ( this.userMouse.type === 'mousedown' && this.pressEntityObject != intersect_entity ) { + + this.pressEntityObject = intersect_entity; + + if ( this.pressEntityObject.dispatchEvent ) { + + this.pressEntityObject.dispatchEvent( { type: 'pressstart-entity', mouseEvent: event } ); + + } + + } + + if ( this.userMouse.type === 'mousedown' && this.pressObject != intersect ) { + + this.pressObject = intersect; + + if ( this.pressObject.dispatchEvent ) { + + this.pressObject.dispatchEvent( { type: 'pressstart', mouseEvent: event } ); + + } + + } + + if ( this.userMouse.type === 'mousemove' || this.options.enableReticle ) { + + if ( intersect && intersect.dispatchEvent ) { + + intersect.dispatchEvent( { type: 'hover', mouseEvent: event } ); + + } + + if ( this.pressEntityObject && this.pressEntityObject.dispatchEvent ) { + + this.pressEntityObject.dispatchEvent( { type: 'pressmove-entity', mouseEvent: event } ); + + } + + if ( this.pressObject && this.pressObject.dispatchEvent ) { + + this.pressObject.dispatchEvent( { type: 'pressmove', mouseEvent: event } ); + + } + + } + + } + + if ( !intersect_entity && this.pressEntityObject && this.pressEntityObject.dispatchEvent ) { + + this.pressEntityObject.dispatchEvent( { type: 'pressstop-entity', mouseEvent: event } ); + + this.pressEntityObject = undefined; + + } + + if ( !intersect && this.pressObject && this.pressObject.dispatchEvent ) { + + this.pressObject.dispatchEvent( { type: 'pressstop', mouseEvent: event } ); + + this.pressObject = undefined; + + } + + } + + // Infospot handler + if ( intersect && intersect instanceof Infospot ) { + + this.infospot = intersect; + + if ( type === 'click' ) { + + return true; + + } + + + } else if ( this.infospot ) { + + this.hideInfospot(); + + } + + // Auto rotate + if ( this.options.autoRotate && this.userMouse.type !== 'mousemove' ) { + + // Auto-rotate idle timer + clearTimeout( this.autoRotateRequestId ); + + if ( this.control === this.OrbitControls ) { + + this.OrbitControls.autoRotate = false; + this.autoRotateRequestId = window.setTimeout( this.enableAutoRate.bind( this ), this.options.autoRotateActivationDuration ); + + } + + } + + }, + + /** + * Get converted intersect + * @param {array} intersects + * @memberOf Viewer + * @instance + */ + getConvertedIntersect: function ( intersects ) { + + let intersect; + + for ( let i = 0; i < intersects.length; i++ ) { + + if ( intersects[i].distance >= 0 && intersects[i].object && !intersects[i].object.passThrough ) { + + if ( intersects[i].object.entity && intersects[i].object.entity.passThrough ) { + continue; + } else if ( intersects[i].object.entity && !intersects[i].object.entity.passThrough ) { + intersect = intersects[i].object.entity; + break; + } else { + intersect = intersects[i].object; + break; + } + + } + + } + + return intersect; + + }, + + /** + * Hide infospot + * @memberOf Viewer + * @instance + */ + hideInfospot: function () { + + if ( this.infospot ) { + + this.infospot.onHoverEnd(); + + this.infospot = undefined; + + } + + }, + + /** + * Toggle control bar + * @memberOf Viewer + * @instance + * @fires Viewer#control-bar-toggle + */ + toggleControlBar: function () { + + const { widget } = this; + + /** + * Toggle control bar event + * @type {object} + * @event Viewer#control-bar-toggle + */ + if ( widget ) { + + widget.dispatchEvent( { type: 'control-bar-toggle' } ); + + } + + }, + + /** + * On key down + * @param {KeyboardEvent} event + * @memberOf Viewer + * @instance + */ + onKeyDown: function ( event ) { + + if ( this.options.output && this.options.output !== 'none' && event.key === 'Control' ) { + + this.OUTPUT_INFOSPOT = true; + + } + + }, + + /** + * On key up + * @param {KeyboardEvent} event + * @memberOf Viewer + * @instance + */ + onKeyUp: function () { + + this.OUTPUT_INFOSPOT = false; + + }, + + /** + * Update control and callbacks + * @memberOf Viewer + * @instance + */ + update: function () { + + Tween.update(); + + this.updateCallbacks.forEach( function( callback ){ callback(); } ); + + this.control.update(); + + this.scene.traverse( function( child ){ + if ( child instanceof Infospot + && child.element + && ( this.hoverObject === child + || child.element.style.display !== 'none' + || (child.element.left && child.element.left.style.display !== 'none') + || (child.element.right && child.element.right.style.display !== 'none') ) ) { + if ( this.checkSpriteInViewport( child ) ) { + const { x, y } = this.getScreenVector( child.getWorldPosition( new Vector3() ) ); + child.translateElement( x, y ); + } else { + child.onDismiss(); + } + + } + }.bind( this ) ); + + }, + + /** + * Rendering function to be called on every animation frame + * Render reticle last + * @memberOf Viewer + * @instance + */ + render: function () { + + if ( this.mode === MODES.CARDBOARD || this.mode === MODES.STEREO ) { + + this.renderer.clear(); + this.effect.render( this.scene, this.camera ); + this.effect.render( this.sceneReticle, this.camera ); + + + } else { + + this.renderer.clear(); + this.renderer.render( this.scene, this.camera ); + this.renderer.clearDepth(); + this.renderer.render( this.sceneReticle, this.camera ); + + } + + }, + + /** + * Animate + * @memberOf Viewer + * @instance + */ + animate: function () { + + this.requestAnimationId = window.requestAnimationFrame( this.animate.bind( this ) ); + + this.onChange(); + + }, + + /** + * On change + * @memberOf Viewer + * @instance + */ + onChange: function () { + + this.update(); + this.render(); + + }, + + /** + * Register mouse and touch event on container + * @memberOf Viewer + * @instance + */ + registerMouseAndTouchEvents: function () { + + const options = { passive: false }; + + this.container.addEventListener( 'mousedown' , this.HANDLER_MOUSE_DOWN, options ); + this.container.addEventListener( 'mousemove' , this.HANDLER_MOUSE_MOVE, options ); + this.container.addEventListener( 'mouseup' , this.HANDLER_MOUSE_UP , options ); + this.container.addEventListener( 'touchstart', this.HANDLER_MOUSE_DOWN, options ); + this.container.addEventListener( 'touchend' , this.HANDLER_MOUSE_UP , options ); + + }, + + /** + * Unregister mouse and touch event on container + * @memberOf Viewer + * @instance + */ + unregisterMouseAndTouchEvents: function () { + + this.container.removeEventListener( 'mousedown' , this.HANDLER_MOUSE_DOWN, false ); + this.container.removeEventListener( 'mousemove' , this.HANDLER_MOUSE_MOVE, false ); + this.container.removeEventListener( 'mouseup' , this.HANDLER_MOUSE_UP , false ); + this.container.removeEventListener( 'touchstart', this.HANDLER_MOUSE_DOWN, false ); + this.container.removeEventListener( 'touchend' , this.HANDLER_MOUSE_UP , false ); + + }, + + /** + * Register reticle event + * @memberOf Viewer + * @instance + */ + registerReticleEvent: function () { + + this.addUpdateCallback( this.HANDLER_TAP ); + + }, + + /** + * Unregister reticle event + * @memberOf Viewer + * @instance + */ + unregisterReticleEvent: function () { + + this.removeUpdateCallback( this.HANDLER_TAP ); + + }, + + /** + * Update reticle event + * @memberOf Viewer + * @instance + */ + updateReticleEvent: function () { + + const clientX = this.container.clientWidth / 2 + this.container.offsetLeft; + const clientY = this.container.clientHeight / 2; + + this.removeUpdateCallback( this.HANDLER_TAP ); + this.HANDLER_TAP = this.onTap.bind( this, { clientX, clientY } ); + this.addUpdateCallback( this.HANDLER_TAP ); + + }, + + /** + * Register container and window listeners + * @memberOf Viewer + * @instance + */ + registerEventListeners: function () { + + // Resize Event + window.addEventListener( 'resize' , this.HANDLER_WINDOW_RESIZE, true ); + + // Keyboard Event + window.addEventListener( 'keydown', this.HANDLER_KEY_DOWN, true ); + window.addEventListener( 'keyup' , this.HANDLER_KEY_UP , true ); + + }, + + /** + * Unregister container and window listeners + * @memberOf Viewer + * @instance + */ + unregisterEventListeners: function () { + + // Resize Event + window.removeEventListener( 'resize' , this.HANDLER_WINDOW_RESIZE, true ); + + // Keyboard Event + window.removeEventListener( 'keydown', this.HANDLER_KEY_DOWN, true ); + window.removeEventListener( 'keyup' , this.HANDLER_KEY_UP , true ); + + }, + + /** + * Dispose all scene objects and clear cache + * @memberOf Viewer + * @instance + */ + dispose: function () { + + this.tweenLeftAnimation.stop(); + this.tweenUpAnimation.stop(); + + // Unregister dom event listeners + this.unregisterEventListeners(); + + // recursive disposal on 3d objects + function recursiveDispose ( object ) { + + for ( let i = object.children.length - 1; i >= 0; i-- ) { + + recursiveDispose( object.children[i] ); + object.remove( object.children[i] ); + + } + + if ( object instanceof Panorama || object instanceof Infospot ) { + + object.dispose(); + object = null; + + } else if ( object.dispatchEvent ){ + + object.dispatchEvent( 'dispose' ); + + } + + } + + recursiveDispose( this.scene ); + + // dispose widget + if ( this.widget ) { + + this.widget.dispose(); + this.widget = null; + + } + + // clear cache + if ( Cache && Cache.enabled ) { + + Cache.clear(); + + } + + }, + + /** + * Destroy viewer by disposing and stopping requestAnimationFrame + * @memberOf Viewer + * @instance + */ + destroy: function () { + + this.dispose(); + this.render(); + window.cancelAnimationFrame( this.requestAnimationId ); + + }, + + /** + * On panorama dispose + * @memberOf Viewer + * @instance + */ + onPanoramaDispose: function ( panorama ) { + + if ( panorama instanceof VideoPanorama ) { + + this.hideVideoWidget(); + + } + + if ( panorama === this.panorama ) { + + this.panorama = null; + + } + + }, + + /** + * Load ajax call + * @param {string} url - URL to be requested + * @param {function} [callback] - Callback after request completes + * @memberOf Viewer + * @instance + */ + loadAsyncRequest: function ( url, callback = () => {} ) { + + const request = new window.XMLHttpRequest(); + request.onloadend = function ( event ) { + callback( event ); + }; + request.open( 'GET', url, true ); + request.send( null ); + + }, + + /** + * View indicator in upper left + * @memberOf Viewer + * @instance + */ + addViewIndicator: function () { + + const scope = this; + + function loadViewIndicator ( asyncEvent ) { + + if ( asyncEvent.loaded === 0 ) return; + + const viewIndicatorDiv = asyncEvent.target.responseXML.documentElement; + viewIndicatorDiv.style.width = scope.viewIndicatorSize + 'px'; + viewIndicatorDiv.style.height = scope.viewIndicatorSize + 'px'; + viewIndicatorDiv.style.position = 'absolute'; + viewIndicatorDiv.style.top = '10px'; + viewIndicatorDiv.style.left = '10px'; + viewIndicatorDiv.style.opacity = '0.5'; + viewIndicatorDiv.style.cursor = 'pointer'; + viewIndicatorDiv.id = 'panolens-view-indicator-container'; + + scope.container.appendChild( viewIndicatorDiv ); + + const indicator = viewIndicatorDiv.querySelector( '#indicator' ); + const setIndicatorD = function () { + + scope.radius = scope.viewIndicatorSize * 0.225; + scope.currentPanoAngle = scope.camera.rotation.y - Math$1.degToRad( 90 ); + scope.fovAngle = Math$1.degToRad( scope.camera.fov ) ; + scope.leftAngle = -scope.currentPanoAngle - scope.fovAngle / 2; + scope.rightAngle = -scope.currentPanoAngle + scope.fovAngle / 2; + scope.leftX = scope.radius * Math.cos( scope.leftAngle ); + scope.leftY = scope.radius * Math.sin( scope.leftAngle ); + scope.rightX = scope.radius * Math.cos( scope.rightAngle ); + scope.rightY = scope.radius * Math.sin( scope.rightAngle ); + scope.indicatorD = 'M ' + scope.leftX + ' ' + scope.leftY + ' A ' + scope.radius + ' ' + scope.radius + ' 0 0 1 ' + scope.rightX + ' ' + scope.rightY; + + if ( scope.leftX && scope.leftY && scope.rightX && scope.rightY && scope.radius ) { + + indicator.setAttribute( 'd', scope.indicatorD ); + + } + + }; + + scope.addUpdateCallback( setIndicatorD ); + + const indicatorOnMouseEnter = function () { + + this.style.opacity = '1'; + + }; + + const indicatorOnMouseLeave = function () { + + this.style.opacity = '0.5'; + + }; + + viewIndicatorDiv.addEventListener( 'mouseenter', indicatorOnMouseEnter ); + viewIndicatorDiv.addEventListener( 'mouseleave', indicatorOnMouseLeave ); + } + + this.loadAsyncRequest( DataImage.ViewIndicator, loadViewIndicator ); + + }, + + /** + * Append custom control item to existing control bar + * @param {object} [option={}] - Style object to overwirte default element style. It takes 'style', 'onTap' and 'group' properties. + * @memberOf Viewer + * @instance + */ + appendControlItem: function ( option ) { + + const item = this.widget.createCustomItem( option ); + + if ( option.group === 'video' ) { + + this.widget.videoElement.appendChild( item ); + + } else { + + this.widget.barElement.appendChild( item ); + + } + + return item; + + }, + + /** + * Clear all cached files + * @memberOf Viewer + * @instance + */ + clearAllCache: function () { + + Cache.clear(); + + } + +} ); + +if ( REVISION$1 != THREE_REVISION ) { + + console.warn( `three.js version is not matched. Please consider use the target revision ${THREE_REVISION}` ); + +} + +/** + * Panolens.js + * @author pchen66 + * @namespace PANOLENS + */ +window.TWEEN = Tween; + +export { BasicPanorama, CONTROLS, CameraPanorama, CubePanorama, CubeTextureLoader, DataImage, EmptyPanorama, GoogleStreetviewPanorama, ImageLittlePlanet, ImageLoader, ImagePanorama, Infospot, LittlePlanet, MODES, Media, Panorama, REVISION, Reticle, THREE_REVISION, THREE_VERSION, TextureLoader, VERSION, VideoPanorama, Viewer, Widget }; From 3ef96a906e8e55513b19c98e77df33c8826ea899 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Wed, 2 Mar 2022 16:26:56 +0100 Subject: [PATCH 05/42] rewrite remaining ES5 constructors to class constructors --- docs/BasicPanorama.html | 4 +- docs/CameraPanorama.html | 4 +- docs/Constants.js.html | 28 +- docs/CubePanorama.html | 4 +- docs/DataImage.js.html | 4 +- docs/EmptyPanorama.html | 4 +- docs/GoogleStreetviewLoader.html | 4 +- docs/GoogleStreetviewPanorama.html | 4 +- docs/ImageLittlePlanet.html | 4 +- docs/ImagePanorama.html | 146 +- docs/Infospot.html | 58 +- docs/LittlePlanet.html | 4 +- docs/Media.html | 4 +- docs/PANOLENS.html | 4 +- docs/Panolens.js.html | 4 +- docs/Panorama.html | 206 +- docs/Reticle.html | 40 +- docs/VideoPanorama.html | 4 +- docs/Viewer.html | 159 +- docs/Widget.html | 96 +- docs/external-CardboardEffect.html | 4 +- docs/external-DeviceOrientationControls.html | 4 +- docs/external-OrbitControls.html | 4 +- docs/external-StereoEffect.html | 4 +- docs/index.html | 4 +- docs/infospot_Infospot.js.html | 275 +- docs/interface_Reticle.js.html | 227 +- ...controls_DeviceOrientationControls.js.html | 4 +- docs/lib_controls_OrbitControls.js.html | 4 +- docs/lib_effects_CardboardEffect.js.html | 4 +- docs/lib_effects_StereoEffect.js.html | 4 +- docs/loaders_CubeTextureLoader.js.html | 4 +- docs/loaders_GoogleStreetviewLoader.js.html | 4 +- docs/loaders_ImageLoader.js.html | 87 +- docs/loaders_TextureLoader.js.html | 4 +- docs/media_Media.js.html | 4 +- docs/module-CONTROLS.html | 4 +- docs/module-CONTROL_BUTTONS.html | 307 ++ docs/module-CubeTextureLoader.html | 4 +- docs/module-DataImage.html | 4 +- docs/module-ImageLoader.html | 4 +- docs/module-MODES.html | 4 +- docs/module-OUTPUTS.html | 307 ++ docs/module-REVISION.html | 4 +- docs/module-StereographicShader.html | 4 +- docs/module-THREE_REVISION.html | 4 +- docs/module-THREE_VERSION.html | 4 +- docs/module-TextureLoader.html | 4 +- docs/module-VERSION.html | 4 +- docs/panorama_BasicPanorama.js.html | 4 +- docs/panorama_CameraPanorama.js.html | 4 +- docs/panorama_CubePanorama.js.html | 4 +- docs/panorama_EmptyPanorama.js.html | 4 +- .../panorama_GoogleStreetviewPanorama.js.html | 4 +- docs/panorama_ImageLittlePlanet.js.html | 4 +- docs/panorama_ImagePanorama.js.html | 56 +- docs/panorama_LittlePlanet.js.html | 4 +- docs/panorama_Panorama.js.html | 405 +- docs/panorama_VideoPanorama.js.html | 4 +- docs/shaders_StereographicShader.js.html | 4 +- docs/viewer_Viewer.js.html | 24 +- docs/widget_Widget.js.html | 171 +- package-lock.json | 3734 +++++++++++------ src/lib/controls/OrbitControls.js | 2 +- src/lib/effects/CardboardEffect.js | 221 +- src/lib/effects/StereoEffect.js | 55 +- src/panorama/BasicPanorama.js | 22 +- src/panorama/CameraPanorama.js | 59 +- src/panorama/CubePanorama.js | 50 +- src/panorama/EmptyPanorama.js | 22 +- src/panorama/GoogleStreetviewPanorama.js | 122 +- src/panorama/ImageLittlePlanet.js | 30 +- src/panorama/ImagePanorama.js | 3 - src/panorama/LittlePlanet.js | 139 +- src/panorama/VideoPanorama.js | 140 +- src/viewer/Viewer.js | 1879 ++++----- test/src/panorama/CubePanorama.test.js | 4 +- 77 files changed, 5333 insertions(+), 3925 deletions(-) create mode 100644 docs/module-CONTROL_BUTTONS.html create mode 100644 docs/module-OUTPUTS.html diff --git a/docs/BasicPanorama.html b/docs/BasicPanorama.html index af8c551f..58b17f7b 100644 --- a/docs/BasicPanorama.html +++ b/docs/BasicPanorama.html @@ -41,7 +41,7 @@ -

Home

Github

Classes

Modules

Externals

Events

Namespaces

+

Home

Github

Classes

Modules

Externals

Events

Namespaces

@@ -192,7 +192,7 @@

new Basi
- Documentation generated by JSDoc 3.6.2 on Wed Jun 12 2019 12:14:50 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.6.6 on Wed Mar 02 2022 13:45:12 GMT+0100 (Central European Standard Time) using the docdash theme.
diff --git a/docs/CameraPanorama.html b/docs/CameraPanorama.html index 6ce28346..7c08b118 100644 --- a/docs/CameraPanorama.html +++ b/docs/CameraPanorama.html @@ -41,7 +41,7 @@ -

Home

Github

Classes

Modules

Externals

Events

Namespaces

+

Home

Github

Classes

Modules

Externals

Events

Namespaces

@@ -713,7 +713,7 @@

stop
- Documentation generated by JSDoc 3.6.2 on Wed Jun 12 2019 12:14:50 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.6.6 on Wed Mar 02 2022 13:45:12 GMT+0100 (Central European Standard Time) using the docdash theme.
diff --git a/docs/Constants.js.html b/docs/Constants.js.html index 24b89728..ae59eab1 100644 --- a/docs/Constants.js.html +++ b/docs/Constants.js.html @@ -41,7 +41,7 @@ -

Home

Github

Classes

Modules

Externals

Events

Namespaces

+

Home

Github

Classes

Modules

Externals

Events

Namespaces

@@ -108,7 +108,29 @@

Constants.js

* @property {number} CARDBOARD 2 * @property {number} STEREO 3 */ -export const MODES = { UNKNOWN: 0, NORMAL: 1, CARDBOARD: 2, STEREO: 3 }; +export const MODES = { UNKNOWN: 0, NORMAL: 1, CARDBOARD: 2, STEREO: 3 }; + +/** + * CONTROL_BUTTONS + * @module CONTROL_BUTTONS + * @example PANOLENS.VIEWER.CONTROL_BUTTONS + * @property {string} FULLSCREEN + * @property {string} SETTING + * @property {string} VIDEO + */ +export const CONTROL_BUTTONS = { FULLSCREEN: 'fullscreen', SETTING: 'setting', VIDEO: 'video' }; + +/** + * OUTPUTS + * @module OUTPUTS + * @example PANOLENS.VIEWER.OUTPUTS + * @property {string} NONE + * @property {string} CONSOLE + * @property {string} OVERLAY + */ +export const OUTPUTS = { NONE: 'none', CONSOLE: 'console', OVERLAY: 'overlay' }; + + @@ -122,7 +144,7 @@

Constants.js


- Documentation generated by JSDoc 3.6.2 on Wed Jun 12 2019 12:14:50 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.6.6 on Wed Mar 02 2022 13:45:12 GMT+0100 (Central European Standard Time) using the docdash theme.
diff --git a/docs/CubePanorama.html b/docs/CubePanorama.html index 1a013e3a..81cb33fd 100644 --- a/docs/CubePanorama.html +++ b/docs/CubePanorama.html @@ -41,7 +41,7 @@ -

Home

Github

Classes

Modules

Externals

Events

Namespaces

+

Home

Github

Classes

Modules

Externals

Events

Namespaces

@@ -555,7 +555,7 @@
Parameters:

- Documentation generated by JSDoc 3.6.2 on Wed Jun 12 2019 12:14:50 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.6.6 on Wed Mar 02 2022 13:45:12 GMT+0100 (Central European Standard Time) using the docdash theme.
diff --git a/docs/DataImage.js.html b/docs/DataImage.js.html index bab1a7e8..48576192 100644 --- a/docs/DataImage.js.html +++ b/docs/DataImage.js.html @@ -41,7 +41,7 @@ -

Home

Github

Classes

Modules

Externals

Events

Namespaces

+

Home

Github

Classes

Modules

Externals

Events

Namespaces

@@ -100,7 +100,7 @@

DataImage.js


- Documentation generated by JSDoc 3.6.2 on Wed Jun 12 2019 12:14:50 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.6.6 on Wed Mar 02 2022 13:45:12 GMT+0100 (Central European Standard Time) using the docdash theme.
diff --git a/docs/EmptyPanorama.html b/docs/EmptyPanorama.html index c593a408..f4e73fac 100644 --- a/docs/EmptyPanorama.html +++ b/docs/EmptyPanorama.html @@ -41,7 +41,7 @@ -

Home

Github

Classes

Modules

Externals

Events

Namespaces

+

Home

Github

Classes

Modules

Externals

Events

Namespaces

@@ -192,7 +192,7 @@

new Empt
- Documentation generated by JSDoc 3.6.2 on Wed Jun 12 2019 12:14:50 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.6.6 on Wed Mar 02 2022 13:45:12 GMT+0100 (Central European Standard Time) using the docdash theme.
diff --git a/docs/GoogleStreetviewLoader.html b/docs/GoogleStreetviewLoader.html index b802d126..78a2a6dd 100644 --- a/docs/GoogleStreetviewLoader.html +++ b/docs/GoogleStreetviewLoader.html @@ -41,7 +41,7 @@ -

Home

Github

Classes

Modules

Externals

Events

Namespaces

+

Home

Github

Classes

Modules

Externals

Events

Namespaces

@@ -1255,7 +1255,7 @@
Parameters:

- Documentation generated by JSDoc 3.6.2 on Wed Jun 12 2019 12:14:50 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.6.6 on Wed Mar 02 2022 13:45:12 GMT+0100 (Central European Standard Time) using the docdash theme.
diff --git a/docs/GoogleStreetviewPanorama.html b/docs/GoogleStreetviewPanorama.html index e6baa7cd..ba1e5137 100644 --- a/docs/GoogleStreetviewPanorama.html +++ b/docs/GoogleStreetviewPanorama.html @@ -41,7 +41,7 @@ -

Home

Github

Classes

Modules

Externals

Events

Namespaces

+

Home

Github

Classes

Modules

Externals

Events

Namespaces

@@ -1119,7 +1119,7 @@
Parameters:

- Documentation generated by JSDoc 3.6.2 on Wed Jun 12 2019 12:14:50 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.6.6 on Wed Mar 02 2022 13:45:12 GMT+0100 (Central European Standard Time) using the docdash theme.
diff --git a/docs/ImageLittlePlanet.html b/docs/ImageLittlePlanet.html index 59153fbf..52b17ee6 100644 --- a/docs/ImageLittlePlanet.html +++ b/docs/ImageLittlePlanet.html @@ -41,7 +41,7 @@ -

Home

Github

Classes

Modules

Externals

Events

Namespaces

+

Home

Github

Classes

Modules

Externals

Events

Namespaces

@@ -698,7 +698,7 @@
Parameters:

- Documentation generated by JSDoc 3.6.2 on Wed Jun 12 2019 12:14:50 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.6.6 on Wed Mar 02 2022 13:45:12 GMT+0100 (Central European Standard Time) using the docdash theme.
diff --git a/docs/ImagePanorama.html b/docs/ImagePanorama.html index 49f40518..e1767e48 100644 --- a/docs/ImagePanorama.html +++ b/docs/ImagePanorama.html @@ -41,7 +41,7 @@ -

Home

Github

Classes

Modules

Externals

Events

Namespaces

+

Home

Github

Classes

Modules

Externals

Events

Namespaces

@@ -242,7 +242,7 @@

disposeSource:
@@ -304,142 +304,6 @@

disposeload(src)

- - - - - - -
- - -
Source:
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -
-

Load image asset

-
- - - - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
src - - -* - - - -

Url or image element

- - - - - - - - - - - @@ -465,7 +329,7 @@

onLoadSource:
@@ -601,7 +465,7 @@

resetSource:
@@ -691,7 +555,7 @@

reset
- Documentation generated by JSDoc 3.6.2 on Wed Jun 12 2019 12:14:50 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.6.6 on Wed Mar 02 2022 13:45:13 GMT+0100 (Central European Standard Time) using the docdash theme.
diff --git a/docs/Infospot.html b/docs/Infospot.html index 336e627a..52c8dd7e 100644 --- a/docs/Infospot.html +++ b/docs/Infospot.html @@ -41,7 +41,7 @@ -

Home

Github

Classes

Modules

Externals

Events

Namespaces

+

Home

Github

Classes

Modules

Externals

Events

Namespaces

@@ -340,7 +340,7 @@

addHov
Source:
@@ -531,7 +531,7 @@

addHoverT
Source:
@@ -722,7 +722,7 @@

disposeSource:
@@ -809,7 +809,7 @@

enableRa
Source:
@@ -965,7 +965,7 @@

focusSource:
@@ -1160,7 +1160,7 @@

getContai
Source:
@@ -1271,7 +1271,7 @@

hideSource:
@@ -1427,7 +1427,7 @@

lockH
Source:
@@ -1514,7 +1514,7 @@

onClickSource:
@@ -1651,7 +1651,7 @@

onDismissSource:
@@ -1787,7 +1787,7 @@

onDual
Source:
@@ -1924,7 +1924,7 @@

onHoverSource:
@@ -2061,7 +2061,7 @@

onHoverEnd<
Source:
@@ -2149,7 +2149,7 @@

onHoverSt
Source:
@@ -2286,7 +2286,7 @@

rem
Source:
@@ -2373,7 +2373,7 @@

setContai
Source:
@@ -2512,7 +2512,7 @@

se
Source:
@@ -2599,7 +2599,7 @@

setEle
Source:
@@ -2781,7 +2781,7 @@

setFocu
Source:
@@ -2868,7 +2868,7 @@

setTextSource:
@@ -3004,7 +3004,7 @@

showSource:
@@ -3160,7 +3160,7 @@

trans
Source:
@@ -3319,7 +3319,7 @@

unl
Source:
@@ -3414,7 +3414,7 @@

dismiss

Source:
@@ -3511,7 +3511,7 @@

panolens-container

Source:
@@ -3660,7 +3660,7 @@

panolens-dual-eye-effectSource:
@@ -3809,7 +3809,7 @@

panolens-dual-eye-effectSource:
@@ -3957,7 +3957,7 @@

Type:

- Documentation generated by JSDoc 3.6.2 on Wed Jun 12 2019 12:14:50 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.6.6 on Wed Mar 02 2022 13:45:13 GMT+0100 (Central European Standard Time) using the docdash theme.
diff --git a/docs/LittlePlanet.html b/docs/LittlePlanet.html index b77eb401..25fc394f 100644 --- a/docs/LittlePlanet.html +++ b/docs/LittlePlanet.html @@ -41,7 +41,7 @@ -

Home

Github

Classes

Modules

Externals

Events

Namespaces

+

Home

Github

Classes

Modules

Externals

Events

Namespaces

@@ -372,7 +372,7 @@
Parameters:

- Documentation generated by JSDoc 3.6.2 on Wed Jun 12 2019 12:14:50 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.6.6 on Wed Mar 02 2022 13:45:13 GMT+0100 (Central European Standard Time) using the docdash theme.
diff --git a/docs/Media.html b/docs/Media.html index 8110cbbc..1088e946 100644 --- a/docs/Media.html +++ b/docs/Media.html @@ -41,7 +41,7 @@ -

Home

Github

Classes

Modules

Externals

Events

Namespaces

+

Home

Github

Classes

Modules

Externals

Events

Namespaces

@@ -1862,7 +1862,7 @@
Type:

- Documentation generated by JSDoc 3.6.2 on Wed Jun 12 2019 12:14:50 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.6.6 on Wed Mar 02 2022 13:45:13 GMT+0100 (Central European Standard Time) using the docdash theme.
diff --git a/docs/PANOLENS.html b/docs/PANOLENS.html index 43b014e7..14cc5d19 100644 --- a/docs/PANOLENS.html +++ b/docs/PANOLENS.html @@ -41,7 +41,7 @@ -

Home

Github

Classes

Modules

Externals

Events

Namespaces

+

Home

Github

Classes

Modules

Externals

Events

Namespaces

@@ -163,7 +163,7 @@


- Documentation generated by JSDoc 3.6.2 on Wed Jun 12 2019 12:14:50 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.6.6 on Wed Mar 02 2022 13:45:13 GMT+0100 (Central European Standard Time) using the docdash theme.
diff --git a/docs/Panolens.js.html b/docs/Panolens.js.html index da5ba978..3f3d09d5 100644 --- a/docs/Panolens.js.html +++ b/docs/Panolens.js.html @@ -41,7 +41,7 @@ -

Home

Github

Classes

Modules

Externals

Events

Namespaces

+

Home

Github

Classes

Modules

Externals

Events

Namespaces

@@ -99,7 +99,7 @@

Panolens.js


- Documentation generated by JSDoc 3.6.2 on Wed Jun 12 2019 12:14:50 GMT-0700 (Pacific Daylight Time) using the docdash theme. + Documentation generated by JSDoc 3.6.6 on Wed Mar 02 2022 13:45:12 GMT+0100 (Central European Standard Time) using the docdash theme.
diff --git a/docs/Panorama.html b/docs/Panorama.html index e7879dad..3ad54aa9 100644 --- a/docs/Panorama.html +++ b/docs/Panorama.html @@ -41,7 +41,7 @@ -

Home

Github

Classes

Modules

Externals

Events

Namespaces

+

Home

Github

Classes

Modules

Externals

Events

Namespaces

@@ -249,144 +249,6 @@
Parameters:

Methods

- - - - -

add(object)

- - - - - - -
- - -
Source:
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -
-

Adding an object -To counter the scale.x = -1, it will automatically add an -empty object with inverted scale on x

-
- - - - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
object - - -THREE.Object3D - - - -

The object to be added

- - - - - - - - - - - - - - - - - - - - @@ -403,7 +265,7 @@

disposeSource:
@@ -490,7 +352,7 @@

fadeInSource:
@@ -582,7 +444,7 @@

fadeOutSource:
@@ -669,7 +531,7 @@

getZoomLe
Source:
@@ -778,7 +640,7 @@