Input: 调整编辑器的尺寸和布局 Resize editor layout [CODESPLIT] function ( width , height ) { width = width || null ; height = height || null ; var state = this . state ; var editor = this . editor ; var preview = this . preview ; var toolbar = this . toolbar ; var settings = this . settings ; var codeMirror = this . codeMirror ; if ( width ) { editor . css ( "width" , ( typeof width === "number" ) ? width + "px" : width ) ; } if ( settings . autoHeight && ! state . fullscreen && ! state . preview ) { editor . css ( "height" , "auto" ) ; codeMirror . css ( "height" , "auto" ) ; } else { if ( height ) { editor . css ( "height" , ( typeof height === "number" ) ? height + "px" : height ) ; } if ( state . fullscreen ) { editor . height ( $ ( window ) . height ( ) ) ; } if ( settings . toolbar && ! settings . readOnly ) { codeMirror . css ( "margin-top" , toolbar . height ( ) + 1 ) . height ( editor . height ( ) - toolbar . height ( ) ) ; } else { codeMirror . css ( "margin-top" , 0 ) . height ( editor . height ( ) ) ; } } if ( settings . watch ) { codeMirror . width ( editor . width ( ) / 2 ) ; preview . width ( ( ! state . preview ) ? editor . width ( ) / 2 : editor . width ( ) ) ; this . previewContainer . css ( "padding" , settings . autoHeight ? "20px 20px 50px 40px" : "20px" ) ; if ( settings . toolbar && ! settings . readOnly ) { preview . css ( "top" , toolbar . height ( ) + 1 ) ; } else { preview . css ( "top" , 0 ) ; } if ( settings . autoHeight && ! state . fullscreen && ! state . preview ) { preview . height ( "" ) ; } else { var previewHeight = ( settings . toolbar && ! settings . readOnly ) ? editor . height ( ) - toolbar . height ( ) : editor . height ( ) ; preview . height ( previewHeight ) ; } } else { codeMirror . width ( editor . width ( ) ) ; preview . hide ( ) ; } if ( state . loaded ) { $ . proxy ( settings . onresize , this ) ( ) ; } return this ; }


Input:
解析和保存Markdown代码 Parse & Saving Markdown source code [CODESPLIT] function ( ) { if ( timer === null ) { return this ; } var _this = this ; var state = this . state ; var settings = this . settings ; var cm = this . cm ; var cmValue = cm . getValue ( ) ; var previewContainer = this . previewContainer ; if ( settings . mode !== "gfm" && settings . mode !== "markdown" ) { this . markdownTextarea . val ( cmValue ) ; return this ; } var marked = editormd . $marked ; var markdownToC = this . markdownToC = [ ] ; var rendererOptions = this . markedRendererOptions = { toc : settings . toc , tocm : settings . tocm , tocStartLevel : settings . tocStartLevel , pageBreak : settings . pageBreak , taskList : settings . taskList , emoji : settings . emoji , tex : settings . tex , atLink : settings . atLink , // for @link
 emailLink : settings . emailLink , // for mail address auto link
 flowChart : settings . flowChart , sequenceDiagram : settings . sequenceDiagram , previewCodeHighlight : settings . previewCodeHighlight , } ; var markedOptions = this . markedOptions = { renderer : editormd . markedRenderer ( markdownToC , rendererOptions ) , gfm : true , tables : true , breaks : true , pedantic : false , sanitize : ( settings . htmlDecode ) ? false : true , // 关闭忽略HTML标签，即开启识别HTML标签，默认为false
 smartLists : true , smartypants : true } ; marked . setOptions ( markedOptions ) ; var newMarkdownDoc = editormd . $marked ( cmValue , markedOptions ) ; //console.info("cmValue", cmValue, newMarkdownDoc);
 newMarkdownDoc = editormd . filterHTMLTags ( newMarkdownDoc , settings . htmlDecode ) ; //console.error("cmValue", cmValue, newMarkdownDoc);
 this . markdownTextarea . text ( cmValue ) ; cm . save ( ) ; if ( settings . saveHTMLToTextarea ) { this . htmlTextarea . text ( newMarkdownDoc ) ; } if ( settings . watch || ( ! settings . watch && state . preview ) ) { previewContainer . html ( newMarkdownDoc ) ; this . previewCodeHighlight ( ) ; if ( settings . toc ) { var tocContainer = ( settings . tocContainer === "" ) ? previewContainer : $ ( settings . tocContainer ) ; var tocMenu = tocContainer . find ( "." + this . classPrefix + "toc-menu" ) ; tocContainer . attr ( "previewContainer" , ( settings . tocContainer === "" ) ? "true" : "false" ) ; if ( settings . tocContainer !== "" && tocMenu . length > 0 ) { tocMenu . remove ( ) ; } editormd . markdownToCRenderer ( markdownToC , tocContainer , settings . tocDropdown , settings . tocStartLevel ) ; if ( settings . tocDropdown || tocContainer . find ( "." + this . classPrefix + "toc-menu" ) . length > 0 ) { editormd . tocDropdownMenu ( tocContainer , ( settings . tocTitle !== "" ) ? settings . tocTitle : this . lang . tocTitle ) ; } if ( settings . tocContainer !== "" ) { previewContainer . find ( ".markdown-toc" ) . css ( "border" , "none" ) ; } } if ( settings . tex ) { if ( ! editormd . kaTeXLoaded && settings . autoLoadModules ) { editormd . loadKaTeX ( function ( ) { editormd . $katex = katex ; editormd . kaTeXLoaded = true ; _this . katexRender ( ) ; } ) ; } else { editormd . $katex = katex ; this . katexRender ( ) ; } } if ( settings . flowChart || settings . sequenceDiagram ) { flowchartTimer = setTimeout ( function ( ) { clearTimeout ( flowchartTimer ) ; _this . flowChartAndSequenceDiagramRender ( ) ; flowchartTimer = null ; } , 10 ) ; } if ( state . loaded ) { $ . proxy ( settings . onchange , this ) ( ) ; } } return this ; }


Input: 追加markdown append Markdown to editor [CODESPLIT] function ( md ) { var settings = this . settings ; var cm = this . cm ; cm . setValue ( cm . getValue ( ) + md ) ; return this ; }


Input: 开启实时预览 Enable real - time watching [CODESPLIT] function ( callback ) { var settings = this . settings ; if ( $ . inArray ( settings . mode , [ "gfm" , "markdown" ] ) < 0 ) { return this ; } this . state . watching = settings . watch = true ; this . preview . show ( ) ; if ( this . toolbar ) { var watchIcon = settings . toolbarIconsClass . watch ; var unWatchIcon = settings . toolbarIconsClass . unwatch ; var icon = this . toolbar . find ( ".fa[name=watch]" ) ; icon . parent ( ) . attr ( "title" , settings . lang . toolbar . watch ) ; icon . removeClass ( unWatchIcon ) . addClass ( watchIcon ) ; } this . codeMirror . css ( "border-right" , "1px solid #ddd" ) . width ( this . editor . width ( ) / 2 ) ; timer = 0 ; this . save ( ) . resize ( ) ; if ( ! settings . onwatch ) { settings . onwatch = callback || function ( ) { } ; } $ . proxy ( settings . onwatch , this ) ( ) ; return this ; }


Input: 关闭实时预览 Disable real - time watching [CODESPLIT] function ( callback ) { var settings = this . settings ; this . state . watching = settings . watch = false ; this . preview . hide ( ) ; if ( this . toolbar ) { var watchIcon = settings . toolbarIconsClass . watch ; var unWatchIcon = settings . toolbarIconsClass . unwatch ; var icon = this . toolbar . find ( ".fa[name=watch]" ) ; icon . parent ( ) . attr ( "title" , settings . lang . toolbar . unwatch ) ; icon . removeClass ( watchIcon ) . addClass ( unWatchIcon ) ; } this . codeMirror . css ( "border-right" , "none" ) . width ( this . editor . width ( ) ) ; this . resize ( ) ; if ( ! settings . onunwatch ) { settings . onunwatch = callback || function ( ) { } ; } $ . proxy ( settings . onunwatch , this ) ( ) ; return this ; }


Input:
隐藏编辑器部分，只预览HTML Enter preview html state [CODESPLIT] function ( ) { var _this = this ; var editor = this . editor ; var preview = this . preview ; var toolbar = this . toolbar ; var settings = this . settings ; var codeMirror = this . codeMirror ; var previewContainer = this . previewContainer ; if ( $ . inArray ( settings . mode , [ "gfm" , "markdown" ] ) < 0 ) { return this ; } if ( settings . toolbar && toolbar ) { toolbar . toggle ( ) ; toolbar . find ( ".fa[name=preview]" ) . toggleClass ( "active" ) ; } codeMirror . toggle ( ) ; var escHandle = function ( event ) { if ( event . shiftKey && event . keyCode === 27 ) { _this . previewed ( ) ; } } ; if ( codeMirror . css ( "display" ) === "none" ) // 为了兼容Zepto，而不使用codeMirror.is(":hidden")
 { this . state . preview = true ; if ( this . state . fullscreen ) { preview . css ( "background" , "#fff" ) ; } editor . find ( "." + this . classPrefix + "preview-close-btn" ) . show ( ) . bind ( editormd . mouseOrTouch ( "click" , "touchend" ) , function ( ) { _this . previewed ( ) ; } ) ; if ( ! settings . watch ) { this . save ( ) ; } else { previewContainer . css ( "padding" , "" ) ; } previewContainer . addClass ( this . classPrefix + "preview-active" ) ; preview . show ( ) . css ( { position : "" , top : 0 , width : editor . width ( ) , height : ( settings . autoHeight && ! this . state . fullscreen ) ? "auto" : editor . height ( ) } ) ; if ( this . state . loaded ) { $ . proxy ( settings . onpreviewing , this ) ( ) ; } $ ( window ) . bind ( "keyup" , escHandle ) ; } else { $ ( window ) . unbind ( "keyup" , escHandle ) ; this . previewed ( ) ; } }


Input: 显示编辑器部分，退出只预览HTML Exit preview html state [CODESPLIT] function ( ) { var editor = this . editor ; var preview = this . preview ; var toolbar = this . toolbar ; var settings = this . settings ; var previewContainer = this . previewContainer ; var previewCloseBtn = editor . find ( "." + this . classPrefix + "preview-close-btn" ) ; this . state . preview = false ; this . codeMirror . show ( ) ; if ( settings . toolbar ) { toolbar . show ( ) ; } preview [ ( settings . watch ) ? "show" : "hide" ] ( ) ; previewCloseBtn . hide ( ) . unbind ( editormd . mouseOrTouch ( "click" , "touchend" ) ) ; previewContainer . removeClass ( this . classPrefix + "preview-active" ) ; if ( settings . watch ) { previewContainer . css ( "padding" , "20px" ) ; } preview . css ( { background : null , position : "absolute" , width : editor . width ( ) / 2 , height : ( settings . autoHeight && ! this . state . fullscreen ) ? "auto" : editor . height ( ) - toolbar . height ( ) , top : ( settings . toolbar ) ? toolbar . height ( ) : 0 } ) ; if ( this . state . loaded ) { $ . proxy ( settings . onpreviewed , this ) ( ) ; } return this ; }


Input: 编辑器全屏显示 Fullscreen show [CODESPLIT] function ( ) { var _this = this ; var state = this . state ; var editor = this . editor ; var preview = this . preview ; var toolbar = this . toolbar ; var settings = this . settings ; var fullscreenClass = this . classPrefix + "fullscreen" ; if ( toolbar ) { toolbar . find ( ".fa[name=fullscreen]" ) . parent ( ) . toggleClass ( "active" ) ; } var escHandle = function ( event ) { if ( ! event . shiftKey && event . keyCode === 27 ) { if ( state . fullscreen ) { _this . fullscreenExit ( ) ; } } } ; if ( ! editor . hasClass ( fullscreenClass ) ) { state . fullscreen = true ; $ ( "html,body" ) . css ( "overflow" , "hidden" ) ; editor . css ( { width : $ ( window ) . width ( ) , height : $ ( window ) . height ( ) } ) . addClass ( fullscreenClass ) ; this . resize ( ) ; $ . proxy ( settings . onfullscreen , this ) ( ) ; $ ( window ) . bind ( "keyup" , escHandle ) ; } else { $ ( window ) . unbind ( "keyup" , escHandle ) ; this . fullscreenExit ( ) ; } return this ; }


Input: 编辑器退出全屏显示 Exit fullscreen state [CODESPLIT] function ( ) { var editor = this . editor ; var settings = this . settings ; var toolbar = this . toolbar ; var fullscreenClass = this . classPrefix + "fullscreen" ; this . state . fullscreen = false ; if ( toolbar ) { toolbar . find ( ".fa[name=fullscreen]" ) . parent ( ) . removeClass ( "active" ) ; } $ ( "html,body" ) . css ( "overflow" , "" ) ; editor . css ( { width : editor . data ( "oldWidth" ) , height : editor . data ( "oldHeight" ) } ) . removeClass ( fullscreenClass ) ; this . resize ( ) ; $ . proxy ( settings . onfullscreenExit , this ) ( ) ; return this ; }


Input: 加载并执行插件 Load and execute the plugin [CODESPLIT] function ( name , path ) { var _this = this ; var cm = this . cm ; var settings = this . settings ; path = settings . pluginPath + path ; if ( typeof define === "function" ) { if ( typeof this [ name ] === "undefined" ) { alert ( "Error: " + name + " plugin is not found, you are not load this plugin." ) ; return this ; } this [ name ] ( cm ) ; return this ; } if ( $ . inArray ( path , editormd . loadFiles . plugin ) < 0 ) { editormd . loadPlugin ( path , function ( ) { editormd . loadPlugins [ name ] = _this [ name ] ; _this [ name ] ( cm ) ; } ) ; } else { $ . proxy ( editormd . loadPlugins [ name ] , this ) ( cm ) ; } return this ; }


