diff --git a/app/static/app/js/ModelView.jsx b/app/static/app/js/ModelView.jsx index 6c163a06..8154aa5e 100644 --- a/app/static/app/js/ModelView.jsx +++ b/app/static/app/js/ModelView.jsx @@ -11,10 +11,8 @@ require('./vendor/OBJLoader'); THREE.MTLLoader = require('./vendor/MTLLoader'); THREE.OrbitControls = require('three-orbit-controls')(THREE); -import Stats from './vendor/Stats'; import dat from './vendor/dat.gui'; import Potree from './vendor/potree'; -import ProgressBar from './vendor/potree/ProgressBar'; class ModelView extends React.Component { static defaultProps = { @@ -65,6 +63,39 @@ class ModelView extends React.Component { componentDidMount() { var container = this.container; + window.viewer = new Potree.Viewer(container); + viewer.setEDLEnabled(true); + viewer.setPointSize(1); + viewer.setMaterial("RGB"); // ["RGB", "Elevation", "Classification", "Intensity"] + viewer.setFOV(60); + viewer.setPointSizing("Adaptive"); // ["Fixed", "Attenuated", "Adaptive"] + viewer.setQuality("Squares"); // ["Squares", "Circles", "Interpolation"] + viewer.setPointBudget(1*1000*1000); + viewer.setIntensityRange(0, 300); + viewer.setWeightClassification(1); + viewer.setBackground("gradient"); // ["skybox", "gradient", "black", "white"]; + viewer.loadSettingsFromURL(); + + viewer.setDescription("Point cloud courtesy of sigeom sa"); + + viewer.loadGUI(() => { + viewer.setLanguage('en'); + $("#menu_tools").next().show(); + // viewer.toggleSidebar(); + }); + + // Sigeom + Potree.loadPointCloud(this.potreeFilePath(), "TODO.sa", e => { + let scene = viewer.scene; + scene.addPointCloud(e.pointcloud); + + //scene.view.position.set(589974.341, 231698.397, 986.146); + //scene.view.lookAt(new THREE.Vector3(589851.587, 231428.213, 715.634)); + viewer.fitToScreen(); + }); + + return; + var sceneProperties = { path: this.potreeFilePath(), cameraPosition: null, @@ -1060,9 +1091,23 @@ class ModelView extends React.Component {
{ this.container = domNode; }} style={{height: "100%", width: "100%", position: "relative"}} onContextMenu={(e) => {e.preventDefault();}}> + +
{ this.container = domNode; }}> +
+
+
+
+
+ +
+
+ +
+ { this.texturedModelStandby = domNode; }} @@ -1078,3 +1123,4 @@ class ModelView extends React.Component { } export default ModelView; + \ No newline at end of file diff --git a/app/static/app/js/ModelView.jsx.bak b/app/static/app/js/ModelView.jsx.bak new file mode 100644 index 00000000..6c163a06 --- /dev/null +++ b/app/static/app/js/ModelView.jsx.bak @@ -0,0 +1,1080 @@ +import React from 'react'; +import './css/ModelView.scss'; +import ErrorMessage from './components/ErrorMessage'; +import SwitchModeButton from './components/SwitchModeButton'; +import AssetDownloadButtons from './components/AssetDownloadButtons'; +import Standby from './components/Standby'; +import $ from 'jquery'; + +const THREE = require('three'); // import does not work :/ +require('./vendor/OBJLoader'); +THREE.MTLLoader = require('./vendor/MTLLoader'); +THREE.OrbitControls = require('three-orbit-controls')(THREE); + +import Stats from './vendor/Stats'; +import dat from './vendor/dat.gui'; +import Potree from './vendor/potree'; +import ProgressBar from './vendor/potree/ProgressBar'; + +class ModelView extends React.Component { + static defaultProps = { + task: null + }; + + static propTypes = { + task: React.PropTypes.object.isRequired, // The object should contain two keys: {id: , project: } + }; + + constructor(props){ + super(props); + + this.state = { + error: "" + } + } + + assetsPath(){ + return `/api/projects/${this.props.task.project}/tasks/${this.props.task.id}/assets` + } + + potreeFilePath(){ + return this.assetsPath() + '/potree_pointcloud/cloud.js'; + } + + texturedModelDirectoryPath(){ + return this.assetsPath() + '/odm_texturing/'; + } + + hasGeoreferencedAssets(){ + return this.props.task.available_assets.indexOf('geotiff') !== -1; + } + + objFilePath(){ + let file = this.hasGeoreferencedAssets() ? + 'odm_textured_model_geo.obj' : + 'odm_textured_model.obj'; + + return this.texturedModelDirectoryPath() + file; + } + + mtlFilename(){ + // For some reason, loading odm_textured_model_geo.mtl does not load textures properly + return 'odm_textured_model.mtl'; + } + + componentDidMount() { + var container = this.container; + + var sceneProperties = { + path: this.potreeFilePath(), + cameraPosition: null, + cameraTarget: null, + sizeType: "Adaptive", // options: "Fixed", "Attenuated", "Adaptive" + quality: "Interpolation", // options: "Squares", "Circles", "Interpolation", "Splats" + fov: 75, // field of view in degree + material: "RGB", // options: "RGB", "Height", "Intensity", "Classification" + pointLimit: 1, // max number of points in millions + navigation: "Orbit", // options: "Earth", "Orbit", "Flight" + pointSize: 1.2 + }; + + if(sceneProperties.quality === null){ + sceneProperties.quality = "Squares"; + } + + var fov = sceneProperties.fov; + var pointSize = sceneProperties.pointSize; + var pointCountTarget = sceneProperties.pointLimit; + var opacity = 1; + var pointSizeType = null; + var pointColorType = null; + var pointShape = Potree.PointShape.SQUARE; + var clipMode = Potree.ClipMode.HIGHLIGHT_INSIDE; + var quality = null; + var isFlipYZ = false; + var useDEMCollisions = false; + var minNodeSize = 100; + + var showStats = false; + var showBoundingBox = false; + var freeze = false; + + var fpControls; + var orbitControls; + var earthControls; + var controls; + + var progressBar = new ProgressBar(container); + + var pointcloudPath = sceneProperties.path; + + var gui; + var renderer; + var camera; + var scene; + var scenePointCloud; + var sceneBG, cameraBG; + var pointcloud; + var skybox; + var stats; + var clock = new THREE.Clock(); + var showSkybox = false; + var measuringTool; + var profileTool; + var volumeTool; + var transformationTool; + var referenceFrame; + + function setPointSizeType(value){ + if(value === "Fixed"){ + pointSizeType = Potree.PointSizeType.FIXED; + }else if(value === "Attenuated"){ + pointSizeType = Potree.PointSizeType.ATTENUATED; + }else if(value === "Adaptive"){ + pointSizeType = Potree.PointSizeType.ADAPTIVE; + } + }; + + function setQuality(value){ + if(value == "Interpolation" && !Potree.Features.SHADER_INTERPOLATION.isSupported()){ + quality = "Squares"; + }else if(value == "Splats" && !Potree.Features.SHADER_SPLATS.isSupported()){ + quality = "Squares"; + }else{ + quality = value; + } + }; + + function setMaterial(value){ + if(value === "RGB"){ + pointColorType = Potree.PointColorType.RGB; + }else if(value === "Color"){ + pointColorType = Potree.PointColorType.COLOR; + }else if(value === "Elevation"){ + pointColorType = Potree.PointColorType.HEIGHT; + }else if(value === "Intensity"){ + pointColorType = Potree.PointColorType.INTENSITY; + }else if(value === "Intensity Gradient"){ + pointColorType = Potree.PointColorType.INTENSITY_GRADIENT; + }else if(value === "Classification"){ + pointColorType = Potree.PointColorType.CLASSIFICATION; + }else if(value === "Return Number"){ + pointColorType = Potree.PointColorType.RETURN_NUMBER; + }else if(value === "Source"){ + pointColorType = Potree.PointColorType.SOURCE; + }else if(value === "Tree Depth"){ + pointColorType = Potree.PointColorType.TREE_DEPTH; + }else if(value === "Point Index"){ + pointColorType = Potree.PointColorType.POINT_INDEX; + }else if(value === "Normal"){ + pointColorType = Potree.PointColorType.NORMAL; + }else if(value === "Phong"){ + pointColorType = Potree.PointColorType.PHONG; + } + }; + + function initGUI(){ + + setPointSizeType(sceneProperties.sizeType); + setQuality(sceneProperties.quality); + setMaterial(sceneProperties.material); + + // dat.gui + gui = new dat.GUI({autoPlace:false}); + $(gui.domElement).prependTo(container); + + var params = { + "Textured Model": false, + "Points": pointCountTarget, + "Point Size": pointSize, + "FOV": sceneProperties.fov, + "Opacity": opacity, + "Size Type" : sceneProperties.sizeType, + "Show Octree" : false, + "Materials" : sceneProperties.material, + "Clip Mode": "Highlight Inside", + "Quality": sceneProperties.quality, + "Skybox": false, + "WebGL Stats": showStats, + "Show Origin": false, + "Bounding Box": showBoundingBox, + "DEM Collisions": useDEMCollisions, + "Minimum Node Size": minNodeSize, + "Freeze": freeze + }; + + var pToggleTexturedModel = gui.add(params, 'Textured Model'); + pToggleTexturedModel.onChange(toggleTexturedModel); + + var pPoints = gui.add(params, 'Points', 0, 4); + pPoints.onChange(function(value){ + pointCountTarget = value ; + }); + + var fAppearance = gui.addFolder('Appearance'); + + var pPointSize = fAppearance.add(params, 'Point Size', 0, 3); + pPointSize.onChange(function(value){ + pointSize = value; + }); + + var fFOV = fAppearance.add(params, 'FOV', 20, 100); + fFOV.onChange(function(value){ + fov = value; + }); + + var pOpacity = fAppearance.add(params, 'Opacity', 0, 1); + pOpacity.onChange(function(value){ + opacity = value; + }); + + var pSizeType = fAppearance.add(params, 'Size Type', [ "Fixed", "Attenuated", "Adaptive"]); + pSizeType.onChange(function(value){ + setPointSizeType(value); + }); + + var options = []; + var attributes = pointcloud.pcoGeometry.pointAttributes + if(attributes === "LAS" || attributes === "LAZ"){ + options = [ + "RGB", "Color", "Elevation", "Intensity", "Intensity Gradient", + "Classification", "Return Number", "Source", + "Tree Depth"]; + }else{ + for(var i = 0; i < attributes.attributes.length; i++){ + var attribute = attributes.attributes[i]; + + if(attribute === Potree.PointAttribute.COLOR_PACKED){ + options.push("RGB"); + }else if(attribute === Potree.PointAttribute.INTENSITY){ + options.push("Intensity"); + options.push("Intensity Gradient"); + }else if(attribute === Potree.PointAttribute.CLASSIFICATION){ + options.push("Classification"); + } + } + if(attributes.hasNormals()){ + options.push("Phong"); + options.push("Normal"); + } + + options.push("Elevation"); + options.push("Color"); + options.push("Tree Depth"); + } + + // default material is not available. set material to Elevation + if(options.indexOf(params.Materials) < 0){ + console.error("Default Material '" + params.Material + "' is not available. Using Elevation instead"); + setMaterial("Elevation"); + params.Materials = "Elevation"; + } + + + var pMaterial = fAppearance.add(params, 'Materials',options); + pMaterial.onChange(function(value){ + setMaterial(value); + }); + + var qualityOptions = ["Squares", "Circles"]; + if(Potree.Features.SHADER_INTERPOLATION.isSupported()){ + qualityOptions.push("Interpolation"); + } + if(Potree.Features.SHADER_SPLATS.isSupported()){ + qualityOptions.push("Splats"); + } + var pQuality = fAppearance.add(params, 'Quality', qualityOptions); + pQuality.onChange(function(value){ + quality = value; + }); + + var pSykbox = fAppearance.add(params, 'Skybox'); + pSykbox.onChange(function(value){ + showSkybox = value; + }); + + var fSettings = gui.addFolder('Settings'); + + var pClipMode = fSettings.add(params, 'Clip Mode', [ "No Clipping", "Clip Outside", "Highlight Inside"]); + pClipMode.onChange(function(value){ + if(value === "No Clipping"){ + clipMode = Potree.ClipMode.DISABLED; + }else if(value === "Clip Outside"){ + clipMode = Potree.ClipMode.CLIP_OUTSIDE; + }else if(value === "Highlight Inside"){ + clipMode = Potree.ClipMode.HIGHLIGHT_INSIDE; + } + }); + + var pDEMCollisions = fSettings.add(params, 'DEM Collisions'); + pDEMCollisions.onChange(function(value){ + useDEMCollisions = value; + }); + + var pMinNodeSize = fSettings.add(params, 'Minimum Node Size', 0, 1500); + pMinNodeSize.onChange(function(value){ + minNodeSize = value; + }); + + var fDebug = gui.addFolder('Debug'); + + + var pStats = fDebug.add(params, 'WebGL Stats'); + pStats.onChange(function(value){ + showStats = value; + }); + + var pShowOrigin = fDebug.add(params, 'Show Origin'); + pShowOrigin.onChange(toggleOrigin); + + var pBoundingBox = fDebug.add(params, 'Bounding Box'); + pBoundingBox.onChange(function(value){ + showBoundingBox = value; + }); + + var pFreeze = fDebug.add(params, 'Freeze'); + pFreeze.onChange(function(value){ + freeze = value; + }); + + // stats + stats = new Stats(); + stats.domElement.style.position = 'absolute'; + stats.domElement.style.top = '0px'; + stats.domElement.style.margin = '5px'; + container.appendChild( stats.domElement ); + } + + var width = container.clientWidth; + var height = container.clientHeight; + + const initThree = () => { + var aspect = width / height; + var near = 0.1; + var far = 1000*1000; + + scene = new THREE.Scene(); + scenePointCloud = new THREE.Scene(); + sceneBG = new THREE.Scene(); + + camera = new THREE.PerspectiveCamera(fov, aspect, near, far); + cameraBG = new THREE.Camera(); + camera.rotation.order = 'ZYX'; + + referenceFrame = new THREE.Object3D(); + scenePointCloud.add(referenceFrame); + + renderer = new THREE.WebGLRenderer(); + renderer.setSize(width, height); + renderer.autoClear = false; + container.appendChild(renderer.domElement); + + skybox = Potree.utils.loadSkybox("/static/app/potree/textures/skybox/"); + + // camera and controls + camera.position.set(-304, 372, 318); + camera.rotation.y = -Math.PI / 4; + camera.rotation.x = -Math.PI / 6; + + //useOrbitControls(); + earthControls = new THREE.EarthControls(camera, renderer, scenePointCloud); + earthControls.addEventListener("proposeTransform", function(event){ + if(!pointcloud || !useDEMCollisions){ + return; + } + + var demHeight = pointcloud.getDEMHeight(event.newPosition); + if(event.newPosition.y < demHeight){ + event.objections++; + } + }); + useEarthControls(); + + // enable frag_depth extension for the interpolation shader, if available + renderer.context.getExtension("EXT_frag_depth"); + + // load pointcloud + if(pointcloudPath.indexOf("cloud.js") > 0){ + Potree.POCLoader.load(pointcloudPath, (geometry, error) => { + if (error){ + console.log(error); + this.setState({error: "Could not load point cloud. This task doesn't seem to have one. Try processing the task again."}); + return; + } + + pointcloud = new Potree.PointCloudOctree(geometry); + + pointcloud.material.pointSizeType = Potree.PointSizeType.ADAPTIVE; + pointcloud.material.size = pointSize; + pointcloud.visiblePointsTarget = pointCountTarget * 1000 * 1000; + + referenceFrame.add(pointcloud); + + referenceFrame.updateMatrixWorld(true); + var sg = pointcloud.boundingSphere.clone().applyMatrix4(pointcloud.matrixWorld); + + referenceFrame.position.copy(sg.center).multiplyScalar(-1); + referenceFrame.updateMatrixWorld(true); + + if(sg.radius > 50*1000){ + camera.near = 10; + }else if(sg.radius > 10*1000){ + camera.near = 2; + }else if(sg.radius > 1000){ + camera.near = 1; + }else if(sg.radius > 100){ + camera.near = 0.5; + }else{ + camera.near = 0.1; + } + + + flipYZ(); + camera.zoomTo(pointcloud, 1); + initGUI(); + + earthControls.pointclouds.push(pointcloud); + + if(sceneProperties.navigation === "Earth"){ + useEarthControls(); + }else if(sceneProperties.navigation === "Orbit"){ + useOrbitControls(); + }else if(sceneProperties.navigation === "Flight"){ + useFPSControls(); + }else{ + console.warning("No navigation mode specified. Using OrbitControls"); + useOrbitControls(); + } + + if(sceneProperties.cameraPosition != null){ + var cp = new THREE.Vector3(sceneProperties.cameraPosition[0], sceneProperties.cameraPosition[1], sceneProperties.cameraPosition[2]); + camera.position.copy(cp); + } + + if(sceneProperties.cameraTarget != null){ + var ct = new THREE.Vector3(sceneProperties.cameraTarget[0], sceneProperties.cameraTarget[1], sceneProperties.cameraTarget[2]); + camera.lookAt(ct); + + if(sceneProperties.navigation === "Orbit"){ + controls.target.copy(ct); + } + } + }); + }else{ + throw new Error("pointcloudPath does not contain a reference to cloud.js"); + } + + measuringTool = new Potree.MeasuringTool(scenePointCloud, camera, renderer); + profileTool = new Potree.ProfileTool(scenePointCloud, camera, renderer); + volumeTool = new Potree.VolumeTool(scenePointCloud, camera, renderer); + transformationTool = new Potree.TransformationTool(scenePointCloud, camera, renderer); + + // background + var texture = Potree.utils.createBackgroundTexture(512, 512); + + texture.minFilter = texture.magFilter = THREE.NearestFilter; + texture.minFilter = texture.magFilter = THREE.LinearFilter; + + var bg = new THREE.Mesh( + new THREE.PlaneBufferGeometry(2, 2, 0), + new THREE.MeshBasicMaterial({ + map: texture + }) + ); + bg.material.depthTest = false; + bg.material.depthWrite = false; + sceneBG.add(bg); + + window.addEventListener( 'keydown', this.onKeyDown, false ); + + var light = new THREE.AmbientLight( 0xffffff ); // soft white light + scenePointCloud.add( light ); + } + + const toggleTexturedModel = (() => { + let modelReference = null, + initializingModel = false; + + return (flag) => { + if (flag){ + // Need to load model for the first time? + if (modelReference === null && !initializingModel){ + + initializingModel = true; + this.texturedModelStandby.show(); + + const mtlLoader = new THREE.MTLLoader(); + mtlLoader.setTexturePath(this.texturedModelDirectoryPath()); + mtlLoader.setPath(this.texturedModelDirectoryPath()); + + mtlLoader.load(this.mtlFilename(), (materials) => { + materials.preload(); + + const objLoader = new THREE.OBJLoader(); + objLoader.setMaterials(materials); + objLoader.load(this.objFilePath(), (object) => { + + // ODM models are Y-up + object.rotateX(THREE.Math.degToRad(-90)); + + // Bring the model close to center + if (object.children.length > 0){ + const geom = object.children[0].geometry; + + // Compute center + geom.computeBoundingBox(); + + const center = geom.boundingBox.getCenter(); + + object.translateX(-center.x); + object.translateY(-center.y); + object.translateZ(-center.z); + } + + scenePointCloud.add(object); + pointcloud.visible = false; + + modelReference = object; + initializingModel = false; + this.texturedModelStandby.hide(); + }); + }); + }else{ + // Already initialized + modelReference.visible = true; + pointcloud.visible = false; + } + }else{ + modelReference.visible = false; + pointcloud.visible = true; + } + } + })(); + + const toggleOrigin = (function(){ + let grid = null, + axisHelper = null; + return function(flag){ + if (flag){ + if (grid === null){ + grid = Potree.utils.createGrid(5, 5, 2); + axisHelper = new THREE.AxisHelper( 10 ); + scene.add( grid ); + scene.add( axisHelper ); + }else{ + grid.visible = axisHelper.visible = true; + } + }else{ + grid.visible = axisHelper.visible = false; + } + }; + })(); + + function flipYZ(){ + isFlipYZ = !isFlipYZ; + + if(isFlipYZ){ + referenceFrame.matrix.copy(new THREE.Matrix4()); + referenceFrame.applyMatrix(new THREE.Matrix4().set( + 1,0,0,0, + 0,0,1,0, + 0,-1,0,0, + 0,0,0,1 + )); + + }else{ + referenceFrame.matrix.copy(new THREE.Matrix4()); + referenceFrame.applyMatrix(new THREE.Matrix4().set( + 1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 0,0,0,1 + )); + } + + referenceFrame.updateMatrixWorld(true); + pointcloud.updateMatrixWorld(); + var sg = pointcloud.boundingSphere.clone().applyMatrix4(pointcloud.matrixWorld); + referenceFrame.position.copy(sg.center).multiplyScalar(-1); + referenceFrame.updateMatrixWorld(true); + referenceFrame.position.y -= pointcloud.getWorldPosition().y; + referenceFrame.updateMatrixWorld(true); + } + + this.onKeyDown = function(event){ + if(event.keyCode === 69){ + // e pressed + + transformationTool.translate(); + }else if(event.keyCode === 82){ + // r pressed + + transformationTool.scale(); + }else if(event.keyCode === 84){ + // r pressed + + transformationTool.rotate(); + } + }; + + var intensityMax = null; + var heightMin = null; + var heightMax = null; + + function update(){ + Potree.pointLoadLimit = pointCountTarget * 2 * 1000 * 1000; + + if(pointcloud){ + + var bbWorld = Potree.utils.computeTransformedBoundingBox(pointcloud.boundingBox, pointcloud.matrixWorld); + + if(!intensityMax){ + var root = pointcloud.pcoGeometry.root; + if(root != null && root.loaded){ + var attributes = pointcloud.pcoGeometry.root.geometry.attributes; + if(attributes.intensity){ + var array = attributes.intensity.array; + var max = 0; + for(var i = 0; i < array.length; i++){ + max = Math.max(array[i]); + } + + if(max <= 1){ + intensityMax = 1; + }else if(max <= 256){ + intensityMax = 255; + }else{ + intensityMax = max; + } + } + } + } + + if(heightMin === null){ + heightMin = bbWorld.min.y; + heightMax = bbWorld.max.y; + } + + pointcloud.material.clipMode = clipMode; + pointcloud.material.heightMin = heightMin; + pointcloud.material.heightMax = heightMax; + pointcloud.material.intensityMin = 0; + pointcloud.material.intensityMax = intensityMax; + pointcloud.showBoundingBox = showBoundingBox; + pointcloud.generateDEM = useDEMCollisions; + pointcloud.minimumNodePixelSize = minNodeSize; + + if(!freeze){ + pointcloud.update(camera, renderer); + } + } + + if(stats && showStats){ + stats.domElement.style.display = 'block'; + stats.update(); + }else if (stats){ + stats.domElement.style.display = 'none'; + } + + camera.fov = fov; + + if(controls){ + controls.update(clock.getDelta()); + } + + // update progress bar + if(pointcloud){ + var progress = pointcloud.progress; + + progressBar.progress = progress; + + var message; + if(progress === 0 || pointcloud instanceof Potree.PointCloudArena4D){ + message = "loading"; + }else{ + message = "loading: " + parseInt(progress*100) + "%"; + } + progressBar.message = message; + + if(progress === 1){ + progressBar.hide(); + }else if(progress < 1){ + progressBar.show(); + } + } + + volumeTool.update(); + transformationTool.update(); + profileTool.update(); + + + var clipBoxes = []; + + for(var i = 0; i < profileTool.profiles.length; i++){ + var profile = profileTool.profiles[i]; + + for(var j = 0; j < profile.boxes.length; j++){ + var box = profile.boxes[j]; + box.updateMatrixWorld(); + var boxInverse = new THREE.Matrix4().getInverse(box.matrixWorld); + clipBoxes.push(boxInverse); + } + } + + for(var i = 0; i < volumeTool.volumes.length; i++){ + var volume = volumeTool.volumes[i]; + + if(volume.clip){ + volume.updateMatrixWorld(); + var boxInverse = new THREE.Matrix4().getInverse(volume.matrixWorld); + + clipBoxes.push(boxInverse); + } + } + + if(pointcloud){ + pointcloud.material.setClipBoxes(clipBoxes); + } + } + + function useEarthControls(){ + if(controls){ + controls.enabled = false; + } + + controls = earthControls; + controls.enabled = true; + } + + function useFPSControls(){ + if(controls){ + controls.enabled = false; + } + if(!fpControls){ + fpControls = new THREE.FirstPersonControls(camera, renderer.domElement); + fpControls.addEventListener("proposeTransform", function(event){ + if(!pointcloud || !useDEMCollisions){ + return; + } + + var demHeight = pointcloud.getDEMHeight(event.newPosition); + if(event.newPosition.y < demHeight){ + event.objections++; + + var counterProposal = event.newPosition.clone(); + counterProposal.y = demHeight; + + event.counterProposals.push(counterProposal); + } + }); + } + + controls = fpControls; + controls.enabled = true; + + controls.moveSpeed = pointcloud.boundingSphere.radius / 6; + } + + function useOrbitControls(){ + if(controls){ + controls.enabled = false; + } + if(!orbitControls){ + orbitControls = new Potree.OrbitControls(camera, renderer.domElement); + orbitControls.addEventListener("proposeTransform", function(event){ + if(!pointcloud || !useDEMCollisions){ + return; + } + + var demHeight = pointcloud.getDEMHeight(event.newPosition); + if(event.newPosition.y < demHeight){ + event.objections++; + + var counterProposal = event.newPosition.clone(); + counterProposal.y = demHeight; + + event.counterProposals.push(counterProposal); + } + }); + } + + controls = orbitControls; + controls.enabled = true; + + if(pointcloud){ + controls.target.copy(pointcloud.boundingSphere.center.clone().applyMatrix4(pointcloud.matrixWorld)); + } + } + + this.onResize = function(){ + width = container.clientWidth; + height = container.clientHeight; + }; + + window.addEventListener("resize", this.onResize); + + var PotreeRenderer = function(){ + this.render = function(){ + var aspect = width / height; + + camera.aspect = aspect; + camera.updateProjectionMatrix(); + + renderer.setSize(width, height); + + + // render skybox + if(showSkybox){ + skybox.camera.rotation.copy(camera.rotation); + renderer.render(skybox.scene, skybox.camera); + }else{ + renderer.render(sceneBG, cameraBG); + } + + if(pointcloud){ + if(pointcloud.originalMaterial){ + pointcloud.material = pointcloud.originalMaterial; + } + + var bbWorld = Potree.utils.computeTransformedBoundingBox(pointcloud.boundingBox, pointcloud.matrixWorld); + + pointcloud.visiblePointsTarget = pointCountTarget * 1000 * 1000; + pointcloud.material.size = pointSize; + pointcloud.material.opacity = opacity; + pointcloud.material.pointColorType = pointColorType; + pointcloud.material.pointSizeType = pointSizeType; + pointcloud.material.pointShape = (quality === "Circles") ? Potree.PointShape.CIRCLE : Potree.PointShape.SQUARE; + pointcloud.material.interpolate = (quality === "Interpolation"); + pointcloud.material.weighted = false; + } + + // render scene + renderer.render(scene, camera); + renderer.render(scenePointCloud, camera); + + profileTool.render(); + volumeTool.render(); + + renderer.clearDepth(); + measuringTool.render(); + transformationTool.render(); + }; + }; + + // high quality rendering using splats + var HighQualityRenderer = function(){ + var depthMaterial = null; + var attributeMaterial = null; + var normalizationMaterial = null; + + var rtDepth; + var rtNormalize; + + var initHQSPlats = function(){ + if(depthMaterial != null){ + return; + } + + depthMaterial = new Potree.PointCloudMaterial(); + attributeMaterial = new Potree.PointCloudMaterial(); + + depthMaterial.pointColorType = Potree.PointColorType.DEPTH; + depthMaterial.pointShape = Potree.PointShape.CIRCLE; + depthMaterial.interpolate = false; + depthMaterial.weighted = false; + depthMaterial.minSize = 2; + + attributeMaterial.pointShape = Potree.PointShape.CIRCLE; + attributeMaterial.interpolate = false; + attributeMaterial.weighted = true; + attributeMaterial.minSize = 2; + + rtDepth = new THREE.WebGLRenderTarget( 1024, 1024, { + minFilter: THREE.NearestFilter, + magFilter: THREE.NearestFilter, + format: THREE.RGBAFormat, + type: THREE.FloatType + } ); + + rtNormalize = new THREE.WebGLRenderTarget( 1024, 1024, { + minFilter: THREE.LinearFilter, + magFilter: THREE.NearestFilter, + format: THREE.RGBAFormat, + type: THREE.FloatType + } ); + + var uniformsNormalize = { + depthMap: { type: "t", value: rtDepth }, + texture: { type: "t", value: rtNormalize } + }; + + normalizationMaterial = new THREE.ShaderMaterial({ + uniforms: uniformsNormalize, + vertexShader: Potree.Shaders["normalize.vs"], + fragmentShader: Potree.Shaders["normalize.fs"] + }); + } + + var resize = function(width, height){ + if(rtDepth.width == width && rtDepth.height == height){ + return; + } + + rtDepth.dispose(); + rtNormalize.dispose(); + + camera.aspect = width / height; + camera.updateProjectionMatrix(); + + renderer.setSize(width, height); + rtDepth.setSize(width, height); + rtNormalize.setSize(width, height); + }; + + // render with splats + this.render = function(renderer){ + initHQSPlats(); + + resize(width, height); + + + renderer.clear(); + if(showSkybox){ + skybox.camera.rotation.copy(camera.rotation); + renderer.render(skybox.scene, skybox.camera); + }else{ + renderer.render(sceneBG, cameraBG); + } + renderer.render(scene, camera); + + if(pointcloud){ + + depthMaterial.uniforms.octreeSize.value = pointcloud.pcoGeometry.boundingBox.size().x; + attributeMaterial.uniforms.octreeSize.value = pointcloud.pcoGeometry.boundingBox.size().x; + + pointcloud.visiblePointsTarget = pointCountTarget * 1000 * 1000; + var originalMaterial = pointcloud.material; + + {// DEPTH PASS + depthMaterial.size = pointSize; + depthMaterial.pointSizeType = pointSizeType; + depthMaterial.screenWidth = width; + depthMaterial.screenHeight = height; + depthMaterial.uniforms.visibleNodes.value = pointcloud.material.visibleNodesTexture; + depthMaterial.uniforms.octreeSize.value = pointcloud.pcoGeometry.boundingBox.size().x; + depthMaterial.fov = camera.fov * (Math.PI / 180); + depthMaterial.spacing = pointcloud.pcoGeometry.spacing; + depthMaterial.near = camera.near; + depthMaterial.far = camera.far; + depthMaterial.heightMin = heightMin; + depthMaterial.heightMax = heightMax; + depthMaterial.uniforms.visibleNodes.value = pointcloud.material.visibleNodesTexture; + depthMaterial.uniforms.octreeSize.value = pointcloud.pcoGeometry.boundingBox.size().x; + depthMaterial.bbSize = pointcloud.material.bbSize; + depthMaterial.treeType = pointcloud.material.treeType; + + scenePointCloud.overrideMaterial = depthMaterial; + renderer.clearTarget( rtDepth, true, true, true ); + renderer.render(scenePointCloud, camera, rtDepth); + scenePointCloud.overrideMaterial = null; + } + + {// ATTRIBUTE PASS + attributeMaterial.size = pointSize; + attributeMaterial.pointSizeType = pointSizeType; + attributeMaterial.screenWidth = width; + attributeMaterial.screenHeight = height; + attributeMaterial.pointColorType = pointColorType; + attributeMaterial.depthMap = rtDepth; + attributeMaterial.uniforms.visibleNodes.value = pointcloud.material.visibleNodesTexture; + attributeMaterial.uniforms.octreeSize.value = pointcloud.pcoGeometry.boundingBox.size().x; + attributeMaterial.fov = camera.fov * (Math.PI / 180); + attributeMaterial.spacing = pointcloud.pcoGeometry.spacing; + attributeMaterial.near = camera.near; + attributeMaterial.far = camera.far; + attributeMaterial.heightMin = heightMin; + attributeMaterial.heightMax = heightMax; + attributeMaterial.intensityMin = pointcloud.material.intensityMin; + attributeMaterial.intensityMax = pointcloud.material.intensityMax; + attributeMaterial.setClipBoxes(pointcloud.material.clipBoxes); + attributeMaterial.clipMode = pointcloud.material.clipMode; + attributeMaterial.bbSize = pointcloud.material.bbSize; + attributeMaterial.treeType = pointcloud.material.treeType; + + scenePointCloud.overrideMaterial = attributeMaterial; + renderer.clearTarget( rtNormalize, true, true, true ); + renderer.render(scenePointCloud, camera, rtNormalize); + scenePointCloud.overrideMaterial = null; + } + + {// NORMALIZATION PASS + normalizationMaterial.uniforms.depthMap.value = rtDepth; + normalizationMaterial.uniforms.texture.value = rtNormalize; + Potree.utils.screenPass.render(renderer, normalizationMaterial); + } + + pointcloud.material = originalMaterial; + + volumeTool.render(); + renderer.clearDepth(); + profileTool.render(); + measuringTool.render(); + transformationTool.render(); + } + + + } + }; + + var potreeRenderer = new PotreeRenderer(); + var highQualityRenderer = null; + + function loop() { + requestAnimationFrame(loop); + update(); + + if(quality === "Splats"){ + if(!highQualityRenderer){ + highQualityRenderer = new HighQualityRenderer(); + } + highQualityRenderer.render(renderer); + }else{ + potreeRenderer.render(); + } + }; + + initThree(); + loop(); + } + + componentWillUnmount(){ + window.removeEventListener("resize", this.onResize); + window.removeEventListener("keydown", this.onKeyDown); + } + + // React render + render(){ + const showSwitchModeButton = this.props.task.available_assets.indexOf('geotiff') !== -1; + + return (
+ +
{ this.container = domNode; }} + style={{height: "100%", width: "100%", position: "relative"}} + onContextMenu={(e) => {e.preventDefault();}}> + { this.texturedModelStandby = domNode; }} + /> + {showSwitchModeButton ? + : ""} +
+ +
); + } +} + +export default ModelView; diff --git a/app/static/app/js/css/ModelView.scss b/app/static/app/js/css/ModelView.scss index 8e11dab6..ba41cf16 100644 --- a/app/static/app/js/css/ModelView.scss +++ b/app/static/app/js/css/ModelView.scss @@ -61,4 +61,712 @@ } } } + + /* Potree specific */ + #potree_map{ + position: absolute; + left: 50px; + top: 50px; + width: 400px; + height: 400px; + display: none + } + + #potree_map_header{ + position: absolute; + width: 100%; + height: 25px; + top: 0px; + background-color: rgba(0,0,0,0.5); + z-index: 1000; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + } + + #potree_map_content{ + position: absolute; + z-index: 100; + top: 25px; + width: 100%; + height: calc(100% - 25px); + border: 2px solid rgba(0,0,0,0.5); + box-sizing: border-box; + } + + /* CSS - Cascading Style Sheet */ + /* Palette color codes */ + /* Palette URL: http://paletton.com/#uid=13p0u0kex8W2uqu8af7lEqaulDE */ + + /* Feel free to copy&paste color codes to your application */ + + /* As hex codes */ + .color-primary-0 { color: #19282C } /* Main Primary color */ + .color-primary-1 { color: #7A8184 } + .color-primary-2 { color: #39474B } + .color-primary-3 { color: #2D6D82 } + .color-primary-4 { color: #108FB9 } + + /* As RGBa codes */ + .rgba-primary-0 { color: rgba( 25, 40, 44,1) } /* Main Primary color */ + .rgba-primary-1 { color: rgba(122,129,132,1) } + .rgba-primary-2 { color: rgba( 57, 71, 75,1) } + .rgba-primary-3 { color: rgba( 45,109,130,1) } + .rgba-primary-4 { color: rgba( 16,143,185,1) } + + /* Generated by Paletton.com © 2002-2014 */ + /* http://paletton.com */ + + + + :root{ + + --color-0: rgba( 25, 40, 44, 1); + --color-1: rgba(122,129,132, 1); + --color-2: rgba( 57, 71, 75, 1); + --color-3: rgba( 45,109,130, 1); + --color-4: rgba( 16,143,185, 1); + + --bg-color: var(--color-0); + --bg-light-color: rgba( 48, 61, 65, 1); + --bg-dark-color: rgba( 24, 31, 33, 1); + --bg-hover-color: var(--color-2); + + --font-color: #9AA1A4; + --border-color: black; + + --measurement-detail-node-bg-light: var(--color-1); + --measurement-detail-node-bg-dark: var(--color-2); + --measurement-detail-area-bg-color: #eee; + + } + + + .potree_container{ + /*font-size: 75%;*/ + } + + .potree_info_text{ + color: white; + font-weight: bold; + text-shadow: 1px 1px 1px black, + 1px -1px 1px black, + -1px 1px 1px black, + -1px -1px 1px black; + } + + #potree_description{ + position: absolute; + top: 10px; + left: 50%; + transform: translateX(-50%); + text-align: center + } + + #potree_sidebar_container{ + position: absolute; + z-index: 0; + width: 300px; + height: 100%; + overflow-y: auto; + font-size: 85%; + overflow: hidden; + border-right: 1px solid black; + } + + .potree_sidebar_brand{ + margin: 1px 20px; + line-height: 2em; + font-size: 100%; + font-weight: bold; + position: relative; + } + + #potree_sidebar_container a{ + color: #8Aa1c4; + //color: red; + } + + .potree_menu_toggle{ + position: absolute; + float: left; + margin: 8px 8px; + background: none; + width: 2.5em; + height: 2.5em; + z-index: 100; + cursor: pointer; + } + + #potree_map_toggle{ + position: absolute; + float: left; + margin: 8px 8px; + background: none; + width: 2.5em; + height: 2.5em; + z-index: 100; + top: calc(2.5em + 8px); + cursor: pointer; + } + + #potree_render_area{ + position: absolute; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; + overflow: hidden; + z-index: 1; + -webkit-transition: left .35s; + transition: left .35s; + } + + .potree-panel { + border: 1px solid black; + border-radius: 0.4em; + padding: 0px; + background-color: var(--bg-light-color); + } + + .potree-panel-heading{ + background-color: var(--bg-dark-color); + } + + a:hover, a:visited, a:link, a:active{ + color: #ccccff; + text-decoration: none; + } + + .annotation{ + -webkit-transition: opacity .1s; + transition: opacity .1s; + position: absolute; + padding: 10px; + opacity: 0.5; + } + + .annotation-titlebar{ + color: white; + background-color: black; + border-radius: 1.5em; + font-size: 1em; + opacity: 1; + margin: auto; + width: fit-content; + } + + .annotation-expand{ + color: white; + font-size: 0.6em; + opacity: 1; + width: fit-content; + } + + .annotation-action-icon{ + width: 24px; + height: 24px; + filter: invert(1); + display: inline-block; + vertical-align: middle; + line-height: 1.5em; + text-align: center; + font-family: Arial; + font-weight: bold; + padding: 1px 8px 0px 1px; + cursor: default; + } + + .annotation-item { + color: white; + background-color: black; + opacity: 0.5; + border-radius: 1.5em; + font-size: 1em; + line-height: 1.5em; + padding: 1px 8px 0px 8px; + font-weight: bold; + display: flex; + cursor: default; + } + + .annotation-item:hover { + opacity: 1.0; + box-shadow: 0 0 5px #ffffff; + } + + .annotation-main{ + display: flex; + flex-grow: 1; + } + + .annotation-label{ + //position: relative; + display: inline-block; + height: 100%; + flex-grow: 1; + //margin: 2px 8px; + user-select: none; + z-index: 100; + width: fit-content; + vertical-align: middle; + line-height: 1.5em; + //text-align: center; + font-family: Arial; + font-weight: bold; + padding: 1px 8px 0px 8px; + cursor: default; + user-select: none; + white-space: nowrap; + } + + .annotation-description{ + position: relative; + color: white; + background-color: black; + padding: 10px; + margin: 5px 0px 0px 0px; + border-radius: 4px; + display: none; + max-width: 500px; + } + + .annotation-description-close{ + filter: invert(100%); + float: right; + opacity: 0.5; + margin: 0px 0px 8px 8px; + } + + + .annotation-description-content{ + color: white; + } + + .annotation-icon{ + width: 20px; + height: 20px; + filter: invert(100%); + margin: 2px 2px; + opacity: 0.5; + } + + + canvas { + width: 100%; + height: 100% + } + + body{ + margin: 0; + padding: 0; + } + + .axis { + font: 10px sans-serif; + color: var(--font-color); + } + + .axis path{ + fill: none; + stroke: var(--font-color); + shape-rendering: crispEdges; + opacity: 0.7; + } + + .axis line { + fill: none; + stroke: var(--font-color); + shape-rendering: crispEdges; + opacity: 0.1; + } + + + .measurement-panel-icon{ + width: 16px; + height: 16px; + } + + .measurement-panel-title{ + flex-grow: 1; + text-align: center; + } + + .measurement-panel-remove{ + width: 16px; + height: 16px; + } + + .measurement-detail-node-marker{ + border: 1px solid var(--border-color); + border-radius: 6px; + margin: auto; + text-align: center; + background-color: var(--measurement-detail-node-bg-light); + color: var(--border-color); + } + + .measurement-detail-node-area{ + border: 1px solid var(--border-color); + border-radius: 6px; + margin: auto; + margin-top: 15px; + text-align: center; + background-color: var(--measurement-detail-node-bg-light); + color: var(--border-color); + } + + .measurement-detail-node-angle{ + border: 1px solid var(--border-color); + width: 50%; + margin: auto; + border-radius: 6px; + text-align: center; + background-color: var(--measurement-detail-node-bg-); + color: var(--border-color); + } + + .measurement-detail-node-distance{ + border: 1px solid var(--border-color); + width: 50%; + margin: auto; + border-radius: 6px; + text-align: center; + background-color: var(--measurement-detail-node-bg-dark); + color: var(--border-color); + } + + .measurement-detail-edge{ + border: 1px solid var(--border-color); + width: 0px; + height: 2px; + margin: auto; + } + + #measurement_details .panel-body{ + padding: 5px 3px; + } + + .measurement-detail-button{ + width: 100%; + margin-top: 8px; + } + + .pv-panel-heading{ + padding: 4px !important; + display: flex; + flex-direction: row + } + + .pv-menu-list{ + list-style-type: none; + padding: 0; + } + + .pv-menu-list > *{ + margin: 8px 20px; + } + + + .pv-menu-list > li > .ui-slider{ + background-color: var(--color-1) !important; + background: none; + border: 1px solid black; + } + + .pv-menu-list > div > li > .ui-slider{ + background-color: var(--color-1) !important; + background: none; + border: 1px solid black; + } + + .pv-menu-list > li > label{ + width: 100%; + } + + .pv-menu-list select{ + width: 100%; + } + + .pv-menu-list > li > span{ + width: 100% !important; + } + + .pv-menu-list .ui-selectmenu-button span.ui-selectmenu-text{ + line-height: 0.8em; + } + + .ui-selectmenu-text{ + background-color: white; + } + + .panel-body > li > .ui-slider{ + background-color: var(--color-1) !important; + background: none; + border: 1px solid black; + } + + .panel-body > div > li > .ui-slider{ + background-color: var(--color-1) !important; + background: none; + border: 1px solid black; + } + + .pv-select-label{ + margin: 1px; + font-size: 90%; + font-weight: 100; + } + + .button-icon:hover{ + background-color: #09181C; + } + + .ui-widget-content{ + background-color: none !important; + color: var(--font-color) !important; + } + + .navmenu{ + background-color: var(--color-2) !important; + border-color: var(--color-2) !important; + } + + .accordion{ + background-color: var(--bg-color); + color: var(--color-1); + } + + .accordion > h3{ + background-color: var(--color-1) !important; + background: #f6f6f6 50% 50% repeat-x; + border: none; + color: var(--color-2); + padding: 4px 10px 4px 30px; + cursor: pointer; + } + + .accordion > div{ + background: none !important; + } + + .accordion-header{ + margin: 2px 0 0 0; + } + + .accordion-content{ + padding: 0px 0px !important; + border: none !important; + } + + #measurement_details .pv-menu-list > li{ + margin: 20px 10px; + } + + .pv-menu-list > .pv-divider{ + border-top: 1px solid black; + opacity: 0.2; + margin: 8px 0px; + } + + .pv-menu-list-header{ + opacity: 0.8; + } + + .pv-menu-item{ + width: 100%; + } + + .icon-bar{ + height: 4px !important; + border: 1px solid black; + background-color: white; + border-radius: 2px; + } + + .canvas{ + -webkit-transition: top .35s, left .35s, bottom .35s, right .35s, width .35s; + transition: top .35s, left .35s, bottom .35s, right .35s, width .35s; + } + + .profile-container-button{ + cursor: pointer; + } + + + + /* no scrollbar styling in firefox, sadly */ + .navmenu::-webkit-scrollbar{ + height: 12px; + } + + .navmenu::-webkit-scrollbar-track{ + background: rgba(0, 0, 0, 0.1); + } + + .navmenu::-webkit-scrollbar-thumb{ + background: rgba(0, 0, 0, 0.5); + } + + .pv-titlebar{ + background-color: var(--color-1); + color: #var(--color-2); + font-weight: 700; + padding: 5px; + } + + .pv-main-color{ + background: var(--bg-color); + } + + .profile-button:hover{ + background-color: #0000CC; + } + + .unselectable{ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + .selectable{ + -webkit-touch-callout: text; + -webkit-user-select: text; + -khtml-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + } + + + + .divider { + display: block; + text-align: center; + overflow: hidden; + white-space: nowrap; + font-weight: bold; + font-size: 90%; + letter-spacing: 1px; + margin-left: -20px; + margin-right: -20px; + } + + .divider > span { + position: relative; + display: inline-block; + } + + .divider > span:before, + .divider > span:after { + content: ""; + position: absolute; + top: 50%; + width: 9999px; + height: 1px; + background: #b2b2b2; + } + + .divider > span:before { + right: 100%; + margin-right: 5px; + } + + .divider > span:after { + left: 100%; + margin-left: 5px; + } + + + + + + + + + + + + .ol-dragbox { + background-color: rgba(255,255,255,0.4); + border-color: rgba(100,150,0,1); + border: 1px solid red; + } + + + + + + + /* HOVER MENU */ + + .hover_menu{ + position: absolute; + width: 28px; + height: 28px; + border: 0px; + border-radius: 100px; + display: block; + user-select: none; + } + + .hover_menu_icon{ + position: absolute; + //border: 1px solid black; + display: inline-block; + width: 16px; + height: 16px; + padding: 6px; + opacity: 0.5; + background-color: white; + filter:invert(100%); + border-radius: 100px; + } + .hover_menu_icon:hover{ + opacity: 1.0; + box-shadow: 0 0 5px #ffffff; + } + + .hover_menu_icon > img{ + width: 16px; + height: 16px; + } + + .hover_menu_item{ + background: black; + border-radius: 100px; + display: inline-block; + position: absolute; + width:32px; + height: 32px; + opacity: 0.5; + } + + .hover_menu_item:hover{ + opacity: 1.0; + box-shadow: 0 0 5px #ffffff; + } + + .hover_menu_item > img{ + width: 32px; + height: 32px; + filter: invert(100%); + } + + + .measurepanel_downloads { + position: relative; + bottom: -5px; + opacity: 0.5; + } + + .measurepanel_downloads:hover{ + position: relative; + bottom: -5px; + opacity: 1.0; + } + + } diff --git a/app/static/app/js/vendor/potree/ProgressBar.js b/app/static/app/js/vendor/potree/ProgressBar.js deleted file mode 100644 index b3f024a4..00000000 --- a/app/static/app/js/vendor/potree/ProgressBar.js +++ /dev/null @@ -1,85 +0,0 @@ - - -function ProgressBar(container){ - this._progress = 0; - this._message = ""; - - this.maxOpacity = 0.6; - - this.element = document.createElement("div"); - this.elProgress = document.createElement("div"); - this.elProgressMessage = document.createElement("div"); - - //this.element.innerHTML = "element"; - //this.elProgress.innerHTML = "progress"; - - this.element.innerHTML = ""; - this.element.style.position = "absolute"; - this.element.style.bottom = "40px"; - this.element.style.width = "200px"; - this.element.style.marginLeft = "-100px"; - this.element.style.left = "50%"; - this.element.style.borderRadius = "5px"; - this.element.style.border = "1px solid #727678"; - this.element.style.height = "16px"; - this.element.style.padding = "1px"; - this.element.style.textAlign = "center"; - this.element.style.backgroundColor = "#6ba8e5"; - this.element.style.opacity = this.maxOpacity; - this.element.style.pointerEvents = "none"; - - this.elProgress.innerHTML = " "; - this.elProgress.style.backgroundColor = "#b8e1fc"; - this.elProgress.style.position = "absolute"; - this.elProgress.style.borderRadius = "5px"; - this.elProgress.style.width = "0%"; - this.elProgress.style.height = "100%"; - this.elProgress.style.margin = "0px"; - this.elProgress.style.padding = "0px"; - - this.elProgressMessage.style.position = "absolute"; - this.elProgressMessage.style.width = "100%"; - this.elProgressMessage.style.fontSize = "10px"; - - this.elProgressMessage.innerHTML = "loading 1 / 10"; - - - - container.appendChild(this.element); - this.element.appendChild(this.elProgress); - this.element.appendChild(this.elProgressMessage); - - this.hide(); -}; - -ProgressBar.prototype.hide = function(){ - this.element.style.opacity = 0; - this.element.style.transition = "all 0.2s ease"; -}; - -ProgressBar.prototype.show = function(){ - this.element.style.opacity = this.maxOpacity; - this.element.style.transition = "all 0.2s ease"; -}; - -Object.defineProperty(ProgressBar.prototype, "progress", { - get: function(){ - return this._progress; - }, - set: function(value){ - this._progress = value; - this.elProgress.style.width = (value * 100) + "%"; - } -}); - -Object.defineProperty(ProgressBar.prototype, "message", { - get: function(){ - return this._message; - }, - set: function(message){ - this._message = message; - this.elProgressMessage.innerHTML = message; - } -}); - -export default ProgressBar; \ No newline at end of file diff --git a/app/static/app/js/vendor/Stats.js b/app/static/app/js/vendor/potree/js/Stats.js similarity index 100% rename from app/static/app/js/vendor/Stats.js rename to app/static/app/js/vendor/potree/js/Stats.js diff --git a/app/static/app/js/vendor/potree/js/jquery-ui.css b/app/static/app/js/vendor/potree/js/jquery-ui.css new file mode 100644 index 00000000..b845aabb --- /dev/null +++ b/app/static/app/js/vendor/potree/js/jquery-ui.css @@ -0,0 +1,396 @@ +/*! jQuery UI - v1.12.1 - 2017-03-20 +* http://jqueryui.com +* Includes: draggable.css, core.css, resizable.css, menu.css, selectmenu.css, button.css, slider.css +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +.ui-draggable-handle { + -ms-touch-action: none; + touch-action: none; +} +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { + display: none; +} +.ui-helper-hidden-accessible { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} +.ui-helper-reset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + line-height: 1.3; + text-decoration: none; + font-size: 100%; + list-style: none; +} +.ui-helper-clearfix:before, +.ui-helper-clearfix:after { + content: ""; + display: table; + border-collapse: collapse; +} +.ui-helper-clearfix:after { + clear: both; +} +.ui-helper-zfix { + width: 100%; + height: 100%; + top: 0; + left: 0; + position: absolute; + opacity: 0; + filter:Alpha(Opacity=0); /* support: IE8 */ +} + +.ui-front { + z-index: 100; +} + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { + cursor: default !important; + pointer-events: none; +} + + +/* Icons +----------------------------------*/ +.ui-icon { + display: inline-block; + vertical-align: middle; + margin-top: -.25em; + position: relative; + text-indent: -99999px; + overflow: hidden; + background-repeat: no-repeat; +} + +.ui-widget-icon-block { + left: 50%; + margin-left: -8px; + display: block; +} + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.ui-resizable { + position: relative; +} +.ui-resizable-handle { + position: absolute; + font-size: 0.1px; + display: block; + -ms-touch-action: none; + touch-action: none; +} +.ui-resizable-disabled .ui-resizable-handle, +.ui-resizable-autohide .ui-resizable-handle { + display: none; +} +.ui-resizable-n { + cursor: n-resize; + height: 7px; + width: 100%; + top: -5px; + left: 0; +} +.ui-resizable-s { + cursor: s-resize; + height: 7px; + width: 100%; + bottom: -5px; + left: 0; +} +.ui-resizable-e { + cursor: e-resize; + width: 7px; + right: -5px; + top: 0; + height: 100%; +} +.ui-resizable-w { + cursor: w-resize; + width: 7px; + left: -5px; + top: 0; + height: 100%; +} +.ui-resizable-se { + cursor: se-resize; + width: 12px; + height: 12px; + right: 1px; + bottom: 1px; +} +.ui-resizable-sw { + cursor: sw-resize; + width: 9px; + height: 9px; + left: -5px; + bottom: -5px; +} +.ui-resizable-nw { + cursor: nw-resize; + width: 9px; + height: 9px; + left: -5px; + top: -5px; +} +.ui-resizable-ne { + cursor: ne-resize; + width: 9px; + height: 9px; + right: -5px; + top: -5px; +} +.ui-menu { + list-style: none; + padding: 0; + margin: 0; + display: block; + outline: 0; +} +.ui-menu .ui-menu { + position: absolute; +} +.ui-menu .ui-menu-item { + margin: 0; + cursor: pointer; + /* support: IE10, see #8844 */ + list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"); +} +.ui-menu .ui-menu-item-wrapper { + position: relative; + padding: 3px 1em 3px .4em; +} +.ui-menu .ui-menu-divider { + margin: 5px 0; + height: 0; + font-size: 0; + line-height: 0; + border-width: 1px 0 0 0; +} +.ui-menu .ui-state-focus, +.ui-menu .ui-state-active { + margin: -1px; +} + +/* icon support */ +.ui-menu-icons { + position: relative; +} +.ui-menu-icons .ui-menu-item-wrapper { + padding-left: 2em; +} + +/* left-aligned */ +.ui-menu .ui-icon { + position: absolute; + top: 0; + bottom: 0; + left: .2em; + margin: auto 0; +} + +/* right-aligned */ +.ui-menu .ui-menu-icon { + left: auto; + right: 0; +} +.ui-selectmenu-menu { + padding: 0; + margin: 0; + position: absolute; + top: 0; + left: 0; + display: none; +} +.ui-selectmenu-menu .ui-menu { + overflow: auto; + overflow-x: hidden; + padding-bottom: 1px; +} +.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup { + font-size: 1em; + font-weight: bold; + line-height: 1.5; + padding: 2px 0.4em; + margin: 0.5em 0 0 0; + height: auto; + border: 0; +} +.ui-selectmenu-open { + display: block; +} +.ui-selectmenu-text { + display: block; + margin-right: 20px; + overflow: hidden; + text-overflow: ellipsis; +} +.ui-selectmenu-button.ui-button { + text-align: left; + white-space: nowrap; + width: 14em; +} +.ui-selectmenu-icon.ui-icon { + float: right; + margin-top: 0; +} +.ui-button { + padding: .4em 1em; + display: inline-block; + position: relative; + line-height: normal; + margin-right: .1em; + cursor: pointer; + vertical-align: middle; + text-align: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + /* Support: IE <= 11 */ + overflow: visible; +} + +.ui-button, +.ui-button:link, +.ui-button:visited, +.ui-button:hover, +.ui-button:active { + text-decoration: none; +} + +/* to make room for the icon, a width needs to be set here */ +.ui-button-icon-only { + width: 2em; + box-sizing: border-box; + text-indent: -9999px; + white-space: nowrap; +} + +/* no icon support for input elements */ +input.ui-button.ui-button-icon-only { + text-indent: 0; +} + +/* button icon element(s) */ +.ui-button-icon-only .ui-icon { + position: absolute; + top: 50%; + left: 50%; + margin-top: -8px; + margin-left: -8px; +} + +.ui-button.ui-icon-notext .ui-icon { + padding: 0; + width: 2.1em; + height: 2.1em; + text-indent: -9999px; + white-space: nowrap; + +} + +input.ui-button.ui-icon-notext .ui-icon { + width: auto; + height: auto; + text-indent: 0; + white-space: normal; + padding: .4em 1em; +} + +/* workarounds */ +/* Support: Firefox 5 - 40 */ +input.ui-button::-moz-focus-inner, +button.ui-button::-moz-focus-inner { + border: 0; + padding: 0; +} +.ui-slider { + position: relative; + text-align: left; +} +.ui-slider .ui-slider-handle { + position: absolute; + z-index: 2; + width: 1.2em; + height: 1.2em; + cursor: default; + -ms-touch-action: none; + touch-action: none; +} +.ui-slider .ui-slider-range { + position: absolute; + z-index: 1; + font-size: .7em; + display: block; + border: 0; + background-position: 0 0; +} + +/* support: IE8 - See #6727 */ +.ui-slider.ui-state-disabled .ui-slider-handle, +.ui-slider.ui-state-disabled .ui-slider-range { + filter: inherit; +} + +.ui-slider-horizontal { + height: .8em; +} +.ui-slider-horizontal .ui-slider-handle { + top: -.3em; + margin-left: -.6em; +} +.ui-slider-horizontal .ui-slider-range { + top: 0; + height: 100%; +} +.ui-slider-horizontal .ui-slider-range-min { + left: 0; +} +.ui-slider-horizontal .ui-slider-range-max { + right: 0; +} + +.ui-slider-vertical { + width: .8em; + height: 100px; +} +.ui-slider-vertical .ui-slider-handle { + left: -.3em; + margin-left: 0; + margin-bottom: -.6em; +} +.ui-slider-vertical .ui-slider-range { + left: 0; + width: 100%; +} +.ui-slider-vertical .ui-slider-range-min { + bottom: 0; +} +.ui-slider-vertical .ui-slider-range-max { + top: 0; +} diff --git a/app/static/app/js/vendor/potree/js/jquery-ui.js b/app/static/app/js/vendor/potree/js/jquery-ui.js new file mode 100644 index 00000000..f88c81b9 --- /dev/null +++ b/app/static/app/js/vendor/potree/js/jquery-ui.js @@ -0,0 +1,6308 @@ +/*! jQuery UI - v1.12.1 - 2017-03-20 +* http://jqueryui.com +* Includes: widget.js, position.js, data.js, disable-selection.js, form-reset-mixin.js, keycode.js, labels.js, scroll-parent.js, unique-id.js, widgets/draggable.js, widgets/resizable.js, widgets/menu.js, widgets/mouse.js, widgets/selectmenu.js, widgets/slider.js +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +(function( factory ) { + if ( typeof define === "function" && define.amd ) { + + // AMD. Register as an anonymous module. + define([ "jquery" ], factory ); + } else { + + // Browser globals + factory( jQuery ); + } +}(function( $ ) { + +$.ui = $.ui || {}; + +var version = $.ui.version = "1.12.1"; + + +/*! + * jQuery UI Widget 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: Widget +//>>group: Core +//>>description: Provides a factory for creating stateful widgets with a common API. +//>>docs: http://api.jqueryui.com/jQuery.widget/ +//>>demos: http://jqueryui.com/widget/ + + + +var widgetUuid = 0; +var widgetSlice = Array.prototype.slice; + +$.cleanData = ( function( orig ) { + return function( elems ) { + var events, elem, i; + for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) { + try { + + // Only trigger remove when necessary to save time + events = $._data( elem, "events" ); + if ( events && events.remove ) { + $( elem ).triggerHandler( "remove" ); + } + + // Http://bugs.jquery.com/ticket/8235 + } catch ( e ) {} + } + orig( elems ); + }; +} )( $.cleanData ); + +$.widget = function( name, base, prototype ) { + var existingConstructor, constructor, basePrototype; + + // ProxiedPrototype allows the provided prototype to remain unmodified + // so that it can be used as a mixin for multiple widgets (#8876) + var proxiedPrototype = {}; + + var namespace = name.split( "." )[ 0 ]; + name = name.split( "." )[ 1 ]; + var fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + if ( $.isArray( prototype ) ) { + prototype = $.extend.apply( null, [ {} ].concat( prototype ) ); + } + + // Create selector for plugin + $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { + return !!$.data( elem, fullName ); + }; + + $[ namespace ] = $[ namespace ] || {}; + existingConstructor = $[ namespace ][ name ]; + constructor = $[ namespace ][ name ] = function( options, element ) { + + // Allow instantiation without "new" keyword + if ( !this._createWidget ) { + return new constructor( options, element ); + } + + // Allow instantiation without initializing for simple inheritance + // must use "new" keyword (the code above always passes args) + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + + // Extend with the existing constructor to carry over any static properties + $.extend( constructor, existingConstructor, { + version: prototype.version, + + // Copy the object used to create the prototype in case we need to + // redefine the widget later + _proto: $.extend( {}, prototype ), + + // Track widgets that inherit from this widget in case this widget is + // redefined after a widget inherits from it + _childConstructors: [] + } ); + + basePrototype = new base(); + + // We need to make the options hash a property directly on the new instance + // otherwise we'll modify the options hash on the prototype that we're + // inheriting from + basePrototype.options = $.widget.extend( {}, basePrototype.options ); + $.each( prototype, function( prop, value ) { + if ( !$.isFunction( value ) ) { + proxiedPrototype[ prop ] = value; + return; + } + proxiedPrototype[ prop ] = ( function() { + function _super() { + return base.prototype[ prop ].apply( this, arguments ); + } + + function _superApply( args ) { + return base.prototype[ prop ].apply( this, args ); + } + + return function() { + var __super = this._super; + var __superApply = this._superApply; + var returnValue; + + this._super = _super; + this._superApply = _superApply; + + returnValue = value.apply( this, arguments ); + + this._super = __super; + this._superApply = __superApply; + + return returnValue; + }; + } )(); + } ); + constructor.prototype = $.widget.extend( basePrototype, { + + // TODO: remove support for widgetEventPrefix + // always use the name + a colon as the prefix, e.g., draggable:start + // don't prefix for widgets that aren't DOM-based + widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name + }, proxiedPrototype, { + constructor: constructor, + namespace: namespace, + widgetName: name, + widgetFullName: fullName + } ); + + // If this widget is being redefined then we need to find all widgets that + // are inheriting from it and redefine all of them so that they inherit from + // the new version of this widget. We're essentially trying to replace one + // level in the prototype chain. + if ( existingConstructor ) { + $.each( existingConstructor._childConstructors, function( i, child ) { + var childPrototype = child.prototype; + + // Redefine the child widget using the same prototype that was + // originally used, but inherit from the new version of the base + $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, + child._proto ); + } ); + + // Remove the list of existing child constructors from the old constructor + // so the old child constructors can be garbage collected + delete existingConstructor._childConstructors; + } else { + base._childConstructors.push( constructor ); + } + + $.widget.bridge( name, constructor ); + + return constructor; +}; + +$.widget.extend = function( target ) { + var input = widgetSlice.call( arguments, 1 ); + var inputIndex = 0; + var inputLength = input.length; + var key; + var value; + + for ( ; inputIndex < inputLength; inputIndex++ ) { + for ( key in input[ inputIndex ] ) { + value = input[ inputIndex ][ key ]; + if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { + + // Clone objects + if ( $.isPlainObject( value ) ) { + target[ key ] = $.isPlainObject( target[ key ] ) ? + $.widget.extend( {}, target[ key ], value ) : + + // Don't extend strings, arrays, etc. with objects + $.widget.extend( {}, value ); + + // Copy everything else by reference + } else { + target[ key ] = value; + } + } + } + } + return target; +}; + +$.widget.bridge = function( name, object ) { + var fullName = object.prototype.widgetFullName || name; + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string"; + var args = widgetSlice.call( arguments, 1 ); + var returnValue = this; + + if ( isMethodCall ) { + + // If this is an empty collection, we need to have the instance method + // return undefined instead of the jQuery instance + if ( !this.length && options === "instance" ) { + returnValue = undefined; + } else { + this.each( function() { + var methodValue; + var instance = $.data( this, fullName ); + + if ( options === "instance" ) { + returnValue = instance; + return false; + } + + if ( !instance ) { + return $.error( "cannot call methods on " + name + + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + + if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for " + name + + " widget instance" ); + } + + methodValue = instance[ options ].apply( instance, args ); + + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; + return false; + } + } ); + } + } else { + + // Allow multiple hashes to be passed on init + if ( args.length ) { + options = $.widget.extend.apply( null, [ options ].concat( args ) ); + } + + this.each( function() { + var instance = $.data( this, fullName ); + if ( instance ) { + instance.option( options || {} ); + if ( instance._init ) { + instance._init(); + } + } else { + $.data( this, fullName, new object( options, this ) ); + } + } ); + } + + return returnValue; + }; +}; + +$.Widget = function( /* options, element */ ) {}; +$.Widget._childConstructors = []; + +$.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + defaultElement: "
", + + options: { + classes: {}, + disabled: false, + + // Callbacks + create: null + }, + + _createWidget: function( options, element ) { + element = $( element || this.defaultElement || this )[ 0 ]; + this.element = $( element ); + this.uuid = widgetUuid++; + this.eventNamespace = "." + this.widgetName + this.uuid; + + this.bindings = $(); + this.hoverable = $(); + this.focusable = $(); + this.classesElementLookup = {}; + + if ( element !== this ) { + $.data( element, this.widgetFullName, this ); + this._on( true, this.element, { + remove: function( event ) { + if ( event.target === element ) { + this.destroy(); + } + } + } ); + this.document = $( element.style ? + + // Element within the document + element.ownerDocument : + + // Element is window or document + element.document || element ); + this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow ); + } + + this.options = $.widget.extend( {}, + this.options, + this._getCreateOptions(), + options ); + + this._create(); + + if ( this.options.disabled ) { + this._setOptionDisabled( this.options.disabled ); + } + + this._trigger( "create", null, this._getCreateEventData() ); + this._init(); + }, + + _getCreateOptions: function() { + return {}; + }, + + _getCreateEventData: $.noop, + + _create: $.noop, + + _init: $.noop, + + destroy: function() { + var that = this; + + this._destroy(); + $.each( this.classesElementLookup, function( key, value ) { + that._removeClass( value, key ); + } ); + + // We can probably remove the unbind calls in 2.0 + // all event bindings should go through this._on() + this.element + .off( this.eventNamespace ) + .removeData( this.widgetFullName ); + this.widget() + .off( this.eventNamespace ) + .removeAttr( "aria-disabled" ); + + // Clean up events and states + this.bindings.off( this.eventNamespace ); + }, + + _destroy: $.noop, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key; + var parts; + var curOption; + var i; + + if ( arguments.length === 0 ) { + + // Don't return a reference to the internal hash + return $.widget.extend( {}, this.options ); + } + + if ( typeof key === "string" ) { + + // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } + options = {}; + parts = key.split( "." ); + key = parts.shift(); + if ( parts.length ) { + curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); + for ( i = 0; i < parts.length - 1; i++ ) { + curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; + curOption = curOption[ parts[ i ] ]; + } + key = parts.pop(); + if ( arguments.length === 1 ) { + return curOption[ key ] === undefined ? null : curOption[ key ]; + } + curOption[ key ] = value; + } else { + if ( arguments.length === 1 ) { + return this.options[ key ] === undefined ? null : this.options[ key ]; + } + options[ key ] = value; + } + } + + this._setOptions( options ); + + return this; + }, + + _setOptions: function( options ) { + var key; + + for ( key in options ) { + this._setOption( key, options[ key ] ); + } + + return this; + }, + + _setOption: function( key, value ) { + if ( key === "classes" ) { + this._setOptionClasses( value ); + } + + this.options[ key ] = value; + + if ( key === "disabled" ) { + this._setOptionDisabled( value ); + } + + return this; + }, + + _setOptionClasses: function( value ) { + var classKey, elements, currentElements; + + for ( classKey in value ) { + currentElements = this.classesElementLookup[ classKey ]; + if ( value[ classKey ] === this.options.classes[ classKey ] || + !currentElements || + !currentElements.length ) { + continue; + } + + // We are doing this to create a new jQuery object because the _removeClass() call + // on the next line is going to destroy the reference to the current elements being + // tracked. We need to save a copy of this collection so that we can add the new classes + // below. + elements = $( currentElements.get() ); + this._removeClass( currentElements, classKey ); + + // We don't use _addClass() here, because that uses this.options.classes + // for generating the string of classes. We want to use the value passed in from + // _setOption(), this is the new value of the classes option which was passed to + // _setOption(). We pass this value directly to _classes(). + elements.addClass( this._classes( { + element: elements, + keys: classKey, + classes: value, + add: true + } ) ); + } + }, + + _setOptionDisabled: function( value ) { + this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value ); + + // If the widget is becoming disabled, then nothing is interactive + if ( value ) { + this._removeClass( this.hoverable, null, "ui-state-hover" ); + this._removeClass( this.focusable, null, "ui-state-focus" ); + } + }, + + enable: function() { + return this._setOptions( { disabled: false } ); + }, + + disable: function() { + return this._setOptions( { disabled: true } ); + }, + + _classes: function( options ) { + var full = []; + var that = this; + + options = $.extend( { + element: this.element, + classes: this.options.classes || {} + }, options ); + + function processClassString( classes, checkOption ) { + var current, i; + for ( i = 0; i < classes.length; i++ ) { + current = that.classesElementLookup[ classes[ i ] ] || $(); + if ( options.add ) { + current = $( $.unique( current.get().concat( options.element.get() ) ) ); + } else { + current = $( current.not( options.element ).get() ); + } + that.classesElementLookup[ classes[ i ] ] = current; + full.push( classes[ i ] ); + if ( checkOption && options.classes[ classes[ i ] ] ) { + full.push( options.classes[ classes[ i ] ] ); + } + } + } + + this._on( options.element, { + "remove": "_untrackClassesElement" + } ); + + if ( options.keys ) { + processClassString( options.keys.match( /\S+/g ) || [], true ); + } + if ( options.extra ) { + processClassString( options.extra.match( /\S+/g ) || [] ); + } + + return full.join( " " ); + }, + + _untrackClassesElement: function( event ) { + var that = this; + $.each( that.classesElementLookup, function( key, value ) { + if ( $.inArray( event.target, value ) !== -1 ) { + that.classesElementLookup[ key ] = $( value.not( event.target ).get() ); + } + } ); + }, + + _removeClass: function( element, keys, extra ) { + return this._toggleClass( element, keys, extra, false ); + }, + + _addClass: function( element, keys, extra ) { + return this._toggleClass( element, keys, extra, true ); + }, + + _toggleClass: function( element, keys, extra, add ) { + add = ( typeof add === "boolean" ) ? add : extra; + var shift = ( typeof element === "string" || element === null ), + options = { + extra: shift ? keys : extra, + keys: shift ? element : keys, + element: shift ? this.element : element, + add: add + }; + options.element.toggleClass( this._classes( options ), add ); + return this; + }, + + _on: function( suppressDisabledCheck, element, handlers ) { + var delegateElement; + var instance = this; + + // No suppressDisabledCheck flag, shuffle arguments + if ( typeof suppressDisabledCheck !== "boolean" ) { + handlers = element; + element = suppressDisabledCheck; + suppressDisabledCheck = false; + } + + // No element argument, shuffle and use this.element + if ( !handlers ) { + handlers = element; + element = this.element; + delegateElement = this.widget(); + } else { + element = delegateElement = $( element ); + this.bindings = this.bindings.add( element ); + } + + $.each( handlers, function( event, handler ) { + function handlerProxy() { + + // Allow widgets to customize the disabled handling + // - disabled as an array instead of boolean + // - disabled class as method for disabling individual parts + if ( !suppressDisabledCheck && + ( instance.options.disabled === true || + $( this ).hasClass( "ui-state-disabled" ) ) ) { + return; + } + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + + // Copy the guid so direct unbinding works + if ( typeof handler !== "string" ) { + handlerProxy.guid = handler.guid = + handler.guid || handlerProxy.guid || $.guid++; + } + + var match = event.match( /^([\w:-]*)\s*(.*)$/ ); + var eventName = match[ 1 ] + instance.eventNamespace; + var selector = match[ 2 ]; + + if ( selector ) { + delegateElement.on( eventName, selector, handlerProxy ); + } else { + element.on( eventName, handlerProxy ); + } + } ); + }, + + _off: function( element, eventName ) { + eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) + + this.eventNamespace; + element.off( eventName ).off( eventName ); + + // Clear the stack to avoid memory leaks (#10056) + this.bindings = $( this.bindings.not( element ).get() ); + this.focusable = $( this.focusable.not( element ).get() ); + this.hoverable = $( this.hoverable.not( element ).get() ); + }, + + _delay: function( handler, delay ) { + function handlerProxy() { + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + var instance = this; + return setTimeout( handlerProxy, delay || 0 ); + }, + + _hoverable: function( element ) { + this.hoverable = this.hoverable.add( element ); + this._on( element, { + mouseenter: function( event ) { + this._addClass( $( event.currentTarget ), null, "ui-state-hover" ); + }, + mouseleave: function( event ) { + this._removeClass( $( event.currentTarget ), null, "ui-state-hover" ); + } + } ); + }, + + _focusable: function( element ) { + this.focusable = this.focusable.add( element ); + this._on( element, { + focusin: function( event ) { + this._addClass( $( event.currentTarget ), null, "ui-state-focus" ); + }, + focusout: function( event ) { + this._removeClass( $( event.currentTarget ), null, "ui-state-focus" ); + } + } ); + }, + + _trigger: function( type, event, data ) { + var prop, orig; + var callback = this.options[ type ]; + + data = data || {}; + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + + // The original event may come from any element + // so we need to reset the target on the new event + event.target = this.element[ 0 ]; + + // Copy original event properties over to the new event + orig = event.originalEvent; + if ( orig ) { + for ( prop in orig ) { + if ( !( prop in event ) ) { + event[ prop ] = orig[ prop ]; + } + } + } + + this.element.trigger( event, data ); + return !( $.isFunction( callback ) && + callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false || + event.isDefaultPrevented() ); + } +}; + +$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { + $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { + if ( typeof options === "string" ) { + options = { effect: options }; + } + + var hasOptions; + var effectName = !options ? + method : + options === true || typeof options === "number" ? + defaultEffect : + options.effect || defaultEffect; + + options = options || {}; + if ( typeof options === "number" ) { + options = { duration: options }; + } + + hasOptions = !$.isEmptyObject( options ); + options.complete = callback; + + if ( options.delay ) { + element.delay( options.delay ); + } + + if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { + element[ method ]( options ); + } else if ( effectName !== method && element[ effectName ] ) { + element[ effectName ]( options.duration, options.easing, callback ); + } else { + element.queue( function( next ) { + $( this )[ method ](); + if ( callback ) { + callback.call( element[ 0 ] ); + } + next(); + } ); + } + }; +} ); + +var widget = $.widget; + + +/*! + * jQuery UI Position 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/position/ + */ + +//>>label: Position +//>>group: Core +//>>description: Positions elements relative to other elements. +//>>docs: http://api.jqueryui.com/position/ +//>>demos: http://jqueryui.com/position/ + + +( function() { +var cachedScrollbarWidth, + max = Math.max, + abs = Math.abs, + rhorizontal = /left|center|right/, + rvertical = /top|center|bottom/, + roffset = /[\+\-]\d+(\.[\d]+)?%?/, + rposition = /^\w+/, + rpercent = /%$/, + _position = $.fn.position; + +function getOffsets( offsets, width, height ) { + return [ + parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), + parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) + ]; +} + +function parseCss( element, property ) { + return parseInt( $.css( element, property ), 10 ) || 0; +} + +function getDimensions( elem ) { + var raw = elem[ 0 ]; + if ( raw.nodeType === 9 ) { + return { + width: elem.width(), + height: elem.height(), + offset: { top: 0, left: 0 } + }; + } + if ( $.isWindow( raw ) ) { + return { + width: elem.width(), + height: elem.height(), + offset: { top: elem.scrollTop(), left: elem.scrollLeft() } + }; + } + if ( raw.preventDefault ) { + return { + width: 0, + height: 0, + offset: { top: raw.pageY, left: raw.pageX } + }; + } + return { + width: elem.outerWidth(), + height: elem.outerHeight(), + offset: elem.offset() + }; +} + +$.position = { + scrollbarWidth: function() { + if ( cachedScrollbarWidth !== undefined ) { + return cachedScrollbarWidth; + } + var w1, w2, + div = $( "
" + + "
" ), + innerDiv = div.children()[ 0 ]; + + $( "body" ).append( div ); + w1 = innerDiv.offsetWidth; + div.css( "overflow", "scroll" ); + + w2 = innerDiv.offsetWidth; + + if ( w1 === w2 ) { + w2 = div[ 0 ].clientWidth; + } + + div.remove(); + + return ( cachedScrollbarWidth = w1 - w2 ); + }, + getScrollInfo: function( within ) { + var overflowX = within.isWindow || within.isDocument ? "" : + within.element.css( "overflow-x" ), + overflowY = within.isWindow || within.isDocument ? "" : + within.element.css( "overflow-y" ), + hasOverflowX = overflowX === "scroll" || + ( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ), + hasOverflowY = overflowY === "scroll" || + ( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight ); + return { + width: hasOverflowY ? $.position.scrollbarWidth() : 0, + height: hasOverflowX ? $.position.scrollbarWidth() : 0 + }; + }, + getWithinInfo: function( element ) { + var withinElement = $( element || window ), + isWindow = $.isWindow( withinElement[ 0 ] ), + isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9, + hasOffset = !isWindow && !isDocument; + return { + element: withinElement, + isWindow: isWindow, + isDocument: isDocument, + offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 }, + scrollLeft: withinElement.scrollLeft(), + scrollTop: withinElement.scrollTop(), + width: withinElement.outerWidth(), + height: withinElement.outerHeight() + }; + } +}; + +$.fn.position = function( options ) { + if ( !options || !options.of ) { + return _position.apply( this, arguments ); + } + + // Make a copy, we don't want to modify arguments + options = $.extend( {}, options ); + + var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, + target = $( options.of ), + within = $.position.getWithinInfo( options.within ), + scrollInfo = $.position.getScrollInfo( within ), + collision = ( options.collision || "flip" ).split( " " ), + offsets = {}; + + dimensions = getDimensions( target ); + if ( target[ 0 ].preventDefault ) { + + // Force left top to allow flipping + options.at = "left top"; + } + targetWidth = dimensions.width; + targetHeight = dimensions.height; + targetOffset = dimensions.offset; + + // Clone to reuse original targetOffset later + basePosition = $.extend( {}, targetOffset ); + + // Force my and at to have valid horizontal and vertical positions + // if a value is missing or invalid, it will be converted to center + $.each( [ "my", "at" ], function() { + var pos = ( options[ this ] || "" ).split( " " ), + horizontalOffset, + verticalOffset; + + if ( pos.length === 1 ) { + pos = rhorizontal.test( pos[ 0 ] ) ? + pos.concat( [ "center" ] ) : + rvertical.test( pos[ 0 ] ) ? + [ "center" ].concat( pos ) : + [ "center", "center" ]; + } + pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; + pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; + + // Calculate offsets + horizontalOffset = roffset.exec( pos[ 0 ] ); + verticalOffset = roffset.exec( pos[ 1 ] ); + offsets[ this ] = [ + horizontalOffset ? horizontalOffset[ 0 ] : 0, + verticalOffset ? verticalOffset[ 0 ] : 0 + ]; + + // Reduce to just the positions without the offsets + options[ this ] = [ + rposition.exec( pos[ 0 ] )[ 0 ], + rposition.exec( pos[ 1 ] )[ 0 ] + ]; + } ); + + // Normalize collision option + if ( collision.length === 1 ) { + collision[ 1 ] = collision[ 0 ]; + } + + if ( options.at[ 0 ] === "right" ) { + basePosition.left += targetWidth; + } else if ( options.at[ 0 ] === "center" ) { + basePosition.left += targetWidth / 2; + } + + if ( options.at[ 1 ] === "bottom" ) { + basePosition.top += targetHeight; + } else if ( options.at[ 1 ] === "center" ) { + basePosition.top += targetHeight / 2; + } + + atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); + basePosition.left += atOffset[ 0 ]; + basePosition.top += atOffset[ 1 ]; + + return this.each( function() { + var collisionPosition, using, + elem = $( this ), + elemWidth = elem.outerWidth(), + elemHeight = elem.outerHeight(), + marginLeft = parseCss( this, "marginLeft" ), + marginTop = parseCss( this, "marginTop" ), + collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + + scrollInfo.width, + collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + + scrollInfo.height, + position = $.extend( {}, basePosition ), + myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); + + if ( options.my[ 0 ] === "right" ) { + position.left -= elemWidth; + } else if ( options.my[ 0 ] === "center" ) { + position.left -= elemWidth / 2; + } + + if ( options.my[ 1 ] === "bottom" ) { + position.top -= elemHeight; + } else if ( options.my[ 1 ] === "center" ) { + position.top -= elemHeight / 2; + } + + position.left += myOffset[ 0 ]; + position.top += myOffset[ 1 ]; + + collisionPosition = { + marginLeft: marginLeft, + marginTop: marginTop + }; + + $.each( [ "left", "top" ], function( i, dir ) { + if ( $.ui.position[ collision[ i ] ] ) { + $.ui.position[ collision[ i ] ][ dir ]( position, { + targetWidth: targetWidth, + targetHeight: targetHeight, + elemWidth: elemWidth, + elemHeight: elemHeight, + collisionPosition: collisionPosition, + collisionWidth: collisionWidth, + collisionHeight: collisionHeight, + offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], + my: options.my, + at: options.at, + within: within, + elem: elem + } ); + } + } ); + + if ( options.using ) { + + // Adds feedback as second argument to using callback, if present + using = function( props ) { + var left = targetOffset.left - position.left, + right = left + targetWidth - elemWidth, + top = targetOffset.top - position.top, + bottom = top + targetHeight - elemHeight, + feedback = { + target: { + element: target, + left: targetOffset.left, + top: targetOffset.top, + width: targetWidth, + height: targetHeight + }, + element: { + element: elem, + left: position.left, + top: position.top, + width: elemWidth, + height: elemHeight + }, + horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", + vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" + }; + if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { + feedback.horizontal = "center"; + } + if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { + feedback.vertical = "middle"; + } + if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { + feedback.important = "horizontal"; + } else { + feedback.important = "vertical"; + } + options.using.call( this, props, feedback ); + }; + } + + elem.offset( $.extend( position, { using: using } ) ); + } ); +}; + +$.ui.position = { + fit: { + left: function( position, data ) { + var within = data.within, + withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, + outerWidth = within.width, + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = withinOffset - collisionPosLeft, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, + newOverRight; + + // Element is wider than within + if ( data.collisionWidth > outerWidth ) { + + // Element is initially over the left side of within + if ( overLeft > 0 && overRight <= 0 ) { + newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - + withinOffset; + position.left += overLeft - newOverRight; + + // Element is initially over right side of within + } else if ( overRight > 0 && overLeft <= 0 ) { + position.left = withinOffset; + + // Element is initially over both left and right sides of within + } else { + if ( overLeft > overRight ) { + position.left = withinOffset + outerWidth - data.collisionWidth; + } else { + position.left = withinOffset; + } + } + + // Too far left -> align with left edge + } else if ( overLeft > 0 ) { + position.left += overLeft; + + // Too far right -> align with right edge + } else if ( overRight > 0 ) { + position.left -= overRight; + + // Adjust based on position and margin + } else { + position.left = max( position.left - collisionPosLeft, position.left ); + } + }, + top: function( position, data ) { + var within = data.within, + withinOffset = within.isWindow ? within.scrollTop : within.offset.top, + outerHeight = data.within.height, + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = withinOffset - collisionPosTop, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, + newOverBottom; + + // Element is taller than within + if ( data.collisionHeight > outerHeight ) { + + // Element is initially over the top of within + if ( overTop > 0 && overBottom <= 0 ) { + newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - + withinOffset; + position.top += overTop - newOverBottom; + + // Element is initially over bottom of within + } else if ( overBottom > 0 && overTop <= 0 ) { + position.top = withinOffset; + + // Element is initially over both top and bottom of within + } else { + if ( overTop > overBottom ) { + position.top = withinOffset + outerHeight - data.collisionHeight; + } else { + position.top = withinOffset; + } + } + + // Too far up -> align with top + } else if ( overTop > 0 ) { + position.top += overTop; + + // Too far down -> align with bottom edge + } else if ( overBottom > 0 ) { + position.top -= overBottom; + + // Adjust based on position and margin + } else { + position.top = max( position.top - collisionPosTop, position.top ); + } + } + }, + flip: { + left: function( position, data ) { + var within = data.within, + withinOffset = within.offset.left + within.scrollLeft, + outerWidth = within.width, + offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, + collisionPosLeft = position.left - data.collisionPosition.marginLeft, + overLeft = collisionPosLeft - offsetLeft, + overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, + myOffset = data.my[ 0 ] === "left" ? + -data.elemWidth : + data.my[ 0 ] === "right" ? + data.elemWidth : + 0, + atOffset = data.at[ 0 ] === "left" ? + data.targetWidth : + data.at[ 0 ] === "right" ? + -data.targetWidth : + 0, + offset = -2 * data.offset[ 0 ], + newOverRight, + newOverLeft; + + if ( overLeft < 0 ) { + newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - + outerWidth - withinOffset; + if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) { + position.left += myOffset + atOffset + offset; + } + } else if ( overRight > 0 ) { + newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + + atOffset + offset - offsetLeft; + if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) { + position.left += myOffset + atOffset + offset; + } + } + }, + top: function( position, data ) { + var within = data.within, + withinOffset = within.offset.top + within.scrollTop, + outerHeight = within.height, + offsetTop = within.isWindow ? within.scrollTop : within.offset.top, + collisionPosTop = position.top - data.collisionPosition.marginTop, + overTop = collisionPosTop - offsetTop, + overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, + top = data.my[ 1 ] === "top", + myOffset = top ? + -data.elemHeight : + data.my[ 1 ] === "bottom" ? + data.elemHeight : + 0, + atOffset = data.at[ 1 ] === "top" ? + data.targetHeight : + data.at[ 1 ] === "bottom" ? + -data.targetHeight : + 0, + offset = -2 * data.offset[ 1 ], + newOverTop, + newOverBottom; + if ( overTop < 0 ) { + newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - + outerHeight - withinOffset; + if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) { + position.top += myOffset + atOffset + offset; + } + } else if ( overBottom > 0 ) { + newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + + offset - offsetTop; + if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) { + position.top += myOffset + atOffset + offset; + } + } + } + }, + flipfit: { + left: function() { + $.ui.position.flip.left.apply( this, arguments ); + $.ui.position.fit.left.apply( this, arguments ); + }, + top: function() { + $.ui.position.flip.top.apply( this, arguments ); + $.ui.position.fit.top.apply( this, arguments ); + } + } +}; + +} )(); + +var position = $.ui.position; + + +/*! + * jQuery UI :data 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: :data Selector +//>>group: Core +//>>description: Selects elements which have data stored under the specified key. +//>>docs: http://api.jqueryui.com/data-selector/ + + +var data = $.extend( $.expr[ ":" ], { + data: $.expr.createPseudo ? + $.expr.createPseudo( function( dataName ) { + return function( elem ) { + return !!$.data( elem, dataName ); + }; + } ) : + + // Support: jQuery <1.8 + function( elem, i, match ) { + return !!$.data( elem, match[ 3 ] ); + } +} ); + +/*! + * jQuery UI Disable Selection 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: disableSelection +//>>group: Core +//>>description: Disable selection of text content within the set of matched elements. +//>>docs: http://api.jqueryui.com/disableSelection/ + +// This file is deprecated + + +var disableSelection = $.fn.extend( { + disableSelection: ( function() { + var eventType = "onselectstart" in document.createElement( "div" ) ? + "selectstart" : + "mousedown"; + + return function() { + return this.on( eventType + ".ui-disableSelection", function( event ) { + event.preventDefault(); + } ); + }; + } )(), + + enableSelection: function() { + return this.off( ".ui-disableSelection" ); + } +} ); + + + + +// Support: IE8 Only +// IE8 does not support the form attribute and when it is supplied. It overwrites the form prop +// with a string, so we need to find the proper form. +var form = $.fn.form = function() { + return typeof this[ 0 ].form === "string" ? this.closest( "form" ) : $( this[ 0 ].form ); +}; + + +/*! + * jQuery UI Form Reset Mixin 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: Form Reset Mixin +//>>group: Core +//>>description: Refresh input widgets when their form is reset +//>>docs: http://api.jqueryui.com/form-reset-mixin/ + + + +var formResetMixin = $.ui.formResetMixin = { + _formResetHandler: function() { + var form = $( this ); + + // Wait for the form reset to actually happen before refreshing + setTimeout( function() { + var instances = form.data( "ui-form-reset-instances" ); + $.each( instances, function() { + this.refresh(); + } ); + } ); + }, + + _bindFormResetHandler: function() { + this.form = this.element.form(); + if ( !this.form.length ) { + return; + } + + var instances = this.form.data( "ui-form-reset-instances" ) || []; + if ( !instances.length ) { + + // We don't use _on() here because we use a single event handler per form + this.form.on( "reset.ui-form-reset", this._formResetHandler ); + } + instances.push( this ); + this.form.data( "ui-form-reset-instances", instances ); + }, + + _unbindFormResetHandler: function() { + if ( !this.form.length ) { + return; + } + + var instances = this.form.data( "ui-form-reset-instances" ); + instances.splice( $.inArray( this, instances ), 1 ); + if ( instances.length ) { + this.form.data( "ui-form-reset-instances", instances ); + } else { + this.form + .removeData( "ui-form-reset-instances" ) + .off( "reset.ui-form-reset" ); + } + } +}; + + +/*! + * jQuery UI Keycode 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: Keycode +//>>group: Core +//>>description: Provide keycodes as keynames +//>>docs: http://api.jqueryui.com/jQuery.ui.keyCode/ + + +var keycode = $.ui.keyCode = { + BACKSPACE: 8, + COMMA: 188, + DELETE: 46, + DOWN: 40, + END: 35, + ENTER: 13, + ESCAPE: 27, + HOME: 36, + LEFT: 37, + PAGE_DOWN: 34, + PAGE_UP: 33, + PERIOD: 190, + RIGHT: 39, + SPACE: 32, + TAB: 9, + UP: 38 +}; + + + + +// Internal use only +var escapeSelector = $.ui.escapeSelector = ( function() { + var selectorEscape = /([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g; + return function( selector ) { + return selector.replace( selectorEscape, "\\$1" ); + }; +} )(); + + +/*! + * jQuery UI Labels 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: labels +//>>group: Core +//>>description: Find all the labels associated with a given input +//>>docs: http://api.jqueryui.com/labels/ + + + +var labels = $.fn.labels = function() { + var ancestor, selector, id, labels, ancestors; + + // Check control.labels first + if ( this[ 0 ].labels && this[ 0 ].labels.length ) { + return this.pushStack( this[ 0 ].labels ); + } + + // Support: IE <= 11, FF <= 37, Android <= 2.3 only + // Above browsers do not support control.labels. Everything below is to support them + // as well as document fragments. control.labels does not work on document fragments + labels = this.eq( 0 ).parents( "label" ); + + // Look for the label based on the id + id = this.attr( "id" ); + if ( id ) { + + // We don't search against the document in case the element + // is disconnected from the DOM + ancestor = this.eq( 0 ).parents().last(); + + // Get a full set of top level ancestors + ancestors = ancestor.add( ancestor.length ? ancestor.siblings() : this.siblings() ); + + // Create a selector for the label based on the id + selector = "label[for='" + $.ui.escapeSelector( id ) + "']"; + + labels = labels.add( ancestors.find( selector ).addBack( selector ) ); + + } + + // Return whatever we have found for labels + return this.pushStack( labels ); +}; + + +/*! + * jQuery UI Scroll Parent 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: scrollParent +//>>group: Core +//>>description: Get the closest ancestor element that is scrollable. +//>>docs: http://api.jqueryui.com/scrollParent/ + + + +var scrollParent = $.fn.scrollParent = function( includeHidden ) { + var position = this.css( "position" ), + excludeStaticParent = position === "absolute", + overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/, + scrollParent = this.parents().filter( function() { + var parent = $( this ); + if ( excludeStaticParent && parent.css( "position" ) === "static" ) { + return false; + } + return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + + parent.css( "overflow-x" ) ); + } ).eq( 0 ); + + return position === "fixed" || !scrollParent.length ? + $( this[ 0 ].ownerDocument || document ) : + scrollParent; +}; + + +/*! + * jQuery UI Unique ID 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: uniqueId +//>>group: Core +//>>description: Functions to generate and remove uniqueId's +//>>docs: http://api.jqueryui.com/uniqueId/ + + + +var uniqueId = $.fn.extend( { + uniqueId: ( function() { + var uuid = 0; + + return function() { + return this.each( function() { + if ( !this.id ) { + this.id = "ui-id-" + ( ++uuid ); + } + } ); + }; + } )(), + + removeUniqueId: function() { + return this.each( function() { + if ( /^ui-id-\d+$/.test( this.id ) ) { + $( this ).removeAttr( "id" ); + } + } ); + } +} ); + + + + +// This file is deprecated +var ie = $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); + +/*! + * jQuery UI Mouse 1.12.1 + * http://jqueryui.com + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + */ + +//>>label: Mouse +//>>group: Widgets +//>>description: Abstracts mouse-based interactions to assist in creating certain widgets. +//>>docs: http://api.jqueryui.com/mouse/ + + + +var mouseHandled = false; +$( document ).on( "mouseup", function() { + mouseHandled = false; +} ); + +var widgetsMouse = $.widget( "ui.mouse", { + version: "1.12.1", + options: { + cancel: "input, textarea, button, select, option", + distance: 1, + delay: 0 + }, + _mouseInit: function() { + var that = this; + + this.element + .on( "mousedown." + this.widgetName, function( event ) { + return that._mouseDown( event ); + } ) + .on( "click." + this.widgetName, function( event ) { + if ( true === $.data( event.target, that.widgetName + ".preventClickEvent" ) ) { + $.removeData( event.target, that.widgetName + ".preventClickEvent" ); + event.stopImmediatePropagation(); + return false; + } + } ); + + this.started = false; + }, + + // TODO: make sure destroying one instance of mouse doesn't mess with + // other instances of mouse + _mouseDestroy: function() { + this.element.off( "." + this.widgetName ); + if ( this._mouseMoveDelegate ) { + this.document + .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); + } + }, + + _mouseDown: function( event ) { + + // don't let more than one widget handle mouseStart + if ( mouseHandled ) { + return; + } + + this._mouseMoved = false; + + // We may have missed mouseup (out of window) + ( this._mouseStarted && this._mouseUp( event ) ); + + this._mouseDownEvent = event; + + var that = this, + btnIsLeft = ( event.which === 1 ), + + // event.target.nodeName works around a bug in IE 8 with + // disabled inputs (#7620) + elIsCancel = ( typeof this.options.cancel === "string" && event.target.nodeName ? + $( event.target ).closest( this.options.cancel ).length : false ); + if ( !btnIsLeft || elIsCancel || !this._mouseCapture( event ) ) { + return true; + } + + this.mouseDelayMet = !this.options.delay; + if ( !this.mouseDelayMet ) { + this._mouseDelayTimer = setTimeout( function() { + that.mouseDelayMet = true; + }, this.options.delay ); + } + + if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { + this._mouseStarted = ( this._mouseStart( event ) !== false ); + if ( !this._mouseStarted ) { + event.preventDefault(); + return true; + } + } + + // Click event may never have fired (Gecko & Opera) + if ( true === $.data( event.target, this.widgetName + ".preventClickEvent" ) ) { + $.removeData( event.target, this.widgetName + ".preventClickEvent" ); + } + + // These delegates are required to keep context + this._mouseMoveDelegate = function( event ) { + return that._mouseMove( event ); + }; + this._mouseUpDelegate = function( event ) { + return that._mouseUp( event ); + }; + + this.document + .on( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .on( "mouseup." + this.widgetName, this._mouseUpDelegate ); + + event.preventDefault(); + + mouseHandled = true; + return true; + }, + + _mouseMove: function( event ) { + + // Only check for mouseups outside the document if you've moved inside the document + // at least once. This prevents the firing of mouseup in the case of IE<9, which will + // fire a mousemove event if content is placed under the cursor. See #7778 + // Support: IE <9 + if ( this._mouseMoved ) { + + // IE mouseup check - mouseup happened when mouse was out of window + if ( $.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && + !event.button ) { + return this._mouseUp( event ); + + // Iframe mouseup check - mouseup occurred in another document + } else if ( !event.which ) { + + // Support: Safari <=8 - 9 + // Safari sets which to 0 if you press any of the following keys + // during a drag (#14461) + if ( event.originalEvent.altKey || event.originalEvent.ctrlKey || + event.originalEvent.metaKey || event.originalEvent.shiftKey ) { + this.ignoreMissingWhich = true; + } else if ( !this.ignoreMissingWhich ) { + return this._mouseUp( event ); + } + } + } + + if ( event.which || event.button ) { + this._mouseMoved = true; + } + + if ( this._mouseStarted ) { + this._mouseDrag( event ); + return event.preventDefault(); + } + + if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { + this._mouseStarted = + ( this._mouseStart( this._mouseDownEvent, event ) !== false ); + ( this._mouseStarted ? this._mouseDrag( event ) : this._mouseUp( event ) ); + } + + return !this._mouseStarted; + }, + + _mouseUp: function( event ) { + this.document + .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) + .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); + + if ( this._mouseStarted ) { + this._mouseStarted = false; + + if ( event.target === this._mouseDownEvent.target ) { + $.data( event.target, this.widgetName + ".preventClickEvent", true ); + } + + this._mouseStop( event ); + } + + if ( this._mouseDelayTimer ) { + clearTimeout( this._mouseDelayTimer ); + delete this._mouseDelayTimer; + } + + this.ignoreMissingWhich = false; + mouseHandled = false; + event.preventDefault(); + }, + + _mouseDistanceMet: function( event ) { + return ( Math.max( + Math.abs( this._mouseDownEvent.pageX - event.pageX ), + Math.abs( this._mouseDownEvent.pageY - event.pageY ) + ) >= this.options.distance + ); + }, + + _mouseDelayMet: function( /* event */ ) { + return this.mouseDelayMet; + }, + + // These are placeholder methods, to be overriden by extending plugin + _mouseStart: function( /* event */ ) {}, + _mouseDrag: function( /* event */ ) {}, + _mouseStop: function( /* event */ ) {}, + _mouseCapture: function( /* event */ ) { return true; } +} ); + + + + +// $.ui.plugin is deprecated. Use $.widget() extensions instead. +var plugin = $.ui.plugin = { + add: function( module, option, set ) { + var i, + proto = $.ui[ module ].prototype; + for ( i in set ) { + proto.plugins[ i ] = proto.plugins[ i ] || []; + proto.plugins[ i ].push( [ option, set[ i ] ] ); + } + }, + call: function( instance, name, args, allowDisconnected ) { + var i, + set = instance.plugins[ name ]; + + if ( !set ) { + return; + } + + if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || + instance.element[ 0 ].parentNode.nodeType === 11 ) ) { + return; + } + + for ( i = 0; i < set.length; i++ ) { + if ( instance.options[ set[ i ][ 0 ] ] ) { + set[ i ][ 1 ].apply( instance.element, args ); + } + } + } +}; + + + +var safeActiveElement = $.ui.safeActiveElement = function( document ) { + var activeElement; + + // Support: IE 9 only + // IE9 throws an "Unspecified error" accessing document.activeElement from an