Input: 搜索替换 Search & replace [CODESPLIT] function ( command ) { var settings = this . settings ; if ( ! settings . searchReplace ) { alert ( "Error: settings.searchReplace == false" ) ; return this ; } if ( ! settings . readOnly ) { this . cm . execCommand ( command || "find" ) ; } return this ; }


Input: Succinct definitions of keyword token types [CODESPLIT] function kw ( name , options = { } ) { options . keyword = name return keywords [ name ] = new TokenType ( name , options ) }


Input: This has a complexity linear to the value of the code . The assumption is that looking up astral identifier characters is rare . [CODESPLIT] function isInAstralSet ( code , set ) { let pos = 0x10000 for ( let i = 0 ; i < set . length ; i += 2 ) { pos += set [ i ] if ( pos > code ) return false pos += set [ i + 1 ] if ( pos >= code ) return true } }


Input: Transform token names to formats expected by Sassdoc for descriptions and aliases [CODESPLIT] function transformMetadata ( metadata ) { const namesRegEx = new RegExp ( metadata . tokens . map ( token => token . name ) . join ( '|' ) , 'g' ) ; const replaceMap = { } ; metadata . tokens . map ( token => { replaceMap [ token . name ] = formatTokenName ( token . name ) ; } ) ; metadata . tokens . forEach ( ( token , i ) => { // interactive01 to `$interactive-01` if ( token . role ) { token . role . forEach ( ( role , j ) => { metadata . tokens [ i ] . role [ j ] = role . replace ( namesRegEx , match => { return '`$' + replaceMap [ match ] + '`' ; } ) ; } ) ; } // brand01 to brand-01 if ( token . alias ) { token . alias = formatTokenName ( token . alias ) ; } } ) ; return metadata ; }


Input: Rough heuristic used to find the package name for a given file . Idea is to move upwards looking for directories that have a package . json file . Once we find one we report back the name from that file . [CODESPLIT] async function findPackageFor ( filepath ) { let directory = filepath ; while ( directory !== '/' ) { const directoryToSearch = path . dirname ( directory ) ; const files = await fs . readdir ( directoryToSearch ) ; if ( files . indexOf ( 'package.json' ) !== - 1 ) { const packageJson = await fs . readJson ( path . join ( directoryToSearch , 'package.json' ) ) ; return packageJson . name ; } directory = path . resolve ( directory , '..' ) ; } throw new Error ( ` ${ filepath } ` ) ; }


Input: Copyright IBM Corp . 2016 2018 [CODESPLIT] function svgToggleClass ( svg , name , forceAdd ) { const list = svg . getAttribute ( 'class' ) . trim ( ) . split ( / \s+ / ) ; const uniqueList = Object . keys ( list . reduce ( ( o , item ) => Object . assign ( o , { [ item ] : 1 } ) , { } ) ) ; const index = uniqueList . indexOf ( name ) ; const found = index >= 0 ; const add = forceAdd === undefined ? ! found : forceAdd ; if ( found === ! add ) { if ( add ) { uniqueList . push ( name ) ; } else { uniqueList . splice ( index , 1 ) ; } svg . setAttribute ( 'class' , uniqueList . join ( ' ' ) ) ; } }


Input: Inspired by Vue CLI : https : // github . com / vuejs / vue - cli / blob / 31e1b4995edef3d2079da654deedffe002a1d689 / packages / %40vue / cli / bin / vue . js#L172 [CODESPLIT] function cleanArgs ( command ) { return command . options . reduce ( ( acc , option ) => { // TODO: add case for reserved words from commander, like options // Add case for mapping `--foo-bar` to `fooBar` const key = option . long . replace ( / ^-- / , '' ) . split ( '-' ) . map ( ( word , i ) => { if ( i === 0 ) { return word ; } return word [ 0 ] . toUpperCase ( ) + word . slice ( 1 ) ; } ) . join ( '' ) ; // If an option is not present and Command has a method with the same name // it should not be copied if ( typeof command [ key ] !== 'function' ) { return { ... acc , [ key ] : command [ key ] , } ; } return acc ; } , { } ) ; }


Input: Copyright IBM Corp . 2018 2018 [CODESPLIT] async function flatMapAsync ( source , mapFn ) { const results = await Promise . all ( source . map ( mapFn ) ) ; return results . reduce ( ( acc , result ) => acc . concat ( result ) , [ ] ) ; }


Input: Create a JSON file of documented Sass items [CODESPLIT] async function createJson ( sourceDir , config ) { config = config || { } ; return sassdoc . parse ( sourceDir , config ) . then ( data => { return data ; } , err => { console . error ( err ) ; } ) ; }


Input: Remove duplicate objects in require and usedBy arrays . Array objects have name and type properties sometimes nested in a context object . [CODESPLIT] function dedupeArray ( arr ) { return arr . reduce ( ( p , item ) => { const type = item . type || item . context . type ; const name = item . name || item . context . name ; const id = [ type , name ] . join ( '|' ) ; if ( p . temp . indexOf ( id ) === - 1 ) { p . out . push ( item ) ; p . temp . push ( id ) ; } return p ; } , { temp : [ ] , out : [ ] } ) . out ; }


Input: Create GitHub - flavored markdown anchor link [CODESPLIT] function createAnchorLink ( name , heading ) { const anchorLink = heading . toLowerCase ( ) . replace ( /   / g , '-' ) . replace ( / [`~!@#$%^&*()+=<>?,./:;"'|{}\[\]\\–—]/g,  ' ' )   . replace ( / [　。？！，、；：“”【】（）〔〕［］﹃﹄“”‘’﹁﹂—…－～《》〈〉「」]/g,    '' ) ; return ` ${ name } ${ anchorLink } ` ; }


Input: Create markdown for Sassdoc item ( function mixin placeholder variable ) [CODESPLIT] function createMarkdownItem ( item ) { let str = '' ; if ( ! item . context ) return '' ; let status = item . access === 'public' ? '✅' : ' ';  if ( item . deprecated || item . deprecated === '' ) { status += '⚠';  } // Name str += ` \n \n ${ status } ${ createUniqueName ( item . context . name , item . context . type ) } ` ; // Description if ( item . description ) { str += ` \n \n ${ item . description . trim ( ) } ` ; } // Value (variables) if ( item . context . value ) { str += ` \` \` \` ${ item . context . name } ${ item . context . value } \` \` \` ` ; } // Code (mixins) if ( item . context . code ) { let paramStr = '' ; if ( item . parameter ) { item . parameter . forEach ( param => { if ( paramStr ) paramStr += ` ` ; paramStr += ` ${ param . name } ` ; if ( param . default ) paramStr += ` ${ param . default } ` ; } ) ; } str += ` \` \` \` ${ item . context . type } ${ item . context . name } ${ paramStr } ${ item . context . code } \` \` \` ` ; } // Parameters if ( item . parameter && item . parameter . length ) { str += ` ` ; item . parameter . forEach ( param => { const paramType = param . type ? ` \` ${ param . type . replace ( / \| / g , ` \\ ` ) } \` ` : '—';  const paramDefault = param . default ? ` \` ${ param . default } \` ` : '—';  const row = ` \n \` ${ param . name } \` ${ param . description || '—'}  | pa ramType}  | pa ramDefault}  |   str += row ; } ) ; } // Example if ( item . example && item . example . length ) { str += ` \n \n ` ; if ( item . example [ 0 ] . description ) { str += ` ${ item . example [ 0 ] . description } ` ; } str += ` \` \` \` ${ item . example [ 0 ] . type } ${ item . example [ 0 ] . code } \` \` \` ` ; } // Bullets const metadata = [ ] ; const groupName = createGroupName ( item . group ) ; metadata . push ( { key : 'Group' , value : createAnchorLink ( groupName , groupName ) , } ) ; if ( item . return ) { metadata . push ( { key : 'Returns' , value : ` \` ${ item . return . type } \` ${ item . return . description || '' } ` , } ) ; } if ( item . type ) { metadata . push ( { key : 'Type' , value : ` \` ${ item . type } \` ` , } ) ; } if ( item . alias ) { metadata . push ( { key : 'Alias' , value : ` \` ${ item . alias } \` ` , } ) ; } if ( item . aliased ) { let subbullets = '' ; item . aliased . forEach ( aliased => { subbullets += ` \n \` ${ aliased } \` ` ; } ) ; metadata . push ( { key : 'Aliased' , value : subbullets , } ) ; } if ( item . content ) { metadata . push ( { key : 'Content' , value : item . content , } ) ; } if ( item . require && item . require . length ) { let subbullets = '' ; dedupeArray ( item . require ) . forEach ( requires => { subbullets += ` \n ${ createAnchorLink ( ` ${ requires . name } ${ requires . type } ` , createUniqueName ( requires . name , requires . type ) ) } ` ; } ) ; metadata . push ( { key : 'Requires' , value : subbullets , } ) ; } if ( item . usedBy && item . usedBy . length ) { let subbullets = '' ; dedupeArray ( item . usedBy ) . forEach ( usedBy => { subbullets += ` \n ${ createAnchorLink ( ` ${ usedBy . context . name } ${ usedBy . context . type } ` , createUniqueName ( usedBy . context . name , usedBy . context . type ) ) } ` ; } ) ; metadata . push ( { key : 'Used by' , value : subbullets , } ) ; } // if (item.since && item.since.length) { //   metadata.push({ //     key: 'Since', //     value: item.since[0].version, //   }); // } if ( item . link && item . link . length ) { let subbullets = '' ; item . link . forEach ( link => { subbullets += ` \n ${ link . caption || 'Link' } ${ link . url } ` ; } ) ; metadata . push ( { key : 'Links' , value : subbullets , } ) ; } if ( item . deprecated || item . deprecated === '' ) { metadata . push ( { key : 'Deprecated' , value : item . deprecated || 'This may not be available in future releases' , } ) ; } if ( metadata . length ) { str += '\n' ; metadata . forEach ( meta => { str += ` \n ${ meta . key } ${ meta . value } ` ; } ) ; } return str ; }


Input: Create a markdown file of documented Sass items [CODESPLIT] async function createMarkdown ( sourceDir , config ) { config = config || { } ; return sassdoc . parse ( sourceDir , config ) . then ( data => { let markdownFile = '' ; const documentedItems = data . filter ( ( item , index ) => item . access === 'public' || item . access === 'private' ) ; markdownFile += ` ` ; let currentGroup = '' ; documentedItems . forEach ( item => { const itemGroup = createGroupName ( item . group ) ; if ( itemGroup !== currentGroup ) { markdownFile += ` \n \n ${ itemGroup } ` ; currentGroup = itemGroup ; } markdownFile += createMarkdownItem ( item ) ; } ) ; return prettier . format ( toc . insert ( markdownFile , { slugify } ) , prettierOptions ) ; } , err => { console . error ( err ) ; } ) ; }


Input: The goal here is to create a top - level build folder with content to be displayed in the gh - pages branch . Specifically we want packages available at : packages / <package - name > / examples / <example - name > to be mirrored over in the build folder at : build / <package - name > / examples / <example - name > . [CODESPLIT] async function main ( ) { reporter . info ( 'Building examples...' ) ; await fs . remove ( BUILD_DIR ) ; await fs . ensureDir ( BUILD_DIR ) ; const packageNames = await fs . readdir ( PACKAGES_DIR ) ; const packages = await Promise . all ( packageNames . map ( async name => { // Verify that each file that we read from the packages directory is // actually a folder. Typically used to catch `.DS_store` files that // accidentally appear when opening with MacOS Finder const filepath = path . join ( PACKAGES_DIR , name ) ; const stat = await fs . lstat ( filepath ) ; const descriptor = { filepath , name , } ; if ( ! stat . isDirectory ( ) ) { throw new Error ( ` ${ name } ${ filepath } ` ) ; } // Try and figure out if the package has an examples directory, if not // then we can skip it const examplesDir = path . join ( filepath , 'examples' ) ; if ( ! ( await fs . pathExists ( examplesDir ) ) ) { return descriptor ; } const examples = ( await fs . readdir ( examplesDir ) ) . filter ( example => { return example !== '.yarnrc' && ! IGNORE_EXAMPLE_DIRS . has ( example ) ; } ) ; return { ... descriptor , examples : examples . map ( name => ( { filepath : path . join ( examplesDir , name ) , name , } ) ) , } ; } ) ) ; const packagesWithExamples = packages . filter ( pkg => Array . isArray ( pkg . examples ) && pkg . examples . length !== 0 ) ; await Promise . all ( packagesWithExamples . map ( async pkg => { reporter . info ( ` \` ${ pkg . name } \` ` ) ; const { examples , filepath , name } = pkg ; const packageDir = path . join ( BUILD_DIR , name , 'examples' ) ; await fs . ensureDir ( packageDir ) ; await Promise . all ( examples . map ( async example => { reporter . info ( ` \` ${ example . name } \` \` ${ pkg . name } \` ` ) ; const exampleDir = path . join ( packageDir , example . name ) ; const exampleBuildDir = path . join ( example . filepath , 'build' ) ; const packageJsonPath = path . join ( example . filepath , 'package.json' ) ; const packageJson = await fs . readJson ( packageJsonPath ) ; await fs . ensureDir ( exampleDir ) ; if ( packageJson . scripts . build ) { spawn . sync ( 'yarn' , [ 'install' ] , { stdio : 'ignore' , cwd : example . filepath , } ) ; spawn . sync ( 'yarn' , [ 'build' ] , { stdio : 'ignore' , cwd : example . filepath , } ) ; } if ( await fs . pathExists ( exampleBuildDir ) ) { await fs . copy ( exampleBuildDir , exampleDir ) ; return ; } await fs . copy ( example . filepath , exampleDir , { filter ( src , dest ) { const relativePath = path . relative ( example . filepath , src ) ; if ( relativePath . includes ( 'node_modules' ) ) { return false ; } if ( relativePath [ 0 ] === '.' ) { return false ; } return true ; } , } ) ; reporter . success ( ` \` ${ example . name } \` \` ${ pkg . name } \` ` ) ; } ) ) ; reporter . success ( ` \` ${ pkg . name } \` ` ) ; } ) ) ; const links = packagesWithExamples . reduce ( ( html , pkg ) => { const links = pkg . examples . reduce ( ( acc , example ) => { const href = ` ${ pkg . name } ${ example . name } ` ; return acc + ` ${ href } ${ example . name } ` ; } , '' ) ; return ( html + '\n' + ` ${ pkg . name } ${ links } ` ) ; } , '' ) ; const indexFile = ` ${ links } ` ; await fs . writeFile ( path . join ( BUILD_DIR , 'index.html' ) , indexFile ) ; // Copy icons over, useful for adding download links await fs . copy ( path . resolve ( __dirname , '../packages/icons/svg' ) , path . join ( BUILD_DIR , 'icons/svg' ) ) ; }


Input: this . options create - component mix - in creates prototype chain so that options given in constructor argument wins over the one defined in static options property Flatpickr wants flat structure of object instead [CODESPLIT] function flattenOptions ( options ) { const o = { } ; // eslint-disable-next-line guard-for-in, no-restricted-syntax for ( const key in options ) { o [ key ] = options [ key ] ; } return o ; }


Input: Append list item [CODESPLIT] function append ( str , prefix = '' ) { const item = document . createElement ( 'li' ) ; item . textContent = prefix + str ; list . appendChild ( item ) ; }


Input: Sorting method for multires nodes . [CODESPLIT] function multiresNodeSort ( a , b ) { // Base tiles are always first if ( a . level == 1 && b . level != 1 ) { return - 1 ; } if ( b . level == 1 && a . level != 1 ) { return 1 ; } // Higher timestamp first return b . timestamp - a . timestamp ; }


Input: Sorting method for multires node rendering . [CODESPLIT] function multiresNodeRenderSort ( a , b ) { // Lower zoom levels first if ( a . level != b . level ) { return a . level - b . level ; } // Lower distance from center first return a . diff - b . diff ; }


Input: Draws multires nodes . [CODESPLIT] function multiresDraw ( ) { if ( ! program . drawInProgress ) { program . drawInProgress = true ; gl . clear ( gl . COLOR_BUFFER_BIT ) ; for ( var i = 0 ; i < program . currentNodes . length ; i ++ ) { if ( program . currentNodes [ i ] . textureLoaded > 1 ) { //var color = program.currentNodes[i].color; //gl.uniform4f(program.colorUniform, color[0], color[1], color[2], 1.0); // Bind vertex buffer and pass vertices to WebGL gl . bindBuffer ( gl . ARRAY_BUFFER , cubeVertBuf ) ; gl . bufferData ( gl . ARRAY_BUFFER , new Float32Array ( program . currentNodes [ i ] . vertices ) , gl . STATIC_DRAW ) ; gl . vertexAttribPointer ( program . vertPosLocation , 3 , gl . FLOAT , false , 0 , 0 ) ; // Prep for texture gl . bindBuffer ( gl . ARRAY_BUFFER , cubeVertTexCoordBuf ) ; gl . vertexAttribPointer ( program . texCoordLocation , 2 , gl . FLOAT , false , 0 , 0 ) ; // Bind texture and draw tile gl . bindTexture ( gl . TEXTURE_2D , program . currentNodes [ i ] . texture ) ; // Bind program.currentNodes[i].texture to TEXTURE0 gl . drawElements ( gl . TRIANGLES , 6 , gl . UNSIGNED_SHORT , 0 ) ; } } program . drawInProgress = false ; } }


Input: Creates new multires node . [CODESPLIT] function MultiresNode ( vertices , side , level , x , y , path ) { this . vertices = vertices ; this . side = side ; this . level = level ; this . x = x ; this . y = y ; this . path = path . replace ( '%s' , side ) . replace ( '%l' , level ) . replace ( '%x' , x ) . replace ( '%y' , y ) ; }


Input: Rotates a 3x3 matrix . [CODESPLIT] function rotateMatrix ( m , angle , axis ) { var s = Math . sin ( angle ) ; var c = Math . cos ( angle ) ; if ( axis == 'x' ) { return [ m [ 0 ] , c * m [ 1 ] + s * m [ 2 ] , c * m [ 2 ] - s * m [ 1 ] , m [ 3 ] , c * m [ 4 ] + s * m [ 5 ] , c * m [ 5 ] - s * m [ 4 ] , m [ 6 ] , c * m [ 7 ] + s * m [ 8 ] , c * m [ 8 ] - s * m [ 7 ] ] ; } if ( axis == 'y' ) { return [ c * m [ 0 ] - s * m [ 2 ] , m [ 1 ] , c * m [ 2 ] + s * m [ 0 ] , c * m [ 3 ] - s * m [ 5 ] , m [ 4 ] , c * m [ 5 ] + s * m [ 3 ] , c * m [ 6 ] - s * m [ 8 ] , m [ 7 ] , c * m [ 8 ] + s * m [ 6 ] ] ; } if ( axis == 'z' ) { return [ c * m [ 0 ] + s * m [ 1 ] , c * m [ 1 ] - s * m [ 0 ] , m [ 2 ] , c * m [ 3 ] + s * m [ 4 ] , c * m [ 4 ] - s * m [ 3 ] , m [ 5 ] , c * m [ 6 ] + s * m [ 7 ] , c * m [ 7 ] - s * m [ 6 ] , m [ 8 ] ] ; } }


Input: Turns a 3x3 matrix into a 4x4 matrix . [CODESPLIT] function makeMatrix4 ( m ) { return [ m [ 0 ] , m [ 1 ] , m [ 2 ] , 0 , m [ 3 ] , m [ 4 ] , m [ 5 ] , 0 , m [ 6 ] , m [ 7 ] , m [ 8 ] , 0 , 0 , 0 , 0 , 1 ] ; }


Input: Creates a perspective matrix . [CODESPLIT] function makePersp ( hfov , aspect , znear , zfar ) { var fovy = 2 * Math . atan ( Math . tan ( hfov / 2 ) * gl . drawingBufferHeight / gl . drawingBufferWidth ) ; var f = 1 / Math . tan ( fovy / 2 ) ; return [ f / aspect , 0 , 0 , 0 , 0 , f , 0 , 0 , 0 , 0 , ( zfar + znear ) / ( znear - zfar ) , ( 2 * zfar * znear ) / ( znear - zfar ) , 0 , 0 , - 1 , 0 ] ; }


Input: Processes a loaded texture image into a WebGL texture . [CODESPLIT] function processLoadedTexture ( img , tex ) { gl . bindTexture ( gl . TEXTURE_2D , tex ) ; gl . texImage2D ( gl . TEXTURE_2D , 0 , gl . RGB , gl . RGB , gl . UNSIGNED_BYTE , img ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_MAG_FILTER , gl . LINEAR ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_MIN_FILTER , gl . LINEAR ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_WRAP_S , gl . CLAMP_TO_EDGE ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_WRAP_T , gl . CLAMP_TO_EDGE ) ; gl . bindTexture ( gl . TEXTURE_2D , null ) ; }


Input: Loads image and creates texture for a multires node / tile . [CODESPLIT] function processNextTile ( node ) { loadTexture ( node , encodeURI ( node . path + '.' + image . extension ) , function ( texture , loaded ) { node . texture = texture ; node . textureLoaded = loaded ? 2 : 1 ; } , globalParams . crossOrigin ) ; }


Input: Finds and applies optimal multires zoom level . [CODESPLIT] function checkZoom ( hfov ) { // Find optimal level var newLevel = 1 ; while ( newLevel < image . maxLevel && gl . drawingBufferWidth > image . tileResolution * Math . pow ( 2 , newLevel - 1 ) * Math . tan ( hfov / 2 ) * 0.707 ) { newLevel ++ ; } // Apply change program . level = newLevel ; }


Input: Rotates perspective matrix . [CODESPLIT] function rotatePersp ( p , r ) { return [ p [ 0 ] * r [ 0 ] , p [ 0 ] * r [ 1 ] , p [ 0 ] * r [ 2 ] , 0 , p [ 5 ] * r [ 4 ] , p [ 5 ] * r [ 5 ] , p [ 5 ] * r [ 6 ] , 0 , p [ 10 ] * r [ 8 ] , p [ 10 ] * r [ 9 ] , p [ 10 ] * r [ 10 ] , p [ 11 ] , - r [ 8 ] , - r [ 9 ] , - r [ 10 ] , 0 ] ; }


Input: Applies rotated perspective matrix to a 3 - vector ( last element is inverted ) . [CODESPLIT] function applyRotPerspToVec ( m , v ) { return [ m [ 0 ] * v [ 0 ] + m [ 1 ] * v [ 1 ] + m [ 2 ] * v [ 2 ] , m [ 4 ] * v [ 0 ] + m [ 5 ] * v [ 1 ] + m [ 6 ] * v [ 2 ] , m [ 11 ] + m [ 8 ] * v [ 0 ] + m [ 9 ] * v [ 1 ] + m [ 10 ] * v [ 2 ] , 1 / ( m [ 12 ] * v [ 0 ] + m [ 13 ] * v [ 1 ] + m [ 14 ] * v [ 2 ] ) ] ; }


Input: Checks if a vertex is visible . [CODESPLIT] function checkInView ( m , v ) { var vpp = applyRotPerspToVec ( m , v ) ; var winX = vpp [ 0 ] * vpp [ 3 ] ; var winY = vpp [ 1 ] * vpp [ 3 ] ; var winZ = vpp [ 2 ] * vpp [ 3 ] ; var ret = [ 0 , 0 , 0 ] ; if ( winX < - 1 ) ret [ 0 ] = - 1 ; if ( winX > 1 ) ret [ 0 ] = 1 ; if ( winY < - 1 ) ret [ 1 ] = - 1 ; if ( winY > 1 ) ret [ 1 ] = 1 ; if ( winZ < - 1 || winZ > 1 ) ret [ 2 ] = 1 ; return ret ; }


Input: Checks if a square ( tile ) is visible . [CODESPLIT] function checkSquareInView ( m , v ) { var check1 = checkInView ( m , v . slice ( 0 , 3 ) ) ; var check2 = checkInView ( m , v . slice ( 3 , 6 ) ) ; var check3 = checkInView ( m , v . slice ( 6 , 9 ) ) ; var check4 = checkInView ( m , v . slice ( 9 , 12 ) ) ; var testX = check1 [ 0 ] + check2 [ 0 ] + check3 [ 0 ] + check4 [ 0 ] ; if ( testX == - 4 || testX == 4 ) return false ; var testY = check1 [ 1 ] + check2 [ 1 ] + check3 [ 1 ] + check4 [ 1 ] ; if ( testY == - 4 || testY == 4 ) return false ; var testZ = check1 [ 2 ] + check2 [ 2 ] + check3 [ 2 ] + check4 [ 2 ] ; return testZ != 4 ; }


Input: On iOS ( iPhone 5c iOS 10 . 3 ) this WebGL error occurs when the canvas is too big . Unfortuately there s no way to test for this beforehand so we reduce the canvas size if this error is thrown . [CODESPLIT] function handleWebGLError1286 ( ) { console . log ( 'Reducing canvas size due to error 1286!' ) ; canvas . width = Math . round ( canvas . width / 2 ) ; canvas . height = Math . round ( canvas . height / 2 ) ; }


Input: Initializes viewer . [CODESPLIT] function init ( ) { // Display an error for IE 9 as it doesn't work but also doesn't otherwise // show an error (older versions don't work at all) // Based on: http://stackoverflow.com/a/10965203 var div = document . createElement ( "div" ) ; div . innerHTML = "<!--[if lte IE 9]><i></i><![endif]-->" ; if ( div . getElementsByTagName ( "i" ) . length == 1 ) { anError ( ) ; return ; } origHfov = config . hfov ; origPitch = config . pitch ; var i , p ; if ( config . type == 'cubemap' ) { panoImage = [ ] ; for ( i = 0 ; i < 6 ; i ++ ) { panoImage . push ( new Image ( ) ) ; panoImage [ i ] . crossOrigin = config . crossOrigin ; } infoDisplay . load . lbox . style . display = 'block' ; infoDisplay . load . lbar . style . display = 'none' ; } else if ( config . type == 'multires' ) { var c = JSON . parse ( JSON . stringify ( config . multiRes ) ) ; // Deep copy // Avoid "undefined" in path, check (optional) multiRes.basePath, too // Use only multiRes.basePath if it's an absolute URL if ( config . basePath && config . multiRes . basePath && ! ( / ^(?:[a-z]+:)?\/\/ / i . test ( config . multiRes . basePath ) ) ) { c . basePath = config . basePath + config . multiRes . basePath ; } else if ( config . multiRes . basePath ) { c . basePath = config . multiRes . basePath ; } else if ( config . basePath ) { c . basePath = config . basePath ; } panoImage = c ; } else { if ( config . dynamic === true ) { panoImage = config . panorama ; } else { if ( config . panorama === undefined ) { anError ( config . strings . noPanoramaError ) ; return ; } panoImage = new Image ( ) ; } } // Configure image loading if ( config . type == 'cubemap' ) { // Quick loading counter for synchronous loading var itemsToLoad = 6 ; var onLoad = function ( ) { itemsToLoad -- ; if ( itemsToLoad === 0 ) { onImageLoad ( ) ; } } ; var onError = function ( e ) { var a = document . createElement ( 'a' ) ; a . href = e . target . src ; a . textContent = a . href ; anError ( config . strings . fileAccessError . replace ( '%s' , a . outerHTML ) ) ; } ; for ( i = 0 ; i < panoImage . length ; i ++ ) { p = config . cubeMap [ i ] ; if ( p == "null" ) { // support partial cubemap image with explicitly empty faces console . log ( 'Will use background instead of missing cubemap face ' + i ) ; onLoad ( ) ; } else { if ( config . basePath && ! absoluteURL ( p ) ) { p = config . basePath + p ; } panoImage [ i ] . onload = onLoad ; panoImage [ i ] . onerror = onError ; panoImage [ i ] . src = sanitizeURL ( p ) ; } } } else if ( config . type == 'multires' ) { onImageLoad ( ) ; } else { p = '' ; if ( config . basePath ) { p = config . basePath ; } if ( config . dynamic !== true ) { // Still image p = absoluteURL ( config . panorama ) ? config . panorama : p + config . panorama ; panoImage . onload = function ( ) { window . URL . revokeObjectURL ( this . src ) ; // Clean up onImageLoad ( ) ; } ; var xhr = new XMLHttpRequest ( ) ; xhr . onloadend = function ( ) { if ( xhr . status != 200 ) { // Display error if image can't be loaded var a = document . createElement ( 'a' ) ; a . href = p ; a . textContent = a . href ; anError ( config . strings . fileAccessError . replace ( '%s' , a . outerHTML ) ) ; } var img = this . response ; parseGPanoXMP ( img ) ; infoDisplay . load . msg . innerHTML = '' ; } ; xhr . onprogress = function ( e ) { if ( e . lengthComputable ) { // Display progress var percent = e . loaded / e . total * 100 ; infoDisplay . load . lbarFill . style . width = percent + '%' ; var unit , numerator , denominator ; if ( e . total > 1e6 ) { unit = 'MB' ; numerator = ( e . loaded / 1e6 ) . toFixed ( 2 ) ; denominator = ( e . total / 1e6 ) . toFixed ( 2 ) ; } else if ( e . total > 1e3 ) { unit = 'kB' ; numerator = ( e . loaded / 1e3 ) . toFixed ( 1 ) ; denominator = ( e . total / 1e3 ) . toFixed ( 1 ) ; } else { unit = 'B' ; numerator = e . loaded ; denominator = e . total ; } infoDisplay . load . msg . innerHTML = numerator + ' / ' + denominator + ' ' + unit ; } else { // Display loading spinner infoDisplay . load . lbox . style . display = 'block' ; infoDisplay . load . lbar . style . display = 'none' ; } } ; try { xhr . open ( 'GET' , p , true ) ; } catch ( e ) { // Malformed URL anError ( config . strings . malformedURLError ) ; } xhr . responseType = 'blob' ; xhr . setRequestHeader ( 'Accept' , 'image/*,*/*;q=0.9' ) ; xhr . withCredentials = config . crossOrigin === 'use-credentials' ; xhr . send ( ) ; } } if ( config . draggable ) uiContainer . classList . add ( 'pnlm-grab' ) ; uiContainer . classList . remove ( 'pnlm-grabbing' ) ; }


Input: Create renderer and initialize event listeners once image is loaded . [CODESPLIT] function onImageLoad ( ) { if ( ! renderer ) renderer = new libpannellum . renderer ( renderContainer ) ; // Only add event listeners once if ( ! listenersAdded ) { listenersAdded = true ; dragFix . addEventListener ( 'mousedown' , onDocumentMouseDown , false ) ; document . addEventListener ( 'mousemove' , onDocumentMouseMove , false ) ; document . addEventListener ( 'mouseup' , onDocumentMouseUp , false ) ; if ( config . mouseZoom ) { uiContainer . addEventListener ( 'mousewheel' , onDocumentMouseWheel , false ) ; uiContainer . addEventListener ( 'DOMMouseScroll' , onDocumentMouseWheel , false ) ; } if ( config . doubleClickZoom ) { dragFix . addEventListener ( 'dblclick' , onDocumentDoubleClick , false ) ; } container . addEventListener ( 'mozfullscreenchange' , onFullScreenChange , false ) ; container . addEventListener ( 'webkitfullscreenchange' , onFullScreenChange , false ) ; container . addEventListener ( 'msfullscreenchange' , onFullScreenChange , false ) ; container . addEventListener ( 'fullscreenchange' , onFullScreenChange , false ) ; window . addEventListener ( 'resize' , onDocumentResize , false ) ; window . addEventListener ( 'orientationchange' , onDocumentResize , false ) ; if ( ! config . disableKeyboardCtrl ) { container . addEventListener ( 'keydown' , onDocumentKeyPress , false ) ; container . addEventListener ( 'keyup' , onDocumentKeyUp , false ) ; container . addEventListener ( 'blur' , clearKeys , false ) ; } document . addEventListener ( 'mouseleave' , onDocumentMouseUp , false ) ; if ( document . documentElement . style . pointerAction === '' && document . documentElement . style . touchAction === '' ) { dragFix . addEventListener ( 'pointerdown' , onDocumentPointerDown , false ) ; dragFix . addEventListener ( 'pointermove' , onDocumentPointerMove , false ) ; dragFix . addEventListener ( 'pointerup' , onDocumentPointerUp , false ) ; dragFix . addEventListener ( 'pointerleave' , onDocumentPointerUp , false ) ; } else { dragFix . addEventListener ( 'touchstart' , onDocumentTouchStart , false ) ; dragFix . addEventListener ( 'touchmove' , onDocumentTouchMove , false ) ; dragFix . addEventListener ( 'touchend' , onDocumentTouchEnd , false ) ; } // Deal with MS pointer events if ( window . navigator . pointerEnabled ) container . style . touchAction = 'none' ; } renderInit ( ) ; setHfov ( config . hfov ) ; // possibly adapt hfov after configuration and canvas is complete; prevents empty space on top or bottom by zomming out too much setTimeout ( function ( ) { isTimedOut = true ; } , 500 ) ; }


Input: Extract the requested tag from the XMP data [CODESPLIT] function ( tag ) { var result ; if ( xmpData . indexOf ( tag + '="' ) >= 0 ) { result = xmpData . substring ( xmpData . indexOf ( tag + '="' ) + tag . length + 2 ) ; result = result . substring ( 0 , result . indexOf ( '"' ) ) ; } else if ( xmpData . indexOf ( tag + '>' ) >= 0 ) { result = xmpData . substring ( xmpData . indexOf ( tag + '>' ) + tag . length + 1 ) ; result = result . substring ( 0 , result . indexOf ( '<' ) ) ; } if ( result !== undefined ) { return Number ( result ) ; } return null ; }


Input: Displays an error message . [CODESPLIT] function anError ( errorMsg ) { if ( errorMsg === undefined ) errorMsg = config . strings . genericWebGLError ; infoDisplay . errorMsg . innerHTML = '<p>' + errorMsg + '</p>' ; controls . load . style . display = 'none' ; infoDisplay . load . box . style . display = 'none' ; infoDisplay . errorMsg . style . display = 'table' ; error = true ; renderContainer . style . display = 'none' ; fireEvent ( 'error' , errorMsg ) ; }


Input: Hides error message display . [CODESPLIT] function clearError ( ) { if ( error ) { infoDisplay . load . box . style . display = 'none' ; infoDisplay . errorMsg . style . display = 'none' ; error = false ; fireEvent ( 'errorcleared' ) ; } }


Input: Displays about message . [CODESPLIT] function aboutMessage ( event ) { var pos = mousePosition ( event ) ; aboutMsg . style . left = pos . x + 'px' ; aboutMsg . style . top = pos . y + 'px' ; clearTimeout ( aboutMessage . t1 ) ; clearTimeout ( aboutMessage . t2 ) ; aboutMsg . style . display = 'block' ; aboutMsg . style . opacity = 1 ; aboutMessage . t1 = setTimeout ( function ( ) { aboutMsg . style . opacity = 0 ; } , 2000 ) ; aboutMessage . t2 = setTimeout ( function ( ) { aboutMsg . style . display = 'none' ; } , 2500 ) ; event . preventDefault ( ) ; }


Input: Calculate mouse position relative to top left of viewer container . [CODESPLIT] function mousePosition ( event ) { var bounds = container . getBoundingClientRect ( ) ; var pos = { } ; // pageX / pageY needed for iOS pos . x = ( event . clientX || event . pageX ) - bounds . left ; pos . y = ( event . clientY || event . pageY ) - bounds . top ; return pos ; }


Input: Event handler for mouse clicks . Initializes panning . Prints center and click location coordinates when hot spot debugging is enabled . [CODESPLIT] function onDocumentMouseDown ( event ) { // Override default action event . preventDefault ( ) ; // But not all of it container . focus ( ) ; // Only do something if the panorama is loaded if ( ! loaded || ! config . draggable ) { return ; } // Calculate mouse position relative to top left of viewer container var pos = mousePosition ( event ) ; // Log pitch / yaw of mouse click when debugging / placing hot spots if ( config . hotSpotDebug ) { var coords = mouseEventToCoords ( event ) ; console . log ( 'Pitch: ' + coords [ 0 ] + ', Yaw: ' + coords [ 1 ] + ', Center Pitch: ' + config . pitch + ', Center Yaw: ' + config . yaw + ', HFOV: ' + config . hfov ) ; } // Turn off auto-rotation if enabled stopAnimation ( ) ; stopOrientation ( ) ; config . roll = 0 ; speed . hfov = 0 ; isUserInteracting = true ; latestInteraction = Date . now ( ) ; onPointerDownPointerX = pos . x ; onPointerDownPointerY = pos . y ; onPointerDownYaw = config . yaw ; onPointerDownPitch = config . pitch ; uiContainer . classList . add ( 'pnlm-grabbing' ) ; uiContainer . classList . remove ( 'pnlm-grab' ) ; fireEvent ( 'mousedown' , event ) ; animateInit ( ) ; }


Input: Event handler for double clicks . Zooms in at clicked location [CODESPLIT] function onDocumentDoubleClick ( event ) { if ( config . minHfov === config . hfov ) { _this . setHfov ( origHfov , 1000 ) ; } else { var coords = mouseEventToCoords ( event ) ; _this . lookAt ( coords [ 0 ] , coords [ 1 ] , config . minHfov , 1000 ) ; } }


Input: Calculate panorama pitch and yaw from location of mouse event . [CODESPLIT] function mouseEventToCoords ( event ) { var pos = mousePosition ( event ) ; var canvas = renderer . getCanvas ( ) ; var canvasWidth = canvas . clientWidth , canvasHeight = canvas . clientHeight ; var x = pos . x / canvasWidth * 2 - 1 ; var y = ( 1 - pos . y / canvasHeight * 2 ) * canvasHeight / canvasWidth ; var focal = 1 / Math . tan ( config . hfov * Math . PI / 360 ) ; var s = Math . sin ( config . pitch * Math . PI / 180 ) ; var c = Math . cos ( config . pitch * Math . PI / 180 ) ; var a = focal * c - y * s ; var root = Math . sqrt ( x * x + a * a ) ; var pitch = Math . atan ( ( y * c + focal * s ) / root ) * 180 / Math . PI ; var yaw = Math . atan2 ( x / root , a / root ) * 180 / Math . PI + config . yaw ; if ( yaw < - 180 ) yaw += 360 ; if ( yaw > 180 ) yaw -= 360 ; return [ pitch , yaw ] ; }


Input: Event handler for mouse moves . Pans center of view . [CODESPLIT] function onDocumentMouseMove ( event ) { if ( isUserInteracting && loaded ) { latestInteraction = Date . now ( ) ; var canvas = renderer . getCanvas ( ) ; var canvasWidth = canvas . clientWidth , canvasHeight = canvas . clientHeight ; var pos = mousePosition ( event ) ; //TODO: This still isn't quite right var yaw = ( ( Math . atan ( onPointerDownPointerX / canvasWidth * 2 - 1 ) - Math . atan ( pos . x / canvasWidth * 2 - 1 ) ) * 180 / Math . PI * config . hfov / 90 ) + onPointerDownYaw ; speed . yaw = ( yaw - config . yaw ) % 360 * 0.2 ; config . yaw = yaw ; var vfov = 2 * Math . atan ( Math . tan ( config . hfov / 360 * Math . PI ) * canvasHeight / canvasWidth ) * 180 / Math . PI ; var pitch = ( ( Math . atan ( pos . y / canvasHeight * 2 - 1 ) - Math . atan ( onPointerDownPointerY / canvasHeight * 2 - 1 ) ) * 180 / Math . PI * vfov / 90 ) + onPointerDownPitch ; speed . pitch = ( pitch - config . pitch ) * 0.2 ; config . pitch = pitch ; } }


Input: Event handler for mouse up events . Stops panning . [CODESPLIT] function onDocumentMouseUp ( event ) { if ( ! isUserInteracting ) { return ; } isUserInteracting = false ; if ( Date . now ( ) - latestInteraction > 15 ) { // Prevents jump when user rapidly moves mouse, stops, and then // releases the mouse button speed . pitch = speed . yaw = 0 ; } uiContainer . classList . add ( 'pnlm-grab' ) ; uiContainer . classList . remove ( 'pnlm-grabbing' ) ; latestInteraction = Date . now ( ) ; fireEvent ( 'mouseup' , event ) ; }


Input: Event handler for touches . Initializes panning if one touch or zooming if two touches . [CODESPLIT] function onDocumentTouchStart ( event ) { // Only do something if the panorama is loaded if ( ! loaded || ! config . draggable ) { return ; } // Turn off auto-rotation if enabled stopAnimation ( ) ; stopOrientation ( ) ; config . roll = 0 ; speed . hfov = 0 ; // Calculate touch position relative to top left of viewer container var pos0 = mousePosition ( event . targetTouches [ 0 ] ) ; onPointerDownPointerX = pos0 . x ; onPointerDownPointerY = pos0 . y ; if ( event . targetTouches . length == 2 ) { // Down pointer is the center of the two fingers var pos1 = mousePosition ( event . targetTouches [ 1 ] ) ; onPointerDownPointerX += ( pos1 . x - pos0 . x ) * 0.5 ; onPointerDownPointerY += ( pos1 . y - pos0 . y ) * 0.5 ; onPointerDownPointerDist = Math . sqrt ( ( pos0 . x - pos1 . x ) * ( pos0 . x - pos1 . x ) + ( pos0 . y - pos1 . y ) * ( pos0 . y - pos1 . y ) ) ; } isUserInteracting = true ; latestInteraction = Date . now ( ) ; onPointerDownYaw = config . yaw ; onPointerDownPitch = config . pitch ; fireEvent ( 'touchstart' , event ) ; animateInit ( ) ; }


Input: Event handler for touch movements . Pans center of view if one touch or adjusts zoom if two touches . [CODESPLIT] function onDocumentTouchMove ( event ) { if ( ! config . draggable ) { return ; } // Override default action event . preventDefault ( ) ; if ( loaded ) { latestInteraction = Date . now ( ) ; } if ( isUserInteracting && loaded ) { var pos0 = mousePosition ( event . targetTouches [ 0 ] ) ; var clientX = pos0 . x ; var clientY = pos0 . y ; if ( event . targetTouches . length == 2 && onPointerDownPointerDist != - 1 ) { var pos1 = mousePosition ( event . targetTouches [ 1 ] ) ; clientX += ( pos1 . x - pos0 . x ) * 0.5 ; clientY += ( pos1 . y - pos0 . y ) * 0.5 ; var clientDist = Math . sqrt ( ( pos0 . x - pos1 . x ) * ( pos0 . x - pos1 . x ) + ( pos0 . y - pos1 . y ) * ( pos0 . y - pos1 . y ) ) ; setHfov ( config . hfov + ( onPointerDownPointerDist - clientDist ) * 0.1 ) ; onPointerDownPointerDist = clientDist ; } // The smaller the config.hfov value (the more zoomed-in the user is), the faster // yaw/pitch are perceived to change on one-finger touchmove (panning) events and vice versa. // To improve usability at both small and large zoom levels (config.hfov values) // we introduce a dynamic pan speed coefficient. // // Currently this seems to *roughly* keep initial drag/pan start position close to // the user's finger while panning regardless of zoom level / config.hfov value. var touchmovePanSpeedCoeff = ( config . hfov / 360 ) * config . touchPanSpeedCoeffFactor ; var yaw = ( onPointerDownPointerX - clientX ) * touchmovePanSpeedCoeff + onPointerDownYaw ; speed . yaw = ( yaw - config . yaw ) % 360 * 0.2 ; config . yaw = yaw ; var pitch = ( clientY - onPointerDownPointerY ) * touchmovePanSpeedCoeff + onPointerDownPitch ; speed . pitch = ( pitch - config . pitch ) * 0.2 ; config . pitch = pitch ; } }


Input: Event handler for end of touches . Stops panning and / or zooming . [CODESPLIT] function onDocumentTouchEnd ( ) { isUserInteracting = false ; if ( Date . now ( ) - latestInteraction > 150 ) { speed . pitch = speed . yaw = 0 ; } onPointerDownPointerDist = - 1 ; latestInteraction = Date . now ( ) ; fireEvent ( 'touchend' , event ) ; }


Input: Event handler for touch starts in IE / Edge . [CODESPLIT] function onDocumentPointerDown ( event ) { if ( event . pointerType == 'touch' ) { pointerIDs . push ( event . pointerId ) ; pointerCoordinates . push ( { clientX : event . clientX , clientY : event . clientY } ) ; event . targetTouches = pointerCoordinates ; onDocumentTouchStart ( event ) ; event . preventDefault ( ) ; } }


Input: Event handler for touch moves in IE / Edge . [CODESPLIT] function onDocumentPointerMove ( event ) { if ( event . pointerType == 'touch' ) { for ( var i = 0 ; i < pointerIDs . length ; i ++ ) { if ( event . pointerId == pointerIDs [ i ] ) { pointerCoordinates [ i ] . clientX = event . clientX ; pointerCoordinates [ i ] . clientY = event . clientY ; event . targetTouches = pointerCoordinates ; onDocumentTouchMove ( event ) ; event . preventDefault ( ) ; return ; } } } }


Input: Event handler for touch ends in IE / Edge . [CODESPLIT] function onDocumentPointerUp ( event ) { if ( event . pointerType == 'touch' ) { var defined = false ; for ( var i = 0 ; i < pointerIDs . length ; i ++ ) { if ( event . pointerId == pointerIDs [ i ] ) pointerIDs [ i ] = undefined ; if ( pointerIDs [ i ] ) defined = true ; } if ( ! defined ) { pointerIDs = [ ] ; pointerCoordinates = [ ] ; onDocumentTouchEnd ( ) ; } event . preventDefault ( ) ; } }


Input: Event handler for mouse wheel . Changes zoom . [CODESPLIT] function onDocumentMouseWheel ( event ) { // Only do something if the panorama is loaded and mouse wheel zoom is enabled if ( ! loaded || ( config . mouseZoom == 'fullscreenonly' && ! fullscreenActive ) ) { return ; } event . preventDefault ( ) ; // Turn off auto-rotation if enabled stopAnimation ( ) ; latestInteraction = Date . now ( ) ; if ( event . wheelDeltaY ) { // WebKit setHfov ( config . hfov - event . wheelDeltaY * 0.05 ) ; speed . hfov = event . wheelDelta < 0 ? 1 : - 1 ; } else if ( event . wheelDelta ) { // Opera / Explorer 9 setHfov ( config . hfov - event . wheelDelta * 0.05 ) ; speed . hfov = event . wheelDelta < 0 ? 1 : - 1 ; } else if ( event . detail ) { // Firefox setHfov ( config . hfov + event . detail * 1.5 ) ; speed . hfov = event . detail > 0 ? 1 : - 1 ; } animateInit ( ) ; }


Input: Event handler for key presses . Updates list of currently pressed keys . [CODESPLIT] function onDocumentKeyPress ( event ) { // Turn off auto-rotation if enabled stopAnimation ( ) ; latestInteraction = Date . now ( ) ; stopOrientation ( ) ; config . roll = 0 ; // Record key pressed var keynumber = event . which || event . keycode ; // Override default action for keys that are used if ( config . capturedKeyNumbers . indexOf ( keynumber ) < 0 ) return ; event . preventDefault ( ) ; // If escape key is pressed if ( keynumber == 27 ) { // If in fullscreen mode if ( fullscreenActive ) { toggleFullscreen ( ) ; } } else { // Change key changeKey ( keynumber , true ) ; } }


Input: Event handler for key releases . Updates list of currently pressed keys . [CODESPLIT] function onDocumentKeyUp ( event ) { // Record key pressed var keynumber = event . which || event . keycode ; // Override default action for keys that are used if ( config . capturedKeyNumbers . indexOf ( keynumber ) < 0 ) return ; event . preventDefault ( ) ; // Change key changeKey ( keynumber , false ) ; }


Input: Updates list of currently pressed keys . [CODESPLIT] function changeKey ( keynumber , value ) { var keyChanged = false ; switch ( keynumber ) { // If minus key is released case 109 : case 189 : case 17 : case 173 : if ( keysDown [ 0 ] != value ) { keyChanged = true ; } keysDown [ 0 ] = value ; break ; // If plus key is released case 107 : case 187 : case 16 : case 61 : if ( keysDown [ 1 ] != value ) { keyChanged = true ; } keysDown [ 1 ] = value ; break ; // If up arrow is released case 38 : if ( keysDown [ 2 ] != value ) { keyChanged = true ; } keysDown [ 2 ] = value ; break ; // If "w" is released case 87 : if ( keysDown [ 6 ] != value ) { keyChanged = true ; } keysDown [ 6 ] = value ; break ; // If down arrow is released case 40 : if ( keysDown [ 3 ] != value ) { keyChanged = true ; } keysDown [ 3 ] = value ; break ; // If "s" is released case 83 : if ( keysDown [ 7 ] != value ) { keyChanged = true ; } keysDown [ 7 ] = value ; break ; // If left arrow is released case 37 : if ( keysDown [ 4 ] != value ) { keyChanged = true ; } keysDown [ 4 ] = value ; break ; // If "a" is released case 65 : if ( keysDown [ 8 ] != value ) { keyChanged = true ; } keysDown [ 8 ] = value ; break ; // If right arrow is released case 39 : if ( keysDown [ 5 ] != value ) { keyChanged = true ; } keysDown [ 5 ] = value ; break ; // If "d" is released case 68 : if ( keysDown [ 9 ] != value ) { keyChanged = true ; } keysDown [ 9 ] = value ; } if ( keyChanged && value ) { if ( typeof performance !== 'undefined' && performance . now ( ) ) { prevTime = performance . now ( ) ; } else { prevTime = Date . now ( ) ; } animateInit ( ) ; } }


Input: Pans and / or zooms panorama based on currently pressed keys . Also handles panorama inertia and auto rotation . [CODESPLIT] function keyRepeat ( ) { // Only do something if the panorama is loaded if ( ! loaded ) { return ; } var isKeyDown = false ; var prevPitch = config . pitch ; var prevYaw = config . yaw ; var prevZoom = config . hfov ; var newTime ; if ( typeof performance !== 'undefined' && performance . now ( ) ) { newTime = performance . now ( ) ; } else { newTime = Date . now ( ) ; } if ( prevTime === undefined ) { prevTime = newTime ; } var diff = ( newTime - prevTime ) * config . hfov / 1700 ; diff = Math . min ( diff , 1.0 ) ; // If minus key is down if ( keysDown [ 0 ] && config . keyboardZoom === true ) { setHfov ( config . hfov + ( speed . hfov * 0.8 + 0.5 ) * diff ) ; isKeyDown = true ; } // If plus key is down if ( keysDown [ 1 ] && config . keyboardZoom === true ) { setHfov ( config . hfov + ( speed . hfov * 0.8 - 0.2 ) * diff ) ; isKeyDown = true ; } // If up arrow or "w" is down if ( keysDown [ 2 ] || keysDown [ 6 ] ) { // Pan up config . pitch += ( speed . pitch * 0.8 + 0.2 ) * diff ; isKeyDown = true ; } // If down arrow or "s" is down if ( keysDown [ 3 ] || keysDown [ 7 ] ) { // Pan down config . pitch += ( speed . pitch * 0.8 - 0.2 ) * diff ; isKeyDown = true ; } // If left arrow or "a" is down if ( keysDown [ 4 ] || keysDown [ 8 ] ) { // Pan left config . yaw += ( speed . yaw * 0.8 - 0.2 ) * diff ; isKeyDown = true ; } // If right arrow or "d" is down if ( keysDown [ 5 ] || keysDown [ 9 ] ) { // Pan right config . yaw += ( speed . yaw * 0.8 + 0.2 ) * diff ; isKeyDown = true ; } if ( isKeyDown ) latestInteraction = Date . now ( ) ; // If auto-rotate var inactivityInterval = Date . now ( ) - latestInteraction ; if ( config . autoRotate ) { // Pan if ( newTime - prevTime > 0.001 ) { var timeDiff = ( newTime - prevTime ) / 1000 ; var yawDiff = ( speed . yaw / timeDiff * diff - config . autoRotate * 0.2 ) * timeDiff yawDiff = ( - config . autoRotate > 0 ? 1 : - 1 ) * Math . min ( Math . abs ( config . autoRotate * timeDiff ) , Math . abs ( yawDiff ) ) ; config . yaw += yawDiff ; } // Deal with stopping auto rotation after a set delay if ( config . autoRotateStopDelay ) { config . autoRotateStopDelay -= newTime - prevTime ; if ( config . autoRotateStopDelay <= 0 ) { config . autoRotateStopDelay = false ; autoRotateSpeed = config . autoRotate ; config . autoRotate = 0 ; } } } // Animated moves if ( animatedMove . pitch ) { animateMove ( 'pitch' ) ; prevPitch = config . pitch ; } if ( animatedMove . yaw ) { animateMove ( 'yaw' ) ; prevYaw = config . yaw ; } if ( animatedMove . hfov ) { animateMove ( 'hfov' ) ; prevZoom = config . hfov ; } // "Inertia" if ( diff > 0 && ! config . autoRotate ) { // "Friction" var slowDownFactor = 1 - config . friction ; // Yaw if ( ! keysDown [ 4 ] && ! keysDown [ 5 ] && ! keysDown [ 8 ] && ! keysDown [ 9 ] && ! animatedMove . yaw ) { config . yaw += speed . yaw * diff * slowDownFactor ; } // Pitch if ( ! keysDown [ 2 ] && ! keysDown [ 3 ] && ! keysDown [ 6 ] && ! keysDown [ 7 ] && ! animatedMove . pitch ) { config . pitch += speed . pitch * diff * slowDownFactor ; } // Zoom if ( ! keysDown [ 0 ] && ! keysDown [ 1 ] && ! animatedMove . hfov ) { setHfov ( config . hfov + speed . hfov * diff * slowDownFactor ) ; } } prevTime = newTime ; if ( diff > 0 ) { speed . yaw = speed . yaw * 0.8 + ( config . yaw - prevYaw ) / diff * 0.2 ; speed . pitch = speed . pitch * 0.8 + ( config . pitch - prevPitch ) / diff * 0.2 ; speed . hfov = speed . hfov * 0.8 + ( config . hfov - prevZoom ) / diff * 0.2 ; // Limit speed var maxSpeed = config . autoRotate ? Math . abs ( config . autoRotate ) : 5 ; speed . yaw = Math . min ( maxSpeed , Math . max ( speed . yaw , - maxSpeed ) ) ; speed . pitch = Math . min ( maxSpeed , Math . max ( speed . pitch , - maxSpeed ) ) ; speed . hfov = Math . min ( maxSpeed , Math . max ( speed . hfov , - maxSpeed ) ) ; } // Stop movement if opposite controls are pressed if ( keysDown [ 0 ] && keysDown [ 1 ] ) { speed . hfov = 0 ; } if ( ( keysDown [ 2 ] || keysDown [ 6 ] ) && ( keysDown [ 3 ] || keysDown [ 7 ] ) ) { speed . pitch = 0 ; } if ( ( keysDown [ 4 ] || keysDown [ 8 ] ) && ( keysDown [ 5 ] || keysDown [ 9 ] ) ) { speed . yaw = 0 ; } }


Input: Animates moves . [CODESPLIT] function animateMove ( axis ) { var t = animatedMove [ axis ] ; var normTime = Math . min ( 1 , Math . max ( ( Date . now ( ) - t . startTime ) / 1000 / ( t . duration / 1000 ) , 0 ) ) ; var result = t . startPosition + config . animationTimingFunction ( normTime ) * ( t . endPosition - t . startPosition ) ; if ( ( t . endPosition > t . startPosition && result >= t . endPosition ) || ( t . endPosition < t . startPosition && result <= t . endPosition ) || t . endPosition === t . startPosition ) { result = t . endPosition ; speed [ axis ] = 0 ; delete animatedMove [ axis ] ; } config [ axis ] = result ; }


Input: Animates view using requestAnimationFrame to trigger rendering . [CODESPLIT] function animate ( ) { render ( ) ; if ( autoRotateStart ) clearTimeout ( autoRotateStart ) ; if ( isUserInteracting || orientation === true ) { requestAnimationFrame ( animate ) ; } else if ( keysDown [ 0 ] || keysDown [ 1 ] || keysDown [ 2 ] || keysDown [ 3 ] || keysDown [ 4 ] || keysDown [ 5 ] || keysDown [ 6 ] || keysDown [ 7 ] || keysDown [ 8 ] || keysDown [ 9 ] || config . autoRotate || animatedMove . pitch || animatedMove . yaw || animatedMove . hfov || Math . abs ( speed . yaw ) > 0.01 || Math . abs ( speed . pitch ) > 0.01 || Math . abs ( speed . hfov ) > 0.01 ) { keyRepeat ( ) ; if ( config . autoRotateInactivityDelay >= 0 && autoRotateSpeed && Date . now ( ) - latestInteraction > config . autoRotateInactivityDelay && ! config . autoRotate ) { config . autoRotate = autoRotateSpeed ; _this . lookAt ( origPitch , undefined , origHfov , 3000 ) ; } requestAnimationFrame ( animate ) ; } else if ( renderer && ( renderer . isLoading ( ) || ( config . dynamic === true && update ) ) ) { requestAnimationFrame ( animate ) ; } else { fireEvent ( 'animatefinished' , { pitch : _this . getPitch ( ) , yaw : _this . getYaw ( ) , hfov : _this . getHfov ( ) } ) ; animating = false ; prevTime = undefined ; var autoRotateStartTime = config . autoRotateInactivityDelay - ( Date . now ( ) - latestInteraction ) ; if ( autoRotateStartTime > 0 ) { autoRotateStart = setTimeout ( function ( ) { config . autoRotate = autoRotateSpeed ; _this . lookAt ( origPitch , undefined , origHfov , 3000 ) ; animateInit ( ) ; } , autoRotateStartTime ) ; } else if ( config . autoRotateInactivityDelay >= 0 && autoRotateSpeed ) { config . autoRotate = autoRotateSpeed ; _this . lookAt ( origPitch , undefined , origHfov , 3000 ) ; animateInit ( ) ; } } }


Input: Renders panorama view . [CODESPLIT] function render ( ) { var tmpyaw ; if ( loaded ) { // Keep a tmp value of yaw for autoRotate comparison later tmpyaw = config . yaw ; // Optionally avoid showing background (empty space) on left or right by adapting min/max yaw var hoffcut = 0 , voffcut = 0 ; if ( config . avoidShowingBackground ) { var canvas = renderer . getCanvas ( ) , hfov2 = config . hfov / 2 , vfov2 = Math . atan2 ( Math . tan ( hfov2 / 180 * Math . PI ) , ( canvas . width / canvas . height ) ) * 180 / Math . PI , transposed = config . vaov > config . haov ; if ( transposed ) { voffcut = vfov2 * ( 1 - Math . min ( Math . cos ( ( config . pitch - hfov2 ) / 180 * Math . PI ) , Math . cos ( ( config . pitch + hfov2 ) / 180 * Math . PI ) ) ) ; } else { hoffcut = hfov2 * ( 1 - Math . min ( Math . cos ( ( config . pitch - vfov2 ) / 180 * Math . PI ) , Math . cos ( ( config . pitch + vfov2 ) / 180 * Math . PI ) ) ) ; } } // Ensure the yaw is within min and max allowed var yawRange = config . maxYaw - config . minYaw , minYaw = - 180 , maxYaw = 180 ; if ( yawRange < 360 ) { minYaw = config . minYaw + config . hfov / 2 + hoffcut ; maxYaw = config . maxYaw - config . hfov / 2 - hoffcut ; if ( yawRange < config . hfov ) { // Lock yaw to average of min and max yaw when both can be seen at once minYaw = maxYaw = ( minYaw + maxYaw ) / 2 ; } config . yaw = Math . max ( minYaw , Math . min ( maxYaw , config . yaw ) ) ; } if ( config . yaw > 180 ) { config . yaw -= 360 ; } else if ( config . yaw < - 180 ) { config . yaw += 360 ; } // Check if we autoRotate in a limited by min and max yaw // If so reverse direction if ( config . autoRotate !== false && tmpyaw != config . yaw && prevTime !== undefined ) { // this condition prevents changing the direction initially config . autoRotate *= - 1 ; } // Ensure the calculated pitch is within min and max allowed var canvas = renderer . getCanvas ( ) ; var vfov = 2 * Math . atan ( Math . tan ( config . hfov / 180 * Math . PI * 0.5 ) / ( canvas . width / canvas . height ) ) / Math . PI * 180 ; var minPitch = config . minPitch + vfov / 2 , maxPitch = config . maxPitch - vfov / 2 ; var pitchRange = config . maxPitch - config . minPitch ; if ( pitchRange < vfov ) { // Lock pitch to average of min and max pitch when both can be seen at once minPitch = maxPitch = ( minPitch + maxPitch ) / 2 ; } if ( isNaN ( minPitch ) ) minPitch = - 90 ; if ( isNaN ( maxPitch ) ) maxPitch = 90 ; config . pitch = Math . max ( minPitch , Math . min ( maxPitch , config . pitch ) ) ; renderer . render ( config . pitch * Math . PI / 180 , config . yaw * Math . PI / 180 , config . hfov * Math . PI / 180 , { roll : config . roll * Math . PI / 180 } ) ; renderHotSpots ( ) ; // Update compass if ( config . compass ) { compass . style . transform = 'rotate(' + ( - config . yaw - config . northOffset ) + 'deg)' ; compass . style . webkitTransform = 'rotate(' + ( - config . yaw - config . northOffset ) + 'deg)' ; } } }


Input: Converts device orientation API Tait - Bryan angles to a quaternion . [CODESPLIT] function taitBryanToQuaternion ( alpha , beta , gamma ) { var r = [ beta ? beta * Math . PI / 180 / 2 : 0 , gamma ? gamma * Math . PI / 180 / 2 : 0 , alpha ? alpha * Math . PI / 180 / 2 : 0 ] ; var c = [ Math . cos ( r [ 0 ] ) , Math . cos ( r [ 1 ] ) , Math . cos ( r [ 2 ] ) ] , s = [ Math . sin ( r [ 0 ] ) , Math . sin ( r [ 1 ] ) , Math . sin ( r [ 2 ] ) ] ; return new Quaternion ( c [ 0 ] * c [ 1 ] * c [ 2 ] - s [ 0 ] * s [ 1 ] * s [ 2 ] , s [ 0 ] * c [ 1 ] * c [ 2 ] - c [ 0 ] * s [ 1 ] * s [ 2 ] , c [ 0 ] * s [ 1 ] * c [ 2 ] + s [ 0 ] * c [ 1 ] * s [ 2 ] , c [ 0 ] * c [ 1 ] * s [ 2 ] + s [ 0 ] * s [ 1 ] * c [ 2 ] ) ; }


Input: Computes current device orientation quaternion from device orientation API Tait - Bryan angles . [CODESPLIT] function computeQuaternion ( alpha , beta , gamma ) { // Convert Tait-Bryan angles to quaternion var quaternion = taitBryanToQuaternion ( alpha , beta , gamma ) ; // Apply world transform quaternion = quaternion . multiply ( new Quaternion ( Math . sqrt ( 0.5 ) , - Math . sqrt ( 0.5 ) , 0 , 0 ) ) ; // Apply screen transform var angle = window . orientation ? - window . orientation * Math . PI / 180 / 2 : 0 ; return quaternion . multiply ( new Quaternion ( Math . cos ( angle ) , 0 , - Math . sin ( angle ) , 0 ) ) ; }


Input: Event handler for device orientation API . Controls pointing . [CODESPLIT] function orientationListener ( e ) { var q = computeQuaternion ( e . alpha , e . beta , e . gamma ) . toEulerAngles ( ) ; if ( typeof ( orientation ) == 'number' && orientation < 10 ) { // This kludge is necessary because iOS sometimes provides a few stale // device orientation events when the listener is removed and then // readded. Thus, we skip the first 10 events to prevent this from // causing problems. orientation += 1 ; } else if ( orientation === 10 ) { // Record starting yaw to prevent jumping orientationYawOffset = q [ 2 ] / Math . PI * 180 + config . yaw ; orientation = true ; requestAnimationFrame ( animate ) ; } else { config . pitch = q [ 0 ] / Math . PI * 180 ; config . roll = - q [ 1 ] / Math . PI * 180 ; config . yaw = - q [ 2 ] / Math . PI * 180 + orientationYawOffset ; } }


Input: Initializes renderer . [CODESPLIT] function renderInit ( ) { try { var params = { } ; if ( config . horizonPitch !== undefined ) params . horizonPitch = config . horizonPitch * Math . PI / 180 ; if ( config . horizonRoll !== undefined ) params . horizonRoll = config . horizonRoll * Math . PI / 180 ; if ( config . backgroundColor !== undefined ) params . backgroundColor = config . backgroundColor ; renderer . init ( panoImage , config . type , config . dynamic , config . haov * Math . PI / 180 , config . vaov * Math . PI / 180 , config . vOffset * Math . PI / 180 , renderInitCallback , params ) ; if ( config . dynamic !== true ) { // Allow image to be garbage collected panoImage = undefined ; } } catch ( event ) { // Panorama not loaded // Display error if there is a bad texture if ( event . type == 'webgl error' || event . type == 'no webgl' ) { anError ( ) ; } else if ( event . type == 'webgl size error' ) { anError ( config . strings . textureSizeError . replace ( '%s' , event . width ) . replace ( '%s' , event . maxWidth ) ) ; } else { anError ( config . strings . unknownError ) ; throw event ; } } }


Input: Triggered when render initialization finishes . Handles fading between scenes as well as showing the compass and hotspots and hiding the loading display . [CODESPLIT] function renderInitCallback ( ) { // Fade if specified if ( config . sceneFadeDuration && renderer . fadeImg !== undefined ) { renderer . fadeImg . style . opacity = 0 ; // Remove image var fadeImg = renderer . fadeImg ; delete renderer . fadeImg ; setTimeout ( function ( ) { renderContainer . removeChild ( fadeImg ) ; fireEvent ( 'scenechangefadedone' ) ; } , config . sceneFadeDuration ) ; } // Show compass if applicable if ( config . compass ) { compass . style . display = 'inline' ; } else { compass . style . display = 'none' ; } // Show hotspots createHotSpots ( ) ; // Hide loading display infoDisplay . load . box . style . display = 'none' ; if ( preview !== undefined ) { renderContainer . removeChild ( preview ) ; preview = undefined ; } loaded = true ; fireEvent ( 'load' ) ; animateInit ( ) ; }


Input: Creates hot spot element for the current scene . [CODESPLIT] function createHotSpot ( hs ) { // Make sure hot spot pitch and yaw are numbers hs . pitch = Number ( hs . pitch ) || 0 ; hs . yaw = Number ( hs . yaw ) || 0 ; var div = document . createElement ( 'div' ) ; div . className = 'pnlm-hotspot-base' if ( hs . cssClass ) div . className += ' ' + hs . cssClass ; else div . className += ' pnlm-hotspot pnlm-sprite pnlm-' + escapeHTML ( hs . type ) ; var span = document . createElement ( 'span' ) ; if ( hs . text ) span . innerHTML = escapeHTML ( hs . text ) ; var a ; if ( hs . video ) { var video = document . createElement ( 'video' ) , p = hs . video ; if ( config . basePath && ! absoluteURL ( p ) ) p = config . basePath + p ; video . src = sanitizeURL ( p ) ; video . controls = true ; video . style . width = hs . width + 'px' ; renderContainer . appendChild ( div ) ; span . appendChild ( video ) ; } else if ( hs . image ) { var p = hs . image ; if ( config . basePath && ! absoluteURL ( p ) ) p = config . basePath + p ; a = document . createElement ( 'a' ) ; a . href = sanitizeURL ( hs . URL ? hs . URL : p ) ; a . target = '_blank' ; span . appendChild ( a ) ; var image = document . createElement ( 'img' ) ; image . src = sanitizeURL ( p ) ; image . style . width = hs . width + 'px' ; image . style . paddingTop = '5px' ; renderContainer . appendChild ( div ) ; a . appendChild ( image ) ; span . style . maxWidth = 'initial' ; } else if ( hs . URL ) { a = document . createElement ( 'a' ) ; a . href = sanitizeURL ( hs . URL ) ; if ( hs . attributes ) { for ( var key in hs . attributes ) { a . setAttribute ( key , hs . attributes [ key ] ) ; } } else { a . target = '_blank' ; } renderContainer . appendChild ( a ) ; div . className += ' pnlm-pointer' ; span . className += ' pnlm-pointer' ; a . appendChild ( div ) ; } else { if ( hs . sceneId ) { div . onclick = div . ontouchend = function ( ) { if ( ! div . clicked ) { div . clicked = true ; loadScene ( hs . sceneId , hs . targetPitch , hs . targetYaw , hs . targetHfov ) ; } return false ; } ; div . className += ' pnlm-pointer' ; span . className += ' pnlm-pointer' ; } renderContainer . appendChild ( div ) ; } if ( hs . createTooltipFunc ) { hs . createTooltipFunc ( div , hs . createTooltipArgs ) ; } else if ( hs . text || hs . video || hs . image ) { div . classList . add ( 'pnlm-tooltip' ) ; div . appendChild ( span ) ; span . style . width = span . scrollWidth - 20 + 'px' ; span . style . marginLeft = - ( span . scrollWidth - div . offsetWidth ) / 2 + 'px' ; span . style . marginTop = - span . scrollHeight - 12 + 'px' ; } if ( hs . clickHandlerFunc ) { div . addEventListener ( 'click' , function ( e ) { hs . clickHandlerFunc ( e , hs . clickHandlerArgs ) ; } , 'false' ) ; div . className += ' pnlm-pointer' ; span . className += ' pnlm-pointer' ; } hs . div = div ; }


Input: Creates hot spot elements for the current scene . [CODESPLIT] function createHotSpots ( ) { if ( hotspotsCreated ) return ; if ( ! config . hotSpots ) { config . hotSpots = [ ] ; } else { // Sort by pitch so tooltip is never obscured by another hot spot config . hotSpots = config . hotSpots . sort ( function ( a , b ) { return a . pitch < b . pitch ; } ) ; config . hotSpots . forEach ( createHotSpot ) ; } hotspotsCreated = true ; renderHotSpots ( ) ; }


Input: Destroys currently created hot spot elements . [CODESPLIT] function destroyHotSpots ( ) { var hs = config . hotSpots ; hotspotsCreated = false ; delete config . hotSpots ; if ( hs ) { for ( var i = 0 ; i < hs . length ; i ++ ) { var current = hs [ i ] . div ; if ( current ) { while ( current . parentNode && current . parentNode != renderContainer ) { current = current . parentNode ; } renderContainer . removeChild ( current ) ; } delete hs [ i ] . div ; } } }


Input: Renders hot spot updating its position and visibility . [CODESPLIT] function renderHotSpot ( hs ) { var hsPitchSin = Math . sin ( hs . pitch * Math . PI / 180 ) , hsPitchCos = Math . cos ( hs . pitch * Math . PI / 180 ) , configPitchSin = Math . sin ( config . pitch * Math . PI / 180 ) , configPitchCos = Math . cos ( config . pitch * Math . PI / 180 ) , yawCos = Math . cos ( ( - hs . yaw + config . yaw ) * Math . PI / 180 ) ; var z = hsPitchSin * configPitchSin + hsPitchCos * yawCos * configPitchCos ; if ( ( hs . yaw <= 90 && hs . yaw > - 90 && z <= 0 ) || ( ( hs . yaw > 90 || hs . yaw <= - 90 ) && z <= 0 ) ) { hs . div . style . visibility = 'hidden' ; } else { var yawSin = Math . sin ( ( - hs . yaw + config . yaw ) * Math . PI / 180 ) , hfovTan = Math . tan ( config . hfov * Math . PI / 360 ) ; hs . div . style . visibility = 'visible' ; // Subpixel rendering doesn't work in Firefox // https://bugzilla.mozilla.org/show_bug.cgi?id=739176 var canvas = renderer . getCanvas ( ) , canvasWidth = canvas . clientWidth , canvasHeight = canvas . clientHeight ; var coord = [ - canvasWidth / hfovTan * yawSin * hsPitchCos / z / 2 , - canvasWidth / hfovTan * ( hsPitchSin * configPitchCos - hsPitchCos * yawCos * configPitchSin ) / z / 2 ] ; // Apply roll var rollSin = Math . sin ( config . roll * Math . PI / 180 ) , rollCos = Math . cos ( config . roll * Math . PI / 180 ) ; coord = [ coord [ 0 ] * rollCos - coord [ 1 ] * rollSin , coord [ 0 ] * rollSin + coord [ 1 ] * rollCos ] ; // Apply transform coord [ 0 ] += ( canvasWidth - hs . div . offsetWidth ) / 2 ; coord [ 1 ] += ( canvasHeight - hs . div . offsetHeight ) / 2 ; var transform = 'translate(' + coord [ 0 ] + 'px, ' + coord [ 1 ] + 'px) translateZ(9999px) rotate(' + config . roll + 'deg)' ; hs . div . style . webkitTransform = transform ; hs . div . style . MozTransform = transform ; hs . div . style . transform = transform ; } }


Input: Merges a scene configuration into the current configuration . [CODESPLIT] function mergeConfig ( sceneId ) { config = { } ; var k , s ; var photoSphereExcludes = [ 'haov' , 'vaov' , 'vOffset' , 'northOffset' , 'horizonPitch' , 'horizonRoll' ] ; specifiedPhotoSphereExcludes = [ ] ; // Merge default config for ( k in defaultConfig ) { if ( defaultConfig . hasOwnProperty ( k ) ) { config [ k ] = defaultConfig [ k ] ; } } // Merge default scene config for ( k in initialConfig . default ) { if ( initialConfig . default . hasOwnProperty ( k ) ) { if ( k == 'strings' ) { for ( s in initialConfig . default . strings ) { if ( initialConfig . default . strings . hasOwnProperty ( s ) ) { config . strings [ s ] = escapeHTML ( initialConfig . default . strings [ s ] ) ; } } } else { config [ k ] = initialConfig . default [ k ] ; if ( photoSphereExcludes . indexOf ( k ) >= 0 ) { specifiedPhotoSphereExcludes . push ( k ) ; } } } } // Merge current scene config if ( ( sceneId !== null ) && ( sceneId !== '' ) && ( initialConfig . scenes ) && ( initialConfig . scenes [ sceneId ] ) ) { var scene = initialConfig . scenes [ sceneId ] ; for ( k in scene ) { if ( scene . hasOwnProperty ( k ) ) { if ( k == 'strings' ) { for ( s in scene . strings ) { if ( scene . strings . hasOwnProperty ( s ) ) { config . strings [ s ] = escapeHTML ( scene . strings [ s ] ) ; } } } else { config [ k ] = scene [ k ] ; if ( photoSphereExcludes . indexOf ( k ) >= 0 ) { specifiedPhotoSphereExcludes . push ( k ) ; } } } } config . scene = sceneId ; } // Merge initial config for ( k in initialConfig ) { if ( initialConfig . hasOwnProperty ( k ) ) { if ( k == 'strings' ) { for ( s in initialConfig . strings ) { if ( initialConfig . strings . hasOwnProperty ( s ) ) { config . strings [ s ] = escapeHTML ( initialConfig . strings [ s ] ) ; } } } else { config [ k ] = initialConfig [ k ] ; if ( photoSphereExcludes . indexOf ( k ) >= 0 ) { specifiedPhotoSphereExcludes . push ( k ) ; } } } } }


Input: Processes configuration options . [CODESPLIT] function processOptions ( isPreview ) { isPreview = isPreview ? isPreview : false ; // Process preview first so it always loads before the browser hits its // maximum number of connections to a server as can happen with cubic // panoramas if ( isPreview && 'preview' in config ) { var p = config . preview ; if ( config . basePath && ! absoluteURL ( p ) ) p = config . basePath + p ; preview = document . createElement ( 'div' ) ; preview . className = 'pnlm-preview-img' ; preview . style . backgroundImage = "url('" + sanitizeURLForCss ( p ) + "')" ; renderContainer . appendChild ( preview ) ; } // Handle different preview values var title = config . title , author = config . author ; if ( isPreview ) { if ( 'previewTitle' in config ) config . title = config . previewTitle ; if ( 'previewAuthor' in config ) config . author = config . previewAuthor ; } // Reset title / author display if ( ! config . hasOwnProperty ( 'title' ) ) infoDisplay . title . innerHTML = '' ; if ( ! config . hasOwnProperty ( 'author' ) ) infoDisplay . author . innerHTML = '' ; if ( ! config . hasOwnProperty ( 'title' ) && ! config . hasOwnProperty ( 'author' ) ) infoDisplay . container . style . display = 'none' ; // Fill in load button label and loading box text controls . load . innerHTML = '<p>' + config . strings . loadButtonLabel + '</p>' ; infoDisplay . load . boxp . innerHTML = config . strings . loadingLabel ; // Process other options for ( var key in config ) { if ( config . hasOwnProperty ( key ) ) { switch ( key ) { case 'title' : infoDisplay . title . innerHTML = escapeHTML ( config [ key ] ) ; infoDisplay . container . style . display = 'inline' ; break ; case 'author' : infoDisplay . author . innerHTML = config . strings . bylineLabel . replace ( '%s' , escapeHTML ( config [ key ] ) ) ; infoDisplay . container . style . display = 'inline' ; break ; case 'fallback' : var link = document . createElement ( 'a' ) ; link . href = sanitizeURL ( config [ key ] ) ; link . target = '_blank' ; link . textContent = 'Click here to view this panorama in an alternative viewer.' ; var message = document . createElement ( 'p' ) ; message . textContent = 'Your browser does not support WebGL.' message . appendChild ( document . createElement ( 'br' ) ) ; message . appendChild ( link ) ; infoDisplay . errorMsg . innerHTML = '' ; // Removes all children nodes infoDisplay . errorMsg . appendChild ( message ) ; break ; case 'hfov' : setHfov ( Number ( config [ key ] ) ) ; break ; case 'autoLoad' : if ( config [ key ] === true && renderer === undefined ) { // Show loading box infoDisplay . load . box . style . display = 'inline' ; // Hide load button controls . load . style . display = 'none' ; // Initialize init ( ) ; } break ; case 'showZoomCtrl' : if ( config [ key ] && config . showControls != false ) { // Show zoom controls controls . zoom . style . display = 'block' ; } else { // Hide zoom controls controls . zoom . style . display = 'none' ; } break ; case 'showFullscreenCtrl' : if ( config [ key ] && config . showControls != false && ( 'fullscreen' in document || 'mozFullScreen' in document || 'webkitIsFullScreen' in document || 'msFullscreenElement' in document ) ) { // Show fullscreen control controls . fullscreen . style . display = 'block' ; } else { // Hide fullscreen control controls . fullscreen . style . display = 'none' ; } break ; case 'hotSpotDebug' : if ( config [ key ] ) hotSpotDebugIndicator . style . display = 'block' ; else hotSpotDebugIndicator . style . display = 'none' ; break ; case 'showControls' : if ( ! config [ key ] ) { controls . orientation . style . display = 'none' ; controls . zoom . style . display = 'none' ; controls . fullscreen . style . display = 'none' ; } break ; case 'orientationOnByDefault' : if ( config [ key ] ) { if ( orientationSupport === undefined ) startOrientationIfSupported = true ; else if ( orientationSupport === true ) startOrientation ( ) ; } break ; } } } if ( isPreview ) { // Restore original values if changed for preview if ( title ) config . title = title ; else delete config . title ; if ( author ) config . author = author ; else delete config . author ; } }


Input: Toggles fullscreen mode . [CODESPLIT] function toggleFullscreen ( ) { if ( loaded && ! error ) { if ( ! fullscreenActive ) { try { if ( container . requestFullscreen ) { container . requestFullscreen ( ) ; } else if ( container . mozRequestFullScreen ) { container . mozRequestFullScreen ( ) ; } else if ( container . msRequestFullscreen ) { container . msRequestFullscreen ( ) ; } else { container . webkitRequestFullScreen ( ) ; } } catch ( event ) { // Fullscreen doesn't work } } else { if ( document . exitFullscreen ) { document . exitFullscreen ( ) ; } else if ( document . mozCancelFullScreen ) { document . mozCancelFullScreen ( ) ; } else if ( document . webkitCancelFullScreen ) { document . webkitCancelFullScreen ( ) ; } else if ( document . msExitFullscreen ) { document . msExitFullscreen ( ) ; } } } }


Input: Event handler for fullscreen changes . [CODESPLIT] function onFullScreenChange ( resize ) { if ( document . fullscreenElement || document . fullscreen || document . mozFullScreen || document . webkitIsFullScreen || document . msFullscreenElement ) { controls . fullscreen . classList . add ( 'pnlm-fullscreen-toggle-button-active' ) ; fullscreenActive = true ; } else { controls . fullscreen . classList . remove ( 'pnlm-fullscreen-toggle-button-active' ) ; fullscreenActive = false ; } if ( resize !== 'resize' ) fireEvent ( 'fullscreenchange' , fullscreenActive ) ; // Resize renderer (deal with browser quirks and fixes #155) renderer . resize ( ) ; setHfov ( config . hfov ) ; animateInit ( ) ; }


Input: Clamps horzontal field of view to viewer s limits . [CODESPLIT] function constrainHfov ( hfov ) { // Keep field of view within bounds var minHfov = config . minHfov ; if ( config . type == 'multires' && renderer && config . multiResMinHfov ) { minHfov = Math . min ( minHfov , renderer . getCanvas ( ) . width / ( config . multiRes . cubeResolution / 90 * 0.9 ) ) ; } if ( minHfov > config . maxHfov ) { // Don't change view if bounds don't make sense console . log ( 'HFOV bounds do not make sense (minHfov > maxHfov).' ) return config . hfov ; } var newHfov = config . hfov ; if ( hfov < minHfov ) { newHfov = minHfov ; } else if ( hfov > config . maxHfov ) { newHfov = config . maxHfov ; } else { newHfov = hfov ; } // Optionally avoid showing background (empty space) on top or bottom by adapting newHfov if ( config . avoidShowingBackground && renderer ) { var canvas = renderer . getCanvas ( ) ; newHfov = Math . min ( newHfov , Math . atan ( Math . tan ( ( config . maxPitch - config . minPitch ) / 360 * Math . PI ) / canvas . height * canvas . width ) * 360 / Math . PI ) ; } return newHfov ; }


Input: Stops auto rotation and animated moves . [CODESPLIT] function stopAnimation ( ) { animatedMove = { } ; autoRotateSpeed = config . autoRotate ? config . autoRotate : autoRotateSpeed ; config . autoRotate = false ; }


Input: Loads panorama . [CODESPLIT] function load ( ) { // Since WebGL error handling is very general, first we clear any error box // since it is a new scene and the error from previous maybe because of lacking // memory etc and not because of a lack of WebGL support etc clearError ( ) ; loaded = false ; controls . load . style . display = 'none' ; infoDisplay . load . box . style . display = 'inline' ; init ( ) ; }


Input: Loads scene . [CODESPLIT] function loadScene ( sceneId , targetPitch , targetYaw , targetHfov , fadeDone ) { loaded = false ; animatedMove = { } ; // Set up fade if specified var fadeImg , workingPitch , workingYaw , workingHfov ; if ( config . sceneFadeDuration && ! fadeDone ) { var data = renderer . render ( config . pitch * Math . PI / 180 , config . yaw * Math . PI / 180 , config . hfov * Math . PI / 180 , { returnImage : true } ) ; if ( data !== undefined ) { fadeImg = new Image ( ) ; fadeImg . className = 'pnlm-fade-img' ; fadeImg . style . transition = 'opacity ' + ( config . sceneFadeDuration / 1000 ) + 's' ; fadeImg . style . width = '100%' ; fadeImg . style . height = '100%' ; fadeImg . onload = function ( ) { loadScene ( sceneId , targetPitch , targetYaw , targetHfov , true ) ; } ; fadeImg . src = data ; renderContainer . appendChild ( fadeImg ) ; renderer . fadeImg = fadeImg ; return ; } } // Set new pointing if ( targetPitch === 'same' ) { workingPitch = config . pitch ; } else { workingPitch = targetPitch ; } if ( targetYaw === 'same' ) { workingYaw = config . yaw ; } else if ( targetYaw === 'sameAzimuth' ) { workingYaw = config . yaw + ( config . northOffset || 0 ) - ( initialConfig . scenes [ sceneId ] . northOffset || 0 ) ; } else { workingYaw = targetYaw ; } if ( targetHfov === 'same' ) { workingHfov = config . hfov ; } else { workingHfov = targetHfov ; } // Destroy hot spots from previous scene destroyHotSpots ( ) ; // Create the new config for the scene mergeConfig ( sceneId ) ; // Stop motion speed . yaw = speed . pitch = speed . hfov = 0 ; // Reload scene processOptions ( ) ; if ( workingPitch !== undefined ) { config . pitch = workingPitch ; } if ( workingYaw !== undefined ) { config . yaw = workingYaw ; } if ( workingHfov !== undefined ) { config . hfov = workingHfov ; } fireEvent ( 'scenechange' , sceneId ) ; load ( ) ; // Properly handle switching to dynamic scenes update = config . dynamicUpdate === true ; if ( config . dynamic ) { panoImage = config . panorama ; onImageLoad ( ) ; } }


Input: Stop using device orientation . [CODESPLIT] function stopOrientation ( ) { window . removeEventListener ( 'deviceorientation' , orientationListener ) ; controls . orientation . classList . remove ( 'pnlm-orientation-button-active' ) ; orientation = false ; }


Input:
Escapes HTML string ( to mitigate possible DOM XSS attacks ) . [CODESPLIT] function escapeHTML ( s ) { if ( ! initialConfig . escapeHTML ) return String ( s ) . split ( '\n' ) . join ( '
' ) ; return String ( s ) . split ( / & / g ) . join ( '&' ) . split ( '"' ) . join ( '"' ) . split ( "'" ) . join ( ''' ) . split ( '<' ) . join ( '<' ) . split ( '>' ) . join ( '>' ) . split ( '/' ) . join ( '/' ) . split ( '\n' ) . join ( '
' ) ; // Allow line breaks }


Input: Fire listeners attached to specified event . [CODESPLIT] function fireEvent ( type ) { if ( type in externalEventListeners ) { // Reverse iteration is useful, if event listener is removed inside its definition for ( var i = externalEventListeners [ type ] . length ; i > 0 ; i -- ) { externalEventListeners [ type ] [ externalEventListeners [ type ] . length - i ] . apply ( null , [ ] . slice . call ( arguments , 1 ) ) ; } } }


Input: Waits for the latchFunction to return true before proceeding to the next block . [CODESPLIT] function ( latchFunction , optional_timeoutMessage , optional_timeout ) { jasmine . getEnv ( ) . currentSpec . waitsFor . apply ( jasmine . getEnv ( ) . currentSpec , arguments ) ; }


Input: Constructor . [CODESPLIT] function OAuthError ( messageOrError , properties ) { var message = messageOrError instanceof Error ? messageOrError . message : messageOrError ; var error = messageOrError instanceof Error ? messageOrError : null ; if ( _ . isEmpty ( properties ) ) { properties = { } ; } _ . defaults ( properties , { code : 500 } ) ; if ( error ) { properties . inner = error ; } if ( _ . isEmpty ( message ) ) { message = statuses [ properties . code ] ; } this . code = this . status = this . statusCode = properties . code ; this . message = message ; for ( var key in properties ) { if ( key !== 'code' ) { this [ key ] = properties [ key ] ; } } Error . captureStackTrace ( this , OAuthError ) ; }


Input: Constructor . [CODESPLIT] function AuthorizationCodeGrantType ( options ) { options = options || { } ; if ( ! options . model ) { throw new InvalidArgumentError ( 'Missing parameter: `model`' ) ; } if ( ! options . model . getAuthorizationCode ) { throw new InvalidArgumentError ( 'Invalid argument: model does not implement `getAuthorizationCode()`' ) ; } if ( ! options . model . revokeAuthorizationCode ) { throw new InvalidArgumentError ( 'Invalid argument: model does not implement `revokeAuthorizationCode()`' ) ; } if ( ! options . model . saveToken ) { throw new InvalidArgumentError ( 'Invalid argument: model does not implement `saveToken()`' ) ; } AbstractGrantType . call ( this , options ) ; }


Input: Constructor . [CODESPLIT] function PasswordGrantType ( options ) { options = options || { } ; if ( ! options . model ) { throw new InvalidArgumentError ( 'Missing parameter: `model`' ) ; } if ( ! options . model . getUser ) { throw new InvalidArgumentError ( 'Invalid argument: model does not implement `getUser()`' ) ; } if ( ! options . model . saveToken ) { throw new InvalidArgumentError ( 'Invalid argument: model does not implement `saveToken()`' ) ; } AbstractGrantType . call ( this , options ) ; }


Input: Constructor . [CODESPLIT] function TokenHandler ( options ) { options = options || { } ; if ( ! options . accessTokenLifetime ) { throw new InvalidArgumentError ( 'Missing parameter: `accessTokenLifetime`' ) ; } if ( ! options . model ) { throw new InvalidArgumentError ( 'Missing parameter: `model`' ) ; } if ( ! options . refreshTokenLifetime ) { throw new InvalidArgumentError ( 'Missing parameter: `refreshTokenLifetime`' ) ; } if ( ! options . model . getClient ) { throw new InvalidArgumentError ( 'Invalid argument: model does not implement `getClient()`' ) ; } this . accessTokenLifetime = options . accessTokenLifetime ; this . grantTypes = _ . assign ( { } , grantTypes , options . extendedGrantTypes ) ; this . model = options . model ; this . refreshTokenLifetime = options . refreshTokenLifetime ; this . allowExtendedTokenAttributes = options . allowExtendedTokenAttributes ; this . requireClientAuthentication = options . requireClientAuthentication || { } ; this . alwaysIssueNewRefreshToken = options . alwaysIssueNewRefreshToken !== false ; }


Input: Constructor . [CODESPLIT] function AbstractGrantType ( options ) { options = options || { } ; if ( ! options . accessTokenLifetime ) { throw new InvalidArgumentError ( 'Missing parameter: `accessTokenLifetime`' ) ; } if ( ! options . model ) { throw new InvalidArgumentError ( 'Missing parameter: `model`' ) ; } this . accessTokenLifetime = options . accessTokenLifetime ; this . model = options . model ; this . refreshTokenLifetime = options . refreshTokenLifetime ; this . alwaysIssueNewRefreshToken = options . alwaysIssueNewRefreshToken ; }


Input: Constructor . [CODESPLIT] function AuthenticateHandler ( options ) { options = options || { } ; if ( ! options . model ) { throw new InvalidArgumentError ( 'Missing parameter: `model`' ) ; } if ( ! options . model . getAccessToken ) { throw new InvalidArgumentError ( 'Invalid argument: model does not implement `getAccessToken()`' ) ; } if ( options . scope && undefined === options . addAcceptedScopesHeader ) { throw new InvalidArgumentError ( 'Missing parameter: `addAcceptedScopesHeader`' ) ; } if ( options . scope && undefined === options . addAuthorizedScopesHeader ) { throw new InvalidArgumentError ( 'Missing parameter: `addAuthorizedScopesHeader`' ) ; } if ( options . scope && ! options . model . verifyScope ) { throw new InvalidArgumentError ( 'Invalid argument: model does not implement `verifyScope()`' ) ; } this . addAcceptedScopesHeader = options . addAcceptedScopesHeader ; this . addAuthorizedScopesHeader = options . addAuthorizedScopesHeader ; this . allowBearerTokensInQueryString = options . allowBearerTokensInQueryString ; this . model = options . model ; this . scope = options . scope ; }


Input: Constructor . [CODESPLIT] function InvalidArgumentError ( message , properties ) { properties = _ . assign ( { code : 500 , name : 'invalid_argument' } , properties ) ; OAuthError . call ( this , message , properties ) ; }


Input: Constructor . [CODESPLIT] function RefreshTokenGrantType ( options ) { options = options || { } ; if ( ! options . model ) { throw new InvalidArgumentError ( 'Missing parameter: `model`' ) ; } if ( ! options . model . getRefreshToken ) { throw new InvalidArgumentError ( 'Invalid argument: model does not implement `getRefreshToken()`' ) ; } if ( ! options . model . revokeToken ) { throw new InvalidArgumentError ( 'Invalid argument: model does not implement `revokeToken()`' ) ; } if ( ! options . model . saveToken ) { throw new InvalidArgumentError ( 'Invalid argument: model does not implement `saveToken()`' ) ; } AbstractGrantType . call ( this , options ) ; }


Input: Constructor . [CODESPLIT] function Request ( options ) { options = options || { } ; if ( ! options . headers ) { throw new InvalidArgumentError ( 'Missing parameter: `headers`' ) ; } if ( ! options . method ) { throw new InvalidArgumentError ( 'Missing parameter: `method`' ) ; } if ( ! options . query ) { throw new InvalidArgumentError ( 'Missing parameter: `query`' ) ; } this . body = options . body || { } ; this . headers = { } ; this . method = options . method ; this . query = options . query ; // Store the headers in lower case. for ( var field in options . headers ) { if ( options . headers . hasOwnProperty ( field ) ) { this . headers [ field . toLowerCase ( ) ] = options . headers [ field ] ; } } // Store additional properties of the request object passed in for ( var property in options ) { if ( options . hasOwnProperty ( property ) && ! this [ property ] ) { this [ property ] = options [ property ] ; } } }


Input: Constructor . [CODESPLIT] function AuthorizeHandler ( options ) { options = options || { } ; if ( options . authenticateHandler && ! options . authenticateHandler . handle ) { throw new InvalidArgumentError ( 'Invalid argument: authenticateHandler does not implement `handle()`' ) ; } if ( ! options . authorizationCodeLifetime ) { throw new InvalidArgumentError ( 'Missing parameter: `authorizationCodeLifetime`' ) ; } if ( ! options . model ) { throw new InvalidArgumentError ( 'Missing parameter: `model`' ) ; } if ( ! options . model . getClient ) { throw new InvalidArgumentError ( 'Invalid argument: model does not implement `getClient()`' ) ; } if ( ! options . model . saveAuthorizationCode ) { throw new InvalidArgumentError ( 'Invalid argument: model does not implement `saveAuthorizationCode()`' ) ; } this . allowEmptyState = options . allowEmptyState ; this . authenticateHandler = options . authenticateHandler || new AuthenticateHandler ( options ) ; this . authorizationCodeLifetime = options . authorizationCodeLifetime ; this . model = options . model ; }


Input: Constructor . [CODESPLIT] function ClientCredentialsGrantType ( options ) { options = options || { } ; if ( ! options . model ) { throw new InvalidArgumentError ( 'Missing parameter: `model`' ) ; } if ( ! options . model . getUserFromClient ) { throw new InvalidArgumentError ( 'Invalid argument: model does not implement `getUserFromClient()`' ) ; } if ( ! options . model . saveToken ) { throw new InvalidArgumentError ( 'Invalid argument: model does not implement `saveToken()`' ) ; } AbstractGrantType . call ( this , options ) ; }


Input: Constructor . [CODESPLIT] function BearerTokenType ( accessToken , accessTokenLifetime , refreshToken , scope , customAttributes ) { if ( ! accessToken ) { throw new InvalidArgumentError ( 'Missing parameter: `accessToken`' ) ; } this . accessToken = accessToken ; this . accessTokenLifetime = accessTokenLifetime ; this . refreshToken = refreshToken ; this . scope = scope ; if ( customAttributes ) { this . customAttributes = customAttributes ; } }


Input: Constructor . [CODESPLIT] function Response ( options ) { options = options || { } ; this . body = options . body || { } ; this . headers = { } ; this . status = 200 ; // Store the headers in lower case. for ( var field in options . headers ) { if ( options . headers . hasOwnProperty ( field ) ) { this . headers [ field . toLowerCase ( ) ] = options . headers [ field ] ; } } // Store additional properties of the response object passed in for ( var property in options ) { if ( options . hasOwnProperty ( property ) && ! this [ property ] ) { this [ property ] = options [ property ] ; } } }