<commit_before>import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
// import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
import Stats from 'stats.js';
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
import { MeshBVH, MeshBVHVisualizer, CONTAINED } from '..';

const params = {
	useBVH: true,

	helperDisplay: false,
	helperDepth: 10,

	wireframeDisplay: false,
	displayModel: true,

	animate: true,
	animation: 'SPIN',
	invert: false,
};

let renderer, camera, scene, gui, stats;
let controls, clock;
let colliderBvh, colliderMesh, bvhHelper;
let frontSideModel, backSideModel, planeMesh;
let clippingPlanes, outlineLines;
let initialClip = false;
let outputElement = null;
let time = 0;

const tempVector = new THREE.Vector3();
const tempLine = new THREE.Line3();
const inverseMatrix = new THREE.Matrix4();
const localPlane = new THREE.Plane();

init();
render();

function init() {

	outputElement = document.getElementById( 'output' );

	const bgColor = new THREE.Color( 0x263238 ).multiplyScalar( 0.1 );

	// renderer setup
	renderer = new THREE.WebGLRenderer( { antialias: true } );
	renderer.setPixelRatio( window.devicePixelRatio );
	renderer.setSize( window.innerWidth, window.innerHeight );
	renderer.setClearColor( bgColor, 1 );
	renderer.shadowMap.enabled = true;
	renderer.shadowMap.type = THREE.PCFSoftShadowMap;
	renderer.outputEncoding = THREE.sRGBEncoding;
	renderer.localClippingEnabled = true;
	document.body.appendChild( renderer.domElement );

	// scene setup
	scene = new THREE.Scene();
	scene.fog = new THREE.Fog( bgColor, 20, 70 );

	// lights
	const light = new THREE.DirectionalLight( 0xffffff, 0.8 );
	light.position.set( 1, 1.5, 2 ).multiplyScalar( 50 );
	scene.add( light );
	scene.add( new THREE.HemisphereLight( 0xffffff, 0x223344, 0.5 ) );

	// camera setup
	camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.01, 50 );
	camera.position.set( 0.4, 0.4, 0.4 );
	camera.far = 100;
	camera.updateProjectionMatrix();

	controls = new OrbitControls( camera, renderer.domElement );

	clock = new THREE.Clock();

	// clippingPlanes
	clippingPlanes = [
		new THREE.Plane(),
	];

	planeMesh = new THREE.Mesh( new THREE.PlaneBufferGeometry(), new THREE.MeshBasicMaterial( {
		side: THREE.DoubleSide,
		stencilWrite: true,
		stencilFunc: THREE.NotEqualStencilFunc,
		stencilFail: THREE.ZeroStencilOp,
		stencilZFail: THREE.ZeroStencilOp,
		stencilZPass: THREE.ZeroStencilOp,
	} ) );
	planeMesh.scale.setScalar( 1.5 );
	planeMesh.material.color.set( 0x80deea ).convertLinearToSRGB();
	planeMesh.renderOrder = 2;
	scene.add( planeMesh );

	// create line geometry with enough data to hold 100000 segments
	const lineGeometry = new THREE.BufferGeometry();
	const linePosAttr = new THREE.BufferAttribute( new Float32Array( 300000 ), 3, false );
	linePosAttr.setUsage( THREE.DynamicDrawUsage );
	lineGeometry.setAttribute( 'position', linePosAttr );
	outlineLines = new THREE.LineSegments( lineGeometry, new THREE.LineBasicMaterial() );
	outlineLines.material.color.set( 0x00acc1 ).convertSRGBToLinear();
	outlineLines.frustumCulled = false;
	outlineLines.renderOrder = 3;

	// load the model
	const loader = new GLTFLoader();
	loader.setMeshoptDecoder( MeshoptDecoder );
	loader.load( '../models/internal_combustion_engine/model.gltf', gltf => {

		// merge the geometry if needed
		// let model = gltf.scene;
		// model.updateMatrixWorld( true );

		// create a merged version if it isn't already
		// const geometries = [];
		// model.traverse( c => {

		// 	if ( c.isMesh ) {

		// 		const clonedGeometry = c.geometry.clone();
		// 		clonedGeometry.applyMatrix4( c.matrixWorld );
		// 		for ( const key in clonedGeometry.attributes ) {

		// 			if ( key === 'position' || key === 'normal' ) {

		// 				continue;

		// 			}

		// 			clonedGeometry.deleteAttribute( key );

		// 		}

		// 		geometries.push( clonedGeometry );

		// 	}

		// } );

		// const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries( geometries );
		// model = new THREE.Mesh( mergedGeometry, new THREE.MeshStandardMaterial() );

		// Render Order
		// 0. Render front model and back model with stencil
		// 1. Render surface color model
		// 2. Render clip pane cap
		// 3. Render outlines

		// use basic material because the using clip caps is expensive since the fragment
		// shader has to run always.
		const model = gltf.scene.children[ 0 ];
		const mergedGeometry = model.geometry;
		model.material = new THREE.MeshBasicMaterial();
		model.position.set( 0, 0, 0 );
		model.quaternion.identity();

		// color the surface of the geometry with an EQUAL depth to limit the amount of
		// fragment shading that has to run.
		const surfaceModel = model.clone();
		surfaceModel.material = new THREE.MeshStandardMaterial( {
			depthFunc: THREE.EqualDepth,
		} );
		surfaceModel.renderOrder = 1;

		outlineLines.scale.copy( model.scale );
		outlineLines.position.set( 0, 0, 0 );
		outlineLines.quaternion.identity();

		model.updateMatrixWorld( true );

		// Adjust all the materials to draw front and back side with stencil for clip cap
		const matSet = new Set();
		const materialMap = new Map();
		frontSideModel = model;
		frontSideModel.updateMatrixWorld( true );
		frontSideModel.traverse( c => {

			if ( c.isMesh ) {

				if ( materialMap.has( c.material ) ) {

					c.material = materialMap.get( c.material );
					return;

				}

				matSet.add( c.material );

				const material = c.material.clone();
				material.color.set( 0xffffff );
				material.roughness = 1.0;
				material.metalness = 0.0;
				material.side = THREE.FrontSide;
				material.stencilWrite = true;
				material.stencilFail = THREE.IncrementWrapStencilOp;
				material.stencilZFail = THREE.IncrementWrapStencilOp;
				material.stencilZPass = THREE.IncrementWrapStencilOp;
				material.clippingPlanes = clippingPlanes;

				materialMap.set( c.material, material );
				c.material = material;

			}

		} );

		materialMap.clear();

		backSideModel = frontSideModel.clone();
		backSideModel.traverse( c => {

			if ( c.isMesh ) {

				if ( materialMap.has( c.material ) ) {

					c.material = materialMap.get( c.material );
					return;

				}

				const material = c.material.clone();
				material.color.set( 0xffffff );
				material.roughness = 1.0;
				material.metalness = 0.0;
				material.colorWrite = false;
				material.depthWrite = false;
				material.side = THREE.BackSide;
				material.stencilWrite = true;
				material.stencilFail = THREE.DecrementWrapStencilOp;
				material.stencilZFail = THREE.DecrementWrapStencilOp;
				material.stencilZPass = THREE.DecrementWrapStencilOp;
				material.clippingPlanes = clippingPlanes;

				materialMap.set( c.material, material );
				c.material = material;

			}

		} );

		// create the collider and preview mesh
		colliderBvh = new MeshBVH( mergedGeometry, { maxLeafTris: 3 } );
		mergedGeometry.boundsTree = colliderBvh;

		colliderMesh = new THREE.Mesh( mergedGeometry, new THREE.MeshBasicMaterial( {
			wireframe: true,
			transparent: true,
			opacity: 0.01,
			depthWrite: false,
		} ) );
		colliderMesh.renderOrder = 2;
		colliderMesh.position.copy( model.position );
		colliderMesh.rotation.copy( model.rotation );
		colliderMesh.scale.copy( model.scale );

		bvhHelper = new MeshBVHVisualizer( colliderMesh, parseInt( params.helperDepth ) );
		bvhHelper.depth = parseInt( params.helperDepth );
		bvhHelper.update();

		// create group of meshes and offset it so they're centered
		const group = new THREE.Group();
		group.add( frontSideModel, backSideModel, surfaceModel, colliderMesh, bvhHelper, outlineLines );

		const box = new THREE.Box3();
		box.setFromObject( frontSideModel );
		box.getCenter( group.position ).multiplyScalar( - 1 );
		group.updateMatrixWorld( true );
		scene.add( group );

	} );

	// dat.gui
	gui = new GUI();

	gui.add( params, 'invert' );
	gui.add( params, 'animate' );
	gui.add( params, 'animation', [ 'SPIN', 'OSCILLATE' ] ).onChange( () => {

		time = 0;

	} );
	gui.add( params, 'displayModel' );
	gui.add( params, 'useBVH' );

	const helperFolder = gui.addFolder( 'helper' );
	helperFolder.add( params, 'wireframeDisplay' );
	helperFolder.add( params, 'helperDisplay' );
	helperFolder.add( params, 'helperDepth', 1, 20, 1 ).onChange( v => {

		if ( bvhHelper ) {

			bvhHelper.depth = parseInt( v );
			bvhHelper.update();

		}

	} );
	helperFolder.open();

	gui.open();

	// stats
	stats = new Stats();
	document.body.appendChild( stats.domElement );

	window.addEventListener( 'resize', function () {

		camera.aspect = window.innerWidth / window.innerHeight;
		camera.updateProjectionMatrix();

		renderer.setSize( window.innerWidth, window.innerHeight );
		renderer.setPixelRatio( window.devicePixelRatio );

	}, false );

}

function render() {

	if ( bvhHelper ) {

		bvhHelper.visible = params.helperDisplay;
		colliderMesh.visible = params.wireframeDisplay;

		frontSideModel.visible = params.displayModel;
		backSideModel.visible = params.displayModel;

	}

	// make the outlines darker if the model is shown
	outlineLines.material.color
		.set( params.displayModel ? 0x00acc1 : 0x4dd0e1 )
		.convertSRGBToLinear();

	const delta = Math.min( clock.getDelta(), 0.03 );
	if ( params.animate ) {

		time += delta;

		if ( params.animation === 'SPIN' ) {

			planeMesh.rotation.x = 0.25 * time;
			planeMesh.rotation.y = 0.25 * time;
			planeMesh.rotation.z = 0.25 * time;
			planeMesh.position.set( 0, 0, 0 );

		} else {

			planeMesh.position.set( Math.sin( 0.25 * time ) * 0.325, 0, 0 );
			planeMesh.rotation.set( 0, Math.PI / 2, 0 );

		}

		planeMesh.updateMatrixWorld();

	}

	const clippingPlane = clippingPlanes[ 0 ];
	clippingPlane.normal.set( 0, 0, params.invert ? 1 : - 1 );
	clippingPlane.constant = 0;
	clippingPlane.applyMatrix4( planeMesh.matrixWorld );

	// Perform the clipping
	if ( colliderBvh && ( params.animate || ! initialClip ) ) {

		initialClip = true;

		// get the clipping plane in the local space of the BVH
		inverseMatrix.copy( colliderMesh.matrixWorld ).invert();
		localPlane.copy( clippingPlane ).applyMatrix4( inverseMatrix );

		let index = 0;
		const posAttr = outlineLines.geometry.attributes.position;
		const startTime = window.performance.now();
		colliderBvh.shapecast( {

			intersectsBounds: box => {

				// if we're not using the BVH then skip straight to iterating over all triangles
				if ( ! params.useBVH ) {

					return CONTAINED;

				}

				return localPlane.intersectsBox( box );

			},

			intersectsTriangle: tri => {

				// check each triangle edge to see if it intersects with the plane. If so then
				// add it to the list of segments.
				let count = 0;
				tempLine.start.copy( tri.a );
				tempLine.end.copy( tri.b );
				if ( localPlane.intersectLine( tempLine, tempVector ) ) {

					posAttr.setXYZ( index, tempVector.x, tempVector.y, tempVector.z );
					index ++;
					count ++;

				}

				tempLine.start.copy( tri.b );
				tempLine.end.copy( tri.c );
				if ( localPlane.intersectLine( tempLine, tempVector ) ) {

					posAttr.setXYZ( index, tempVector.x, tempVector.y, tempVector.z );
					count ++;
					index ++;

				}

				tempLine.start.copy( tri.c );
				tempLine.end.copy( tri.a );
				if ( localPlane.intersectLine( tempLine, tempVector ) ) {

					posAttr.setXYZ( index, tempVector.x, tempVector.y, tempVector.z );
					count ++;
					index ++;

				}

				// If we only intersected with one or three sides then just remove it. This could be handled
				// more gracefully.
				if ( count !== 2 ) {

					index -= count;

				}

			},

		} );

		// set the draw range to only the new segments and offset the lines so they don't intersect with the geometry
		outlineLines.geometry.setDrawRange( 0, index );
		outlineLines.position.copy( clippingPlane.normal ).multiplyScalar( - 0.00001 );
		posAttr.needsUpdate = true;

		const delta = window.performance.now() - startTime;
		outputElement.innerText = `${ parseFloat( delta.toFixed( 3 ) ) }ms`;

	}

	stats.update();
	requestAnimationFrame( render );

	controls.update();

	renderer.render( scene, camera );

}

<commit_msg>Fix: Fix missing intersecting edges<commit_after>import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
// import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
import Stats from 'stats.js';
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
import { MeshBVH, MeshBVHVisualizer, CONTAINED } from '..';

const params = {
	useBVH: true,

	helperDisplay: false,
	helperDepth: 10,

	wireframeDisplay: false,
	displayModel: true,

	animate: true,
	animation: 'SPIN',
	invert: false,
};

let renderer, camera, scene, gui, stats;
let controls, clock;
let colliderBvh, colliderMesh, bvhHelper;
let frontSideModel, backSideModel, planeMesh;
let clippingPlanes, outlineLines;
let initialClip = false;
let outputElement = null;
let time = 0;

const tempVector = new THREE.Vector3();
const tempLine = new THREE.Line3();
const inverseMatrix = new THREE.Matrix4();
const localPlane = new THREE.Plane();

init();
render();

function init() {

	outputElement = document.getElementById( 'output' );

	const bgColor = new THREE.Color( 0x263238 ).multiplyScalar( 0.1 );

	// renderer setup
	renderer = new THREE.WebGLRenderer( { antialias: true } );
	renderer.setPixelRatio( window.devicePixelRatio );
	renderer.setSize( window.innerWidth, window.innerHeight );
	renderer.setClearColor( bgColor, 1 );
	renderer.shadowMap.enabled = true;
	renderer.shadowMap.type = THREE.PCFSoftShadowMap;
	renderer.outputEncoding = THREE.sRGBEncoding;
	renderer.localClippingEnabled = true;
	document.body.appendChild( renderer.domElement );

	// scene setup
	scene = new THREE.Scene();
	scene.fog = new THREE.Fog( bgColor, 20, 70 );

	// lights
	const light = new THREE.DirectionalLight( 0xffffff, 0.8 );
	light.position.set( 1, 1.5, 2 ).multiplyScalar( 50 );
	scene.add( light );
	scene.add( new THREE.HemisphereLight( 0xffffff, 0x223344, 0.5 ) );

	// camera setup
	camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.01, 50 );
	camera.position.set( 0.4, 0.4, 0.4 );
	camera.far = 100;
	camera.updateProjectionMatrix();

	controls = new OrbitControls( camera, renderer.domElement );

	clock = new THREE.Clock();

	// clippingPlanes
	clippingPlanes = [
		new THREE.Plane(),
	];

	planeMesh = new THREE.Mesh( new THREE.PlaneBufferGeometry(), new THREE.MeshBasicMaterial( {
		side: THREE.DoubleSide,
		stencilWrite: true,
		stencilFunc: THREE.NotEqualStencilFunc,
		stencilFail: THREE.ZeroStencilOp,
		stencilZFail: THREE.ZeroStencilOp,
		stencilZPass: THREE.ZeroStencilOp,
	} ) );
	planeMesh.scale.setScalar( 1.5 );
	planeMesh.material.color.set( 0x80deea ).convertLinearToSRGB();
	planeMesh.renderOrder = 2;
	scene.add( planeMesh );

	// create line geometry with enough data to hold 100000 segments
	const lineGeometry = new THREE.BufferGeometry();
	const linePosAttr = new THREE.BufferAttribute( new Float32Array( 300000 ), 3, false );
	linePosAttr.setUsage( THREE.DynamicDrawUsage );
	lineGeometry.setAttribute( 'position', linePosAttr );
	outlineLines = new THREE.LineSegments( lineGeometry, new THREE.LineBasicMaterial() );
	outlineLines.material.color.set( 0x00acc1 ).convertSRGBToLinear();
	outlineLines.frustumCulled = false;
	outlineLines.renderOrder = 3;

	// load the model
	const loader = new GLTFLoader();
	loader.setMeshoptDecoder( MeshoptDecoder );
	loader.load( '../models/internal_combustion_engine/model.gltf', gltf => {

		// merge the geometry if needed
		// let model = gltf.scene;
		// model.updateMatrixWorld( true );

		// create a merged version if it isn't already
		// const geometries = [];
		// model.traverse( c => {

		// 	if ( c.isMesh ) {

		// 		const clonedGeometry = c.geometry.clone();
		// 		clonedGeometry.applyMatrix4( c.matrixWorld );
		// 		for ( const key in clonedGeometry.attributes ) {

		// 			if ( key === 'position' || key === 'normal' ) {

		// 				continue;

		// 			}

		// 			clonedGeometry.deleteAttribute( key );

		// 		}

		// 		geometries.push( clonedGeometry );

		// 	}

		// } );

		// const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries( geometries );
		// model = new THREE.Mesh( mergedGeometry, new THREE.MeshStandardMaterial() );

		// Render Order
		// 0. Render front model and back model with stencil
		// 1. Render surface color model
		// 2. Render clip pane cap
		// 3. Render outlines

		// use basic material because the using clip caps is expensive since the fragment
		// shader has to run always.
		const model = gltf.scene.children[ 0 ];
		const mergedGeometry = model.geometry;
		model.material = new THREE.MeshBasicMaterial();
		model.position.set( 0, 0, 0 );
		model.quaternion.identity();

		// color the surface of the geometry with an EQUAL depth to limit the amount of
		// fragment shading that has to run.
		const surfaceModel = model.clone();
		surfaceModel.material = new THREE.MeshStandardMaterial( {
			depthFunc: THREE.EqualDepth,
		} );
		surfaceModel.renderOrder = 1;

		outlineLines.scale.copy( model.scale );
		outlineLines.position.set( 0, 0, 0 );
		outlineLines.quaternion.identity();

		model.updateMatrixWorld( true );

		// Adjust all the materials to draw front and back side with stencil for clip cap
		const matSet = new Set();
		const materialMap = new Map();
		frontSideModel = model;
		frontSideModel.updateMatrixWorld( true );
		frontSideModel.traverse( c => {

			if ( c.isMesh ) {

				if ( materialMap.has( c.material ) ) {

					c.material = materialMap.get( c.material );
					return;

				}

				matSet.add( c.material );

				const material = c.material.clone();
				material.color.set( 0xffffff );
				material.roughness = 1.0;
				material.metalness = 0.0;
				material.side = THREE.FrontSide;
				material.stencilWrite = true;
				material.stencilFail = THREE.IncrementWrapStencilOp;
				material.stencilZFail = THREE.IncrementWrapStencilOp;
				material.stencilZPass = THREE.IncrementWrapStencilOp;
				material.clippingPlanes = clippingPlanes;

				materialMap.set( c.material, material );
				c.material = material;

			}

		} );

		materialMap.clear();

		backSideModel = frontSideModel.clone();
		backSideModel.traverse( c => {

			if ( c.isMesh ) {

				if ( materialMap.has( c.material ) ) {

					c.material = materialMap.get( c.material );
					return;

				}

				const material = c.material.clone();
				material.color.set( 0xffffff );
				material.roughness = 1.0;
				material.metalness = 0.0;
				material.colorWrite = false;
				material.depthWrite = false;
				material.side = THREE.BackSide;
				material.stencilWrite = true;
				material.stencilFail = THREE.DecrementWrapStencilOp;
				material.stencilZFail = THREE.DecrementWrapStencilOp;
				material.stencilZPass = THREE.DecrementWrapStencilOp;
				material.clippingPlanes = clippingPlanes;

				materialMap.set( c.material, material );
				c.material = material;

			}

		} );

		// create the collider and preview mesh
		colliderBvh = new MeshBVH( mergedGeometry, { maxLeafTris: 3 } );
		mergedGeometry.boundsTree = colliderBvh;

		colliderMesh = new THREE.Mesh( mergedGeometry, new THREE.MeshBasicMaterial( {
			wireframe: true,
			transparent: true,
			opacity: 0.01,
			depthWrite: false,
		} ) );
		colliderMesh.renderOrder = 2;
		colliderMesh.position.copy( model.position );
		colliderMesh.rotation.copy( model.rotation );
		colliderMesh.scale.copy( model.scale );

		bvhHelper = new MeshBVHVisualizer( colliderMesh, parseInt( params.helperDepth ) );
		bvhHelper.depth = parseInt( params.helperDepth );
		bvhHelper.update();

		// create group of meshes and offset it so they're centered
		const group = new THREE.Group();
		group.add( frontSideModel, backSideModel, surfaceModel, colliderMesh, bvhHelper, outlineLines );

		const box = new THREE.Box3();
		box.setFromObject( frontSideModel );
		box.getCenter( group.position ).multiplyScalar( - 1 );
		group.updateMatrixWorld( true );
		scene.add( group );

	} );

	// dat.gui
	gui = new GUI();

	gui.add( params, 'invert' );
	gui.add( params, 'animate' );
	gui.add( params, 'animation', [ 'SPIN', 'OSCILLATE' ] ).onChange( () => {

		time = 0;

	} );
	gui.add( params, 'displayModel' );
	gui.add( params, 'useBVH' );

	const helperFolder = gui.addFolder( 'helper' );
	helperFolder.add( params, 'wireframeDisplay' );
	helperFolder.add( params, 'helperDisplay' );
	helperFolder.add( params, 'helperDepth', 1, 20, 1 ).onChange( v => {

		if ( bvhHelper ) {

			bvhHelper.depth = parseInt( v );
			bvhHelper.update();

		}

	} );
	helperFolder.open();

	gui.open();

	// stats
	stats = new Stats();
	document.body.appendChild( stats.domElement );

	window.addEventListener( 'resize', function () {

		camera.aspect = window.innerWidth / window.innerHeight;
		camera.updateProjectionMatrix();

		renderer.setSize( window.innerWidth, window.innerHeight );
		renderer.setPixelRatio( window.devicePixelRatio );

	}, false );

}

function render() {

	if ( bvhHelper ) {

		bvhHelper.visible = params.helperDisplay;
		colliderMesh.visible = params.wireframeDisplay;

		frontSideModel.visible = params.displayModel;
		backSideModel.visible = params.displayModel;

	}

	// make the outlines darker if the model is shown
	outlineLines.material.color
		.set( params.displayModel ? 0x00acc1 : 0x4dd0e1 )
		.convertSRGBToLinear();

	const delta = Math.min( clock.getDelta(), 0.03 );
	if ( params.animate ) {

		time += delta;

		if ( params.animation === 'SPIN' ) {

			planeMesh.rotation.x = 0.25 * time;
			planeMesh.rotation.y = 0.25 * time;
			planeMesh.rotation.z = 0.25 * time;
			planeMesh.position.set( 0, 0, 0 );

		} else {

			planeMesh.position.set( Math.sin( 0.25 * time ) * 0.325, 0, 0 );
			planeMesh.rotation.set( 0, Math.PI / 2, 0 );

		}

		planeMesh.updateMatrixWorld();

	}

	const clippingPlane = clippingPlanes[ 0 ];
	clippingPlane.normal.set( 0, 0, params.invert ? 1 : - 1 );
	clippingPlane.constant = 0;
	clippingPlane.applyMatrix4( planeMesh.matrixWorld );

	// Perform the clipping
	if ( colliderBvh && ( params.animate || ! initialClip ) ) {

		initialClip = true;

		// get the clipping plane in the local space of the BVH
		inverseMatrix.copy( colliderMesh.matrixWorld ).invert();
		localPlane.copy( clippingPlane ).applyMatrix4( inverseMatrix );

		let index = 0;
		const posAttr = outlineLines.geometry.attributes.position;
		const startTime = window.performance.now();
		colliderBvh.shapecast( {

			intersectsBounds: box => {

				// if we're not using the BVH then skip straight to iterating over all triangles
				if ( ! params.useBVH ) {

					return CONTAINED;

				}

				return localPlane.intersectsBox( box );

			},

			intersectsTriangle: tri => {

				// check each triangle edge to see if it intersects with the plane. If so then
				// add it to the list of segments.
				let count = 0;
				tempLine.start.copy( tri.a );
				tempLine.end.copy( tri.b );
				if ( localPlane.intersectLine( tempLine, tempVector ) ) {

					posAttr.setXYZ( index, tempVector.x, tempVector.y, tempVector.z );
					index ++;
					count ++;

				}

				tempLine.start.copy( tri.b );
				tempLine.end.copy( tri.c );
				if ( localPlane.intersectLine( tempLine, tempVector ) ) {

					posAttr.setXYZ( index, tempVector.x, tempVector.y, tempVector.z );
					count ++;
					index ++;

				}

				tempLine.start.copy( tri.c );
				tempLine.end.copy( tri.a );
				if ( localPlane.intersectLine( tempLine, tempVector ) ) {

					posAttr.setXYZ( index, tempVector.x, tempVector.y, tempVector.z );
					count ++;
					index ++;

				}

				// When the plane passes through a vertex and one of the edges of the triangle, there will be three intersections, two of which must be repeated
				if ( count === 3 ) {

					// If the last point is a duplicate intersection
					if (
						( posAttr.positions[ index - 1 ].x === posAttr.positions[ index - 2 ].x && posAttr.positions[ index - 1 ].y === posAttr.positions[ index - 2 ].y )
							|| ( posAttr.positions[ index - 1 ].x === posAttr.positions[ index - 3 ].x && posAttr.positions[ index - 1 ].y === posAttr.positions[ index - 3 ].y )
					) {

						count --;
						index --;

					} else if ( posAttr.positions[ index - 2 ].x === posAttr.positions[ index - 3 ].x && posAttr.positions[ index - 2 ].y === posAttr.positions[ index - 3 ].y ) {

						// If the last point is not a duplicate intersection
						// Set the penultimate point as a distinct point and delete the last point
						posAttr.setXYZ( index - 2, posAttr.positions[ index - 1 ].x, posAttr.positions[ index - 1 ].y, posAttr.positions[ index - 1 ].z );
						count --;
						index --;

					}

				}

				// If we only intersected with one or three sides then just remove it. This could be handled
				// more gracefully.
				if ( count !== 2 ) {

					index -= count;

				}

			},

		} );

		// set the draw range to only the new segments and offset the lines so they don't intersect with the geometry
		outlineLines.geometry.setDrawRange( 0, index );
		outlineLines.position.copy( clippingPlane.normal ).multiplyScalar( - 0.00001 );
		posAttr.needsUpdate = true;

		const delta = window.performance.now() - startTime;
		outputElement.innerText = `${ parseFloat( delta.toFixed( 3 ) ) }ms`;

	}

	stats.update();
	requestAnimationFrame( render );

	controls.update();

	renderer.render( scene, camera );

}

<|endoftext|>


<commit_before>// shuffle Array
Array.prototype.shuffle = function() {
    var i = this.length, p, t;
    while (i--) {
        p = Math.floor(Math.random()*i);
        t = this[i];
        this[i] = this[p];
        this[p] = t;
    }
    return this
}

// return shuffle covers
covers = covers.shuffle();

// return random number
function R(a, b){
    return Math.floor(Math.random() * (b - a) + a)
}

var L = {
    loader: function() {
        var w = window.innerWidth;
        var css = '';
        $('#loaderstyle').remove()
        $('head').append(css)
    },
    loading: function() {
        $('.loader').addClass('loading').show()
    },
    loaded: function() {
        $('.loader').removeClass('loading').hide()
    }
}

$(function($) {

    // define
    var init, rows, columns, container = $('#container'), S, H, t0, t1;
    var mark = $('#mark');

    // window resize time
    var T;

    // get window width
    var W = window.innerWidth;

    // loader
    L.loader()

    ;(init = function() {

        L.loading()

        var _width = container.width();

        columns = Math.ceil(_width / 100);

        // get item width and gap width
        var itemWidth = Math.floor(_width / columns),
            gapWidth = _width - itemWidth * columns,

            // html string
            str = '';

        // get rows
        rows = 4;

        // total covers to show
        S = covers.slice(0, rows * columns);

        // random covers
        H = covers.slice(rows * columns);

        for (var k = 0; k < S.length; k ++) {
            str += '

';
        }

        $('#container').html(str)

        for(var i = 0; i < rows; i ++) {
            for(var j = 0; j < columns; j ++) {
                var idx = columns * i + j,
                    item = container.find('li').eq(idx),
                    w = j < gapWidth ? itemWidth + 1 : itemWidth;

                item.css({
                    width : w,
                    height : itemWidth
                })

                if (i === 0) {
                    $('#line').append('

')
                }
            }
        }

        container.height(itemWidth * rows).data('w', _width).data('h', itemWidth * rows)

        container.find('li').each(function() {
            var m = $(this).find('img');

            m.attr('src', 'thumbnails/'+ m.data('u') +'.jpg').on('load', function() {
                $(this).addClass('loaded')
            })

        })

        setTimeout(function() {

            var _a = [];
            for (var i = 0; i < S.length; i ++) {
                _a.push(i)
            }

            if (!_a.length) {
                return
            }

            (function f() {

                var n = R(0, _a.length),
                    e = container.find('li').eq(_a[n]).find('img');

                if (e.hasClass('loaded')) {
                    e.css('opacity', 1)
                    _a.splice(n, 1)

                    if (!_a.length) {
                        g()
                        L.loaded()
                    }
                }

                clearTimeout(t0)
                t0 = setTimeout(function() {
                    f()
                }, 100)

            }())

            function g() {

                if (!H.length) {
                    return
                }

                var Hn = R(0, H.length),
                    Hl = 'thumbnails/'+ H[Hn][0] +'.jpg',

                    Sn = R(0, S.length),
                    Sl = container.find('li').eq(Sn);

                $('').on('load', function() {

                    Sl.prepend('')

                    $(Sl.find('img')[0]).css('opacity', 1)
                    $(Sl.find('img')[1]).css('opacity', 0)

                    setTimeout(function() {
                        $(Sl.find('img')[1]).remove()
                    }, 1000)

                    var N = H.splice(Hn, 1);
                    H.push(N[0])

                    clearTimeout(t1)
                    t1 = setTimeout(function() {
                        g()
                    }, R(1001, 3000))
                })

            }

        }, 1000)

    }).call()

    // window resize
    $(window).on('resize', function() {
        mark.trigger('click')
        clearTimeout(T)

        T = setTimeout(function() {
            if (window.innerWidth != W) {
                init()
            }
        }, 500)
    })

    // show big cover
    container.on('click', function(e) {
        if (container.data('click') == 1) {
            return
        }

        var target = $(e.target);

        if (parseInt(target.css('opacity')) === 0 || !target.data('u') || !target.attr('src')) {
            return
        }

        container.data('click', 1)

        L.loading()

        var cover = 'covers/'+ target.data('u') +'.jpg';
        $('').on('load', function() {
            mark.addClass('show')
            L.loaded()

            mark.find('.inner').width(target.data('w')).height(target.data('h'))
            mark.find('.img').html($(this))
            mark.find('.bg').css('background-color', target.data('c'))
            mark.find('.info').html('
'+ target.data('m') +'

'+ target.data('r') +'
')
        })
    })

    // close mark
    mark.on('click', function(e) {
        mark.removeClass('show').addClass('loading').find('.img').html('')
        container.data('click', 0)
    })

})
<commit_msg>fix colums and random thumbnails<commit_after>// shuffle Array
Array.prototype.shuffle = function() {
    var i = this.length, p, t;
    while (i--) {
        p = Math.floor(Math.random()*i);
        t = this[i];
        this[i] = this[p];
        this[p] = t;
    }
    return this
}

// return shuffle covers
covers = covers.shuffle();

// return random number
function R(a, b){
    return Math.floor(Math.random() * (b - a) + a)
}

var L = {
    loader: function() {
        var w = window.innerWidth;
        var css = '';
        $('#loaderstyle').remove()
        $('head').append(css)
    },
    loading: function() {
        $('.loader').addClass('loading').show()
    },
    loaded: function() {
        $('.loader').removeClass('loading').hide()
    }
}

$(function($) {

    // define
    var init, rows, columns, container = $('#container'), S, H, t0, t1;
    var mark = $('#mark');

    // window resize time
    var T;

    // get window width
    var W = window.innerWidth;

    // loader
    L.loader()

    ;(init = function() {

        L.loading()

        var _width = container.width();

        columns = Math.ceil(_width / 100);

        // get item width and gap width
        var itemWidth = Math.floor(_width / columns),
            gapWidth = _width - itemWidth * columns,

            // html string
            str = '';

        // get rows
        rows = 4;

        // total covers to show
        S = covers.slice(0, rows * columns);

        // random covers
        H = covers.slice(rows * columns);

        for (var k = 0; k < S.length; k ++) {
            str += '

';
        }

        $('#container').html(str)

        for(var i = 0; i < rows; i ++) {
            for(var j = 0; j < columns; j ++) {
                var idx = columns * i + j,
                    item = container.find('li').eq(idx),
                    w = j < gapWidth ? itemWidth + 1 : itemWidth;

                item.css({
                    width : w,
                    height : itemWidth
                })

                if (i === 0) {
                    $('#line').append('

')
                }
            }
        }

        container.height(itemWidth * rows).data('w', _width).data('h', itemWidth * rows)

        container.find('li').each(function() {
            var m = $(this).find('img');

            m.attr('src', 'thumbnails/'+ m.data('u') +'.jpg').on('load', function() {
                $(this).addClass('loaded')
            })

        })

        setTimeout(function() {

            var _a = [];
            for (var i = 0; i < S.length; i ++) {
                _a.push(i)
            }

            if (!_a.length) {
                return
            }

            (function f() {

                var n = R(0, _a.length),
                    e = container.find('li').eq(_a[n]).find('img');

                if (e.hasClass('loaded')) {
                    e.css('opacity', 1)
                    _a.splice(n, 1)

                    if (!_a.length) {
                        g()
                        L.loaded()
                    }
                }

                clearTimeout(t0)
                t0 = setTimeout(function() {
                    f()
                }, 100)

            }())

            function g() {

                if (!H.length) {
                    return
                }

                var Hn = R(0, H.length),
                    Hl = 'thumbnails/'+ H[Hn][0] +'.jpg',

                    Sn = R(0, S.length),
                    Sl = container.find('li').eq(Sn);

                $('').on('load', function() {

                    Sl.prepend('')

                    $(Sl.find('img')[0]).css('opacity', 1)
                    $(Sl.find('img')[1]).css('opacity', 0)

                    setTimeout(function() {
                        $(Sl.find('img')[1]).remove()
                    }, 1000)

                    H.splice(Hn, 1)
                    H.push(S[Sn])

                    clearTimeout(t1)
                    t1 = setTimeout(function() {
                        g()
                    }, R(1001, 3000))
                })

            }

        }, 1000)

    }).call()

    // window resize
    $(window).on('resize', function() {
        mark.trigger('click')
        clearTimeout(T)

        T = setTimeout(function() {
            if (window.innerWidth != W) {
                $('#line').html('')
                init()
            }
        }, 500)
    })

    // show big cover
    container.on('click', function(e) {
        if (container.data('click') == 1) {
            return
        }

        var target = $(e.target);

        if (parseInt(target.css('opacity')) === 0 || !target.data('u') || !target.attr('src')) {
            return
        }

        container.data('click', 1)

        L.loading()

        var cover = 'covers/'+ target.data('u') +'.jpg';
        $('').on('load', function() {
            mark.addClass('show')
            L.loaded()

            mark.find('.inner').width(target.data('w')).height(target.data('h'))
            mark.find('.img').html($(this))
            mark.find('.bg').css('background-color', target.data('c'))
            mark.find('.info').html('
'+ target.data('m') +'

'+ target.data('r') +'
')
        })
    })

    // close mark
    mark.on('click', function(e) {
        mark.removeClass('show').addClass('loading').find('.img').html('')
        container.data('click', 0)
    })

})
<|endoftext|>


<commit_before>//  -------------------------------------------------------------------

//    // Example of how to save a many-to-many relation to the database
//    // Create CGroup model
//    cgroups = new model.CGroups
//    OPC = cgroups.create({
//        name:'Orthodox Presbyterian Church',
//        abbreviation:'OPC'
//    })
//    // Instantiate group of congs
//    congs = new model.Congs
//    // Instantiate first congregation model
//    // TODO: Should this be global or not?
//    cong1 = congs.create({
//        name:'Caney OPC',
//        mailing_state:'KS'
//    },{
//        success:function(){
//            // Instantiate second congregation model
//            cong2 = congs.create({
//                name:'Bartlesville OPC',
//                mailing_state:'OK'
//            },{success:function(){
//                // Add congregations to cgroup
//                OPC.get('congregations').add([{_id:cong1.get('_id')},{_id:cong2.get('_id')}])
//                // Save cgroup to db
//                OPC.save({_id:OPC.get('_id')},{success:function(){
//                    $.each([cong1,cong2], function(key, cong){
//                        cong.get('cgroups').add({_id:OPC.get('_id')})
//                        cong.save({},{success:function(){
//                            // Example of how to fetch many-to-many relations from the db
//                            // Fetch the cong so as to populate its relations in the browser
//                            cong.fetch({success:function(){
//                                // Example of how to query for related CGroups
//                                var cong_cgroups = cong.get('cgroups')
//                                for (var i=0; i<cong_cgroups.length; i++){
//                                    var cgroup_id = cong_cgroups.at(i).get('_id')
//                                    var cgroup = cgroups.get(cgroup_id)
//                                }
//                                // Example of how to query by one attribute
//                                congs_by_name = new model.CongsByName
//                                congs_by_name.db.keys = ['Caney OPC']
//                                congs_by_name.fetch({success:function(col, res){
//                                    var caney_opc = col.at(0)
//                                }})
//                            }})
//                        }})
//                    })
//                }})
//            }})
//        }
//    })

//  -------------------------------------------------------------------

// Standard AMD RequireJS define
define([
        'config',
        'backbone'
        ], function(config, Backbone){
    // Fill this with your database information.

    // `ddoc_name` is the name of your couchapp project.
    Backbone.couch_connector.config.db_name = "rcl";
    Backbone.couch_connector.config.ddoc_name = "rcl";
    // If set to true, the connector will listen to the changes feed
    //  and will provide your models with real time remote updates.
    Backbone.couch_connector.config.global_changes = true;
    // This setting enables the code/features in this pull request:
    //  https://github.com/janmonschke/backbone-couchdb/pull/25
    Backbone.couch_connector.config.single_feed = true;
    // Reload the page when the design doc changes
    var db = config.db
    changes = db.changes();
    changes.onChange(function(data){
        for (var i=0; i<data.results.length; i++){
            if (data.results[i].id == '_design/rcl'){
                window.location.reload()
            }
        }
    })

    // Define model objects & collections for querying the database

    // Define link objects for many-to-many relations
    CGroup_Cong = Backbone.RelationalModel.extend({})
    CGroup_Person = Backbone.RelationalModel.extend({})
    CGroup_Role = Backbone.RelationalModel.extend({})
    Cong_Person = Backbone.RelationalModel.extend({})
    Office_Person = Backbone.RelationalModel.extend({})
    Person_Role = Backbone.RelationalModel.extend({})

    CGroup = Backbone.RelationalModel.extend({
        collection:'CGroups',
        urlRoot:'/cgroup',
        // All defaults are commented out because they are here only for the purpose
        //  of documenting the schema, and we don't need all these attributes to appear
        //  on every actual instance of a model object.
//        defaults:{
//            name: '',
//            abbreviaton: '',
//            website: ''
//        },
        relations:[
                   {
                       type:'HasMany', // many-to-many
                       key: 'congregations',
                       relatedModel: 'CGroup_Cong',
                       collectionType:'Congs',
                       //includeInJSON:'_id',
                       includeInJSON:true,
                       reverseRelation: {
                           key: 'cgroup'
                       }
                   },
                   {
                       type:'HasMany', // many-to-many
                       key: 'people',
                       relatedModel: 'CGroup_Person',
                       collectionType:'People',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'cgroup',
                           includeInJSON:'_id'
                       }
                   },
                   {
                       type:'HasMany', // many-to-many
                       key: 'roles',
                       relatedModel: 'CGroup_Role',
                       collectionType:'Roles',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'cgroup',
                           includeInJSON:'_id'
                       }
                   },
                   {
                       type:'HasMany', // one-to-many
                       key: 'directories',
                       relatedModel: 'Directory',
                       collectionType:'Directories',
                       includeInJSON:'_id',
                       // reverseRelation: {
                       //     key: 'cgroup',
                       //     includeInJSON:'_id'
                       // }
                   }
                   ]
    })
    CGroups = Backbone.Collection.extend({
        model:CGroup,
        url:'/cgroups'
    })
    CGroupsByAbbrOrName = Backbone.Collection.extend({
        model:CGroup,
        url:'/cgroups',
        db:{
            view: 'cgroups_by_abbreviation_or_name'
        }
    })
    Cong = Backbone.RelationalModel.extend({
      urlRoot:'/cong',
      collection:'Congs',
//      defaults:{
//          name : '',
//          meeting_address1 : '',
//          meeting_address2:'',
//          meeting_city:'',
//          meeting_region:'',
//          meeting_state:'',
//          meeting_zip:'',
//          meeting_country:'',
//          lat:'',
//          lng:'',
//          mailing_address1:'',
//          mailing_address2:'',
//          mailing_city:'',
//          mailing_state:'',
//          mailing_zip:'',
//          mailing_country:'',
//          phone:'',
//          fax:'',
//          email:'',
//          website:'',
//          sermons_url:'',
//          service_info:'',
//          other_info:'',
//          presbytery_name:'',
//          pastor_name:'',
//          contact_type:'',
//          contact_name:'',
//          date_founded:'', // date
//          number_of_members:'', // integer
//          range_of_number_of_members:'', // textual range, like '20-30' members, where estimates are
//                                         //   permitted/preferred or the only available data
//          organized:'', // boolean, defines whether this is a mission work or an organized congregation
//          source:'', // Foreign key:  Which source this cong's data came from
//          source_cong_id:'', // The ID of this cong in the source's database
//      },
        initialize: function(){
            // Make congs save themselves immediately when their attributes change
            //    to make Backgrid more useful
            Backbone.RelationalModel.prototype.initialize.apply(this, arguments);
            this.on("change", function (model, options) {
                if (options && options.save === false) return;
                model.save();
            });
        },
      relations:[
                 {
                     type:'HasMany', // many-to-many
                     key: 'cgroups',
                     relatedModel: 'CGroup_Cong',
                     collectionType:'CGroups',
                     includeInJSON:true,
                     reverseRelation: {
                         key: 'congregation',
                         includeInJSON:'_id'
                     }
                 },
                 {
                     type:'HasMany', // many-to-many
                     key: 'people',
                     relatedModel: 'Cong_Person',
                     collectionType:'People',
                     includeInJSON:'_id',
                     reverseRelation: {
                         key: 'congregation',
                         includeInJSON:'_id'
                     }
                 }
                 ]
    })
    Congs = Backbone.Collection.extend({
        model:Cong,
        url:'/congs'
    })
    CongsByName = Backbone.Collection.extend({
        model:Cong,
        url:'/congs',
        db:{
            view: 'congs_by_name'
        }
    })
    Person = Backbone.RelationalModel.extend({
        urlRoot:'/person',
        collection:'People',
//        defaults: {
//                    prefix:'',
//                    firstname:'',
//                    lastname:'',
//                    suffix:'',
//                    phone:'',
//                    email:''
//        },
        relations:[
                   {
                       type:'HasMany', // many-to-many
                       key: 'congregations',
                       relatedModel: 'Cong_Person',
                       collectionType:'Congs',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'person',
                           includeInJSON:'_id'
                       }
                   },
                   // TODO: create link object
                   {
                       type:'HasMany', // many-to-many
                       key: 'groups',
                       relatedModel: 'CGroup_Person',
                       collectionType:'CGroups',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'person',
                           includeInJSON:'_id'
                       }
                   },
                   // TODO: create link object
                   {
                       type:'HasMany', // many-to-many
                       key: 'roles',
                       relatedModel: 'Person_Role',
                       collectionType:'Roles',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'person',
                           includeInJSON:'_id'
                       }
                   },
                   // TODO: create link object
                   {
                       type:'HasMany', // many-to-many
                       key: 'offices',
                       relatedModel: 'Office_Person',
                       collectionType:'Offices',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'person',
                           includeInJSON:'_id'
                       }
                   }
                   ]
    })
    People = Backbone.Collection.extend({
        model:Person,
        url:'/people'
    })
    Directory = Backbone.RelationalModel.extend({
        collection:'Directories',
        urlRoot:'/directory',
//          defaults:{
//          url:'', // url of directory's main page
//          url_html:'', // HTML of directory's main page
//          get_url_html:'', // '', 'requested', 'getting', or 'gotten'
//          pagetype:'', // html or rss
//          state_page_urls:'', // template URL of state pages
//          state_url_html:'', // HTML of state page
//          state_url_method:'', // 'get' or 'post', tells Node script which to use
//          get_state_url_html:'', // '', 'requested', 'getting', or 'gotten'
//          state_page_values:[], // list of select box options for this directory's states
//          select_element_xpath:'' // xpath of the select element containing state IDs
//        },
        relations:[
            {
               type:'HasOne', // many-to-one
               key: 'cgroup',
               // TODO: the directory's 'cgroup' is null in the db
               relatedModel: 'CGroup', // was 'CGroup_Directory'
               collectionType:'CGroups',
               includeInJSON:'_id',
               // reverseRelation: {
               //     key: 'directories',
               //     // TODO: Is this needed?
               //     includeInJSON:'_id'
               // }
            }
        ],
        db:{
            changes:true
        }

    })
    Directories = Backbone.Collection.extend({
        model:Directory,
        url:'/directories'
    })
    DirectoriesByURL = Backbone.Collection.extend({
        model:Directory,
        url:'/directories',
        db:{
            view: 'directories_by_url'
        }
    })
    Office = Backbone.RelationalModel.extend({
        collection:'Offices',
        urlRoot:'/office',
//        defaults:{
//            name: ""
//        },
        relations:[
                   {
                       type:'HasMany', // many-to-many
                       key: 'people',
                       relatedModel: 'Office_Person',
                       collectionType:'People',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'office',
                           // TODO: Is this needed?
                           includeInJSON:'_id'
                       }
                   }
                   ]
    })
    Offices = Backbone.Collection.extend({
        model:Office,
        url:'/offices'
    })
    Role = Backbone.RelationalModel.extend({
        collection:'Roles',
        urlRoot:'/role',
//        defaults:{
//            name:''
//        },
        relations:[
                   {
                       type:'HasMany', // many-to-many
                       key: 'people',
                       relatedModel: 'Person_Role',
                       collectionType:'People',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'role',
                           // TODO: Is this needed?
                           includeInJSON:'_id'
                       }
                   },
                   {
                       type:'HasMany', // many-to-many
                       key: 'cgroups',
                       relatedModel: 'CGroup_Role',
                       collectionType:'CGroups',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'role',
                           // TODO: Is this needed?
                           includeInJSON:'_id'
                       }
                   }
                   ]
    })
    Roles = Backbone.Collection.extend({
        model:Role,
        url:'/roles'
    })

    // Define convenience functions
    // TODO: Put these convenience functions into a base class/object that can be used as a mixin
    //  in the objects above.
    function get_one(collection, keys, options) {
        var coll = new collection
        coll.db.keys = keys
        coll.fetch({success:function(col, res){
            var model = col.at(0)
            if (typeof(options.success) !== 'undefined'){
                options.success(model)
            }
        }
        })
    }
    function create_one(collection, attrs_obj, options){
        var coll = new collection
        var model = coll.create(attrs_obj, {success:function(model){
            if (typeof(options.success) !== 'undefined'){
                options.success(model)
            }
        }})
    }
    function get_or_create_one(coll, search_keys, attrs, options){
        get_one(coll,
              search_keys,
              {success:function(doc){
                  if (typeof(doc) === 'undefined'){
                      // The doc didn't exist in the db, so create it
                      create_one(coll,
                               attrs,
                               {success:function(doc){
                                   if (typeof(options.success) !== 'undefined'){
                                       options.success(doc)
                                   }
                               }}
                      )
                  }else{
                      // The doc did exist in the db, so use it
                      if (typeof(options.success) !== 'undefined'){
                          options.success(doc)
                      }
                  }
              }}
        )
    }

    return {
        // link object models
        CGroup_Cong: CGroup_Cong,
        Cong_Person: Cong_Person,
        CGroup_Person: CGroup_Person,
        CGroup_Role: CGroup_Role,
        Office_Person: Office_Person,
        Person_Role: Person_Role,
        // regular object models
        CGroup: CGroup,
        Cong: Cong,
        Directory: Directory,
        Directories: Directories,
        Person: Person,
        Office: Office,
        Role: Role,
        // collections
        CGroups:CGroups,
        CGroupsByAbbrOrName:CGroupsByAbbrOrName,
        Congs:Congs,
        CongsByName: CongsByName,
        Directories:Directories,
        DirectoriesByURL:DirectoriesByURL,
        People:People,
        Offices:Offices,
        Roles:Roles,
        // Convenience functions
        get_one:get_one,
        create_one:create_one,
        get_or_create_one:get_or_create_one
    }
})
<commit_msg>Get directory model object to use correct CouchDB view<commit_after>//  -------------------------------------------------------------------

//    // Example of how to save a many-to-many relation to the database
//    // Create CGroup model
//    cgroups = new model.CGroups
//    OPC = cgroups.create({
//        name:'Orthodox Presbyterian Church',
//        abbreviation:'OPC'
//    })
//    // Instantiate group of congs
//    congs = new model.Congs
//    // Instantiate first congregation model
//    // TODO: Should this be global or not?
//    cong1 = congs.create({
//        name:'Caney OPC',
//        mailing_state:'KS'
//    },{
//        success:function(){
//            // Instantiate second congregation model
//            cong2 = congs.create({
//                name:'Bartlesville OPC',
//                mailing_state:'OK'
//            },{success:function(){
//                // Add congregations to cgroup
//                OPC.get('congregations').add([{_id:cong1.get('_id')},{_id:cong2.get('_id')}])
//                // Save cgroup to db
//                OPC.save({_id:OPC.get('_id')},{success:function(){
//                    $.each([cong1,cong2], function(key, cong){
//                        cong.get('cgroups').add({_id:OPC.get('_id')})
//                        cong.save({},{success:function(){
//                            // Example of how to fetch many-to-many relations from the db
//                            // Fetch the cong so as to populate its relations in the browser
//                            cong.fetch({success:function(){
//                                // Example of how to query for related CGroups
//                                var cong_cgroups = cong.get('cgroups')
//                                for (var i=0; i<cong_cgroups.length; i++){
//                                    var cgroup_id = cong_cgroups.at(i).get('_id')
//                                    var cgroup = cgroups.get(cgroup_id)
//                                }
//                                // Example of how to query by one attribute
//                                congs_by_name = new model.CongsByName
//                                congs_by_name.db.keys = ['Caney OPC']
//                                congs_by_name.fetch({success:function(col, res){
//                                    var caney_opc = col.at(0)
//                                }})
//                            }})
//                        }})
//                    })
//                }})
//            }})
//        }
//    })

//  -------------------------------------------------------------------

// Standard AMD RequireJS define
define([
        'config',
        'backbone'
        ], function(config, Backbone){
    // Fill this with your database information.

    // `ddoc_name` is the name of your couchapp project.
    Backbone.couch_connector.config.db_name = "rcl";
    Backbone.couch_connector.config.ddoc_name = "rcl";
    // If set to true, the connector will listen to the changes feed
    //  and will provide your models with real time remote updates.
    Backbone.couch_connector.config.global_changes = true;
    // This setting enables the code/features in this pull request:
    //  https://github.com/janmonschke/backbone-couchdb/pull/25
    Backbone.couch_connector.config.single_feed = true;
    // Reload the page when the design doc changes
    var db = config.db
    changes = db.changes();
    changes.onChange(function(data){
        for (var i=0; i<data.results.length; i++){
            if (data.results[i].id == '_design/rcl'){
                window.location.reload()
            }
        }
    })

    // Define model objects & collections for querying the database

    // Define link objects for many-to-many relations
    CGroup_Cong = Backbone.RelationalModel.extend({})
    CGroup_Person = Backbone.RelationalModel.extend({})
    CGroup_Role = Backbone.RelationalModel.extend({})
    Cong_Person = Backbone.RelationalModel.extend({})
    Office_Person = Backbone.RelationalModel.extend({})
    Person_Role = Backbone.RelationalModel.extend({})

    CGroup = Backbone.RelationalModel.extend({
        collection:'CGroups',
        urlRoot:'/cgroup',
        // All defaults are commented out because they are here only for the purpose
        //  of documenting the schema, and we don't need all these attributes to appear
        //  on every actual instance of a model object.
//        defaults:{
//            name: '',
//            abbreviaton: '',
//            website: ''
//        },
        relations:[
                   {
                       type:'HasMany', // many-to-many
                       key: 'congregations',
                       relatedModel: 'CGroup_Cong',
                       collectionType:'Congs',
                       //includeInJSON:'_id',
                       includeInJSON:true,
                       reverseRelation: {
                           key: 'cgroup'
                       }
                   },
                   {
                       type:'HasMany', // many-to-many
                       key: 'people',
                       relatedModel: 'CGroup_Person',
                       collectionType:'People',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'cgroup',
                           includeInJSON:'_id'
                       }
                   },
                   {
                       type:'HasMany', // many-to-many
                       key: 'roles',
                       relatedModel: 'CGroup_Role',
                       collectionType:'Roles',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'cgroup',
                           includeInJSON:'_id'
                       }
                   },
                   {
                       type:'HasMany', // one-to-many
                       key: 'directories',
                       relatedModel: 'Directory',
                       collectionType:'Directories',
                       includeInJSON:'_id',
                       // reverseRelation: {
                       //     key: 'cgroup',
                       //     includeInJSON:'_id'
                       // }
                   }
                   ]
    })
    CGroups = Backbone.Collection.extend({
        model:CGroup,
        url:'/cgroups'
    })
    CGroupsByAbbrOrName = Backbone.Collection.extend({
        model:CGroup,
        url:'/cgroups',
        db:{
            view: 'cgroups_by_abbreviation_or_name'
        }
    })
    Cong = Backbone.RelationalModel.extend({
      urlRoot:'/cong',
      collection:'Congs',
//      defaults:{
//          name : '',
//          meeting_address1 : '',
//          meeting_address2:'',
//          meeting_city:'',
//          meeting_region:'',
//          meeting_state:'',
//          meeting_zip:'',
//          meeting_country:'',
//          lat:'',
//          lng:'',
//          mailing_address1:'',
//          mailing_address2:'',
//          mailing_city:'',
//          mailing_state:'',
//          mailing_zip:'',
//          mailing_country:'',
//          phone:'',
//          fax:'',
//          email:'',
//          website:'',
//          sermons_url:'',
//          service_info:'',
//          other_info:'',
//          presbytery_name:'',
//          pastor_name:'',
//          contact_type:'',
//          contact_name:'',
//          date_founded:'', // date
//          number_of_members:'', // integer
//          range_of_number_of_members:'', // textual range, like '20-30' members, where estimates are
//                                         //   permitted/preferred or the only available data
//          organized:'', // boolean, defines whether this is a mission work or an organized congregation
//          source:'', // Foreign key:  Which source this cong's data came from
//          source_cong_id:'', // The ID of this cong in the source's database
//      },
        initialize: function(){
            // Make congs save themselves immediately when their attributes change
            //    to make Backgrid more useful
            Backbone.RelationalModel.prototype.initialize.apply(this, arguments);
            this.on("change", function (model, options) {
                if (options && options.save === false) return;
                model.save();
            });
        },
      relations:[
                 {
                     type:'HasMany', // many-to-many
                     key: 'cgroups',
                     relatedModel: 'CGroup_Cong',
                     collectionType:'CGroups',
                     includeInJSON:true,
                     reverseRelation: {
                         key: 'congregation',
                         includeInJSON:'_id'
                     }
                 },
                 {
                     type:'HasMany', // many-to-many
                     key: 'people',
                     relatedModel: 'Cong_Person',
                     collectionType:'People',
                     includeInJSON:'_id',
                     reverseRelation: {
                         key: 'congregation',
                         includeInJSON:'_id'
                     }
                 }
                 ]
    })
    Congs = Backbone.Collection.extend({
        model:Cong,
        url:'/congs'
    })
    CongsByName = Backbone.Collection.extend({
        model:Cong,
        url:'/congs',
        db:{
            view: 'congs_by_name'
        }
    })
    Directory = Backbone.RelationalModel.extend({
        collection:'Directories',
        urlRoot:'/directory',
//          defaults:{
//          url:'', // url of directory's main page
//          url_html:'', // HTML of directory's main page
//          get_url_html:'', // '', 'requested', 'getting', or 'gotten'
//          pagetype:'', // html or rss
//          state_page_urls:'', // template URL of state pages
//          state_url_html:'', // HTML of state page
//          state_url_method:'', // 'get' or 'post', tells Node script which to use
//          get_state_url_html:'', // '', 'requested', 'getting', or 'gotten'
//          state_page_values:[], // list of select box options for this directory's states
//          select_element_xpath:'' // xpath of the select element containing state IDs
//        },
        relations:[
            {
               type:'HasOne', // many-to-one
               key: 'cgroup',
               // TODO: the directory's 'cgroup' is null in the db
               relatedModel: 'CGroup', // was 'CGroup_Directory'
               collectionType:'CGroups',
               includeInJSON:'_id',
               // reverseRelation: {
               //     key: 'directories',
               //     // TODO: Is this needed?
               //     includeInJSON:'_id'
               // }
            }
        ],
        db:{
            changes:true
        }
    })
    Directories = Backbone.Collection.extend({
        model:Directory,
        url:'/directory'
    })
    DirectoriesByURL = Backbone.Collection.extend({
        model:Directory,
        url:'/directory',
        db:{
            view: 'directories_by_url'
        }
    })
    Person = Backbone.RelationalModel.extend({
        urlRoot:'/person',
        collection:'People',
//        defaults: {
//                    prefix:'',
//                    firstname:'',
//                    lastname:'',
//                    suffix:'',
//                    phone:'',
//                    email:''
//        },
        relations:[
                   {
                       type:'HasMany', // many-to-many
                       key: 'congregations',
                       relatedModel: 'Cong_Person',
                       collectionType:'Congs',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'person',
                           includeInJSON:'_id'
                       }
                   },
                   // TODO: create link object
                   {
                       type:'HasMany', // many-to-many
                       key: 'groups',
                       relatedModel: 'CGroup_Person',
                       collectionType:'CGroups',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'person',
                           includeInJSON:'_id'
                       }
                   },
                   // TODO: create link object
                   {
                       type:'HasMany', // many-to-many
                       key: 'roles',
                       relatedModel: 'Person_Role',
                       collectionType:'Roles',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'person',
                           includeInJSON:'_id'
                       }
                   },
                   // TODO: create link object
                   {
                       type:'HasMany', // many-to-many
                       key: 'offices',
                       relatedModel: 'Office_Person',
                       collectionType:'Offices',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'person',
                           includeInJSON:'_id'
                       }
                   }
                   ]
    })
    People = Backbone.Collection.extend({
        model:Person,
        url:'/people'
    })
    Office = Backbone.RelationalModel.extend({
        collection:'Offices',
        urlRoot:'/office',
//        defaults:{
//            name: ""
//        },
        relations:[
                   {
                       type:'HasMany', // many-to-many
                       key: 'people',
                       relatedModel: 'Office_Person',
                       collectionType:'People',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'office',
                           // TODO: Is this needed?
                           includeInJSON:'_id'
                       }
                   }
                   ]
    })
    Offices = Backbone.Collection.extend({
        model:Office,
        url:'/offices'
    })
    Role = Backbone.RelationalModel.extend({
        collection:'Roles',
        urlRoot:'/role',
//        defaults:{
//            name:''
//        },
        relations:[
                   {
                       type:'HasMany', // many-to-many
                       key: 'people',
                       relatedModel: 'Person_Role',
                       collectionType:'People',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'role',
                           // TODO: Is this needed?
                           includeInJSON:'_id'
                       }
                   },
                   {
                       type:'HasMany', // many-to-many
                       key: 'cgroups',
                       relatedModel: 'CGroup_Role',
                       collectionType:'CGroups',
                       includeInJSON:'_id',
                       reverseRelation: {
                           key: 'role',
                           // TODO: Is this needed?
                           includeInJSON:'_id'
                       }
                   }
                   ]
    })
    Roles = Backbone.Collection.extend({
        model:Role,
        url:'/roles'
    })

    // Define convenience functions
    // TODO: Put these convenience functions into a base class/object that can be used as a mixin
    //  in the objects above.
    function get_one(collection, keys, options) {
        var coll = new collection
        coll.db.keys = keys
        coll.fetch({success:function(col, res){
            var model = col.at(0)
            if (typeof(options.success) !== 'undefined'){
                options.success(model)
            }
        }
        })
    }
    function create_one(collection, attrs_obj, options){
        var coll = new collection
        var model = coll.create(attrs_obj, {success:function(model){
            if (typeof(options.success) !== 'undefined'){
                options.success(model)
            }
        }})
    }
    function get_or_create_one(coll, search_keys, attrs, options){
        get_one(coll,
              search_keys,
              {success:function(doc){
                  if (typeof(doc) === 'undefined'){
                      // The doc didn't exist in the db, so create it
                      create_one(coll,
                               attrs,
                               {success:function(doc){
                                   if (typeof(options.success) !== 'undefined'){
                                       options.success(doc)
                                   }
                               }}
                      )
                  }else{
                      // The doc did exist in the db, so use it
                      if (typeof(options.success) !== 'undefined'){
                          options.success(doc)
                      }
                  }
              }}
        )
    }

    return {
        // link object models
        CGroup_Cong: CGroup_Cong,
        Cong_Person: Cong_Person,
        CGroup_Person: CGroup_Person,
        CGroup_Role: CGroup_Role,
        Office_Person: Office_Person,
        Person_Role: Person_Role,
        // regular object models
        CGroup: CGroup,
        Cong: Cong,
        Directory: Directory,
        Directories: Directories,
        Person: Person,
        Office: Office,
        Role: Role,
        // collections
        CGroups:CGroups,
        CGroupsByAbbrOrName:CGroupsByAbbrOrName,
        Congs:Congs,
        CongsByName: CongsByName,
        Directories:Directories,
        DirectoriesByURL:DirectoriesByURL,
        People:People,
        Offices:Offices,
        Roles:Roles,
        // Convenience functions
        get_one:get_one,
        create_one:create_one,
        get_or_create_one:get_or_create_one
    }
})
<|endoftext|>


<commit_before>/*
 * Some portions extracted from:
 * Parse JavaScript SDK — Version: 1.4.2
 *
 */

import Ember from 'ember';
import DS from 'ember-data';

var get = Ember.get,
    forEach = Ember.ArrayPolyfills.forEach;

export default DS.RESTAdapter.extend({
  PARSE_APPLICATION_ID: null,
  PARSE_JAVASCRIPT_KEY: null,

  host: 'https://api.parse.com',
  namespace: '1',
  classesPath: 'classes',
  parseClientVersion: 'js1.4.2',

  init() {
    this._super();

    this.set('applicationId', this.get('PARSE_APPLICATION_ID'));
    this.set('javascriptKey', this.get('PARSE_JAVASCRIPT_KEY'));
    this.set('installationId', this._getInstallationId());
    this.set('sessionToken', null);
    this.set('userId', null);

    /*
     * avoid pre-flight.
     * Parse._ajax
     */
    this.set('headers', { 'Content-Type': 'text/plain' });
  },

  _getInstallationId() {
    /*
     * Parse._getInstallationId
     */
    var hexOctet = function() {
      return (
        Math.floor((1+Math.random())*0x10000).toString(16).substring(1)
      );
    };

    return (
      hexOctet() + hexOctet() + "-" +
      hexOctet() + "-" +
      hexOctet() + "-" +
      hexOctet() + "-" +
      hexOctet() + hexOctet() + hexOctet());
  },

  ajaxOptions(url, type, options) {
    var hash = options || {};
    hash.data = hash.data || {};
    hash.url = url;
    hash.type = type;
    hash.dataType = 'json';
    hash.context = this;

    if ((hash.data && type !== 'GET')) {
      hash.contentType = 'application/json; charset=utf-8';

      // Parse auth stuff
      hash.data._ClientVersion = this.get('parseClientVersion');
      hash.data._ApplicationId = this.get('applicationId');
      hash.data._JavaScriptKey = this.get('javascriptKey');
      hash.data._InstallationId = this.get('installationId');

      var _sessionToken = this.get('sessionToken');
      if (_sessionToken) {
        hash.data._SessionToken = _sessionToken;
      }

      hash.data = JSON.stringify(hash.data);
    }

    var headers = get(this, 'headers');
    if (headers !== undefined) {
      hash.beforeSend = function (xhr) {
        forEach.call(Ember.keys(headers), function(key) {
          xhr.setRequestHeader(key, headers[key]);
        });
      };
    }

    return hash;
  },

  ajaxError(jqXHR, responseText, errorThrown) {
    if (jqXHR.responseJSON.error === 'invalid session token') {
      // invalid session
      var session = this.container.lookup('service:session');
      session.resetSession();
    }

    return this._super(jqXHR, responseText, errorThrown);
  },

  pathForType(type) {
    if ('user' === type) {
      return 'users';

    } else if ('login' === type) {
      return type;

    } else if ('logout' === type) {
      return type;

    } else if ('requestPasswordReset' === type) {
      return type;

    } else if ('functions' === type) {
      return 'functions';

    } else {
      return this.classesPath + '/' + this.parsePathForType(type);
    }
  },

  // Using TitleStyle is recommended by Parse
  parsePathForType(type) {
    return Ember.String.capitalize(Ember.String.camelize(type));
  },

  parseClassName(key) {
    return Ember.String.capitalize(key);
  },

  /**
  * Because Parse doesn't return a full set of properties on the
  * responses to updates, we want to perform a merge of the response
  * properties onto existing data so that the record maintains
  * latest data.
  */
  createRecord(store, type, record) {
    var serializer = store.serializerFor(type.modelName),
      data = { _method: 'POST' },
      adapter = this;

    serializer.serializeIntoHash(data, type, record, { includeId: true });

    var promise = new Ember.RSVP.Promise(function(resolve, reject) {
      adapter.ajax(adapter.buildURL(type.modelName), 'POST', { data: data })
        .then(function(json) {
          var completed = Ember.merge(data, json);
          resolve(completed);
        }, function(reason) {
          var err = `Code ${reason.responseJSON.code}: ${reason.responseJSON.error}`;
          reject(new Error(err));
        });
    });

    return promise;
  },

  updateRecord(store, type, snapshot) {
    var data = { _method: 'PUT' },
        id = snapshot.id,
        serializer = store.serializerFor(type.modelName);

    serializer.serializeIntoHash(data, type, snapshot);

    // debugger;
    // snapshot.record._relationships.friends.members
    // snapshot.record._relationships.friends.canonicalMembers
    return this.ajax(this.buildURL(type.modelName, id, snapshot), 'POST', { data: data });
  },

  deleteRecord(store, type, snapshot) {
    var data = { _method: 'DELETE' },
        id = snapshot.id;

    return this.ajax(this.buildURL(type.modelName, id, snapshot), 'POST', { data: data });
  },

  findRecord(store, type, id, snapshot) {
    var data = { _method: 'GET' };
    return this.ajax(this.buildURL(type.modelName, id, snapshot), 'POST', { data: data });
  },

  findAll(store, type, sinceToken) {
    var data = { _method: 'GET' };

    if (sinceToken) {
      data.since = sinceToken;
    }

    data.where = {};

    return this.ajax(this.buildURL(type.modelName), 'POST', { data: data });
  },

  /**
  * Implementation of a hasMany that provides a Relation query for Parse
  * objects.
  */
  findHasMany(store, record, relationship) {
    var related = JSON.parse(relationship);

    var query = {
      where: {
        '$relatedTo': {
          'object': {
            '__type': 'Pointer',
            'className': this.parseClassName(record.modelName),
            'objectId': record.id
          },
          key: related.key
        }
      },
      _method: 'GET'
    };

    // the request is to the related type and not the type for the record.
    // the query is where there is a pointer to this record.
    return this.ajax(
              this.buildURL(related.className), 'POST', { data: query });
  },

  /**
  * Implementation of findQuery that automatically wraps query in a
  * JSON string.
  *
  * @example
  *     this.store.find('comment', {
  *       where: {
  *         post: {
  *             "__type":  "Pointer",
  *             "className": "Post",
  *             "objectId": post.get('id')
  *         }
  *       }
  *     });
  */
  findQuery(store, type, query) {
    query._method = 'GET';
    return this.ajax(this.buildURL(type.modelName), 'POST', { data: query });
  },

  shouldReloadAll() {
    return false;
  },

  shouldBackgroundReloadRecord() {
    return false;
  }
});
<commit_msg>Normalize Parse errore responses<commit_after>/*
 * Some portions extracted from:
 * Parse JavaScript SDK — Version: 1.4.2
 *
 */

import Ember from 'ember';
import DS from 'ember-data';

var get = Ember.get,
    forEach = Ember.ArrayPolyfills.forEach;

export default DS.RESTAdapter.extend({
  PARSE_APPLICATION_ID: null,
  PARSE_JAVASCRIPT_KEY: null,

  host: 'https://api.parse.com',
  namespace: '1',
  classesPath: 'classes',
  parseClientVersion: 'js1.4.2',

  init() {
    this._super();

    this.set('applicationId', this.get('PARSE_APPLICATION_ID'));
    this.set('javascriptKey', this.get('PARSE_JAVASCRIPT_KEY'));
    this.set('installationId', this._getInstallationId());
    this.set('sessionToken', null);
    this.set('userId', null);

    /*
     * avoid pre-flight.
     * Parse._ajax
     */
    this.set('headers', { 'Content-Type': 'text/plain' });
  },

  _getInstallationId() {
    /*
     * Parse._getInstallationId
     */
    var hexOctet = function() {
      return (
        Math.floor((1+Math.random())*0x10000).toString(16).substring(1)
      );
    };

    return (
      hexOctet() + hexOctet() + "-" +
      hexOctet() + "-" +
      hexOctet() + "-" +
      hexOctet() + "-" +
      hexOctet() + hexOctet() + hexOctet());
  },

  ajaxOptions(url, type, options) {
    var hash = options || {};
    hash.data = hash.data || {};
    hash.url = url;
    hash.type = type;
    hash.dataType = 'json';
    hash.context = this;

    if ((hash.data && type !== 'GET')) {
      hash.contentType = 'application/json; charset=utf-8';

      // Parse auth stuff
      hash.data._ClientVersion = this.get('parseClientVersion');
      hash.data._ApplicationId = this.get('applicationId');
      hash.data._JavaScriptKey = this.get('javascriptKey');
      hash.data._InstallationId = this.get('installationId');

      var _sessionToken = this.get('sessionToken');
      if (_sessionToken) {
        hash.data._SessionToken = _sessionToken;
      }

      hash.data = JSON.stringify(hash.data);
    }

    var headers = get(this, 'headers');
    if (headers !== undefined) {
      hash.beforeSend = function (xhr) {
        forEach.call(Ember.keys(headers), function(key) {
          xhr.setRequestHeader(key, headers[key]);
        });
      };
    }

    return hash;
  },

  ajaxError(jqXHR, responseText, errorThrown) {
    if (jqXHR.responseJSON.error === 'invalid session token') {
      // invalid session
      var session = this.container.lookup('service:session');
      session.resetSession();
    }

    return this._super(jqXHR, responseText, errorThrown);
  },

  normalizeErrorResponse: function(status, headers, payload) {
    return [
      {
        status: `${status}`,
        title: 'The backend responded with an error',
        details: payload.error,
        code: payload.code
      }
    ];
  },

  pathForType(type) {
    if ('user' === type) {
      return 'users';

    } else if ('login' === type) {
      return type;

    } else if ('logout' === type) {
      return type;

    } else if ('requestPasswordReset' === type) {
      return type;

    } else if ('functions' === type) {
      return 'functions';

    } else {
      return this.classesPath + '/' + this.parsePathForType(type);
    }
  },

  // Using TitleStyle is recommended by Parse
  parsePathForType(type) {
    return Ember.String.capitalize(Ember.String.camelize(type));
  },

  parseClassName(key) {
    return Ember.String.capitalize(key);
  },

  /**
  * Because Parse doesn't return a full set of properties on the
  * responses to updates, we want to perform a merge of the response
  * properties onto existing data so that the record maintains
  * latest data.
  */
  createRecord(store, type, record) {
    var serializer = store.serializerFor(type.modelName),
      data = { _method: 'POST' },
      adapter = this;

    serializer.serializeIntoHash(data, type, record, { includeId: true });

    var promise = new Ember.RSVP.Promise(function(resolve, reject) {
      adapter.ajax(adapter.buildURL(type.modelName), 'POST', { data: data })
        .then(function(json) {
          var completed = Ember.merge(data, json);
          resolve(completed);
        }, function(reason) {
          var err = `Code ${reason.responseJSON.code}: ${reason.responseJSON.error}`;
          reject(new Error(err));
        });
    });

    return promise;
  },

  updateRecord(store, type, snapshot) {
    var data = { _method: 'PUT' },
        id = snapshot.id,
        serializer = store.serializerFor(type.modelName);

    serializer.serializeIntoHash(data, type, snapshot);

    // debugger;
    // snapshot.record._relationships.friends.members
    // snapshot.record._relationships.friends.canonicalMembers
    return this.ajax(this.buildURL(type.modelName, id, snapshot), 'POST', { data: data });
  },

  deleteRecord(store, type, snapshot) {
    var data = { _method: 'DELETE' },
        id = snapshot.id;

    return this.ajax(this.buildURL(type.modelName, id, snapshot), 'POST', { data: data });
  },

  findRecord(store, type, id, snapshot) {
    var data = { _method: 'GET' };
    return this.ajax(this.buildURL(type.modelName, id, snapshot), 'POST', { data: data });
  },

  findAll(store, type, sinceToken) {
    var data = { _method: 'GET' };

    if (sinceToken) {
      data.since = sinceToken;
    }

    data.where = {};

    return this.ajax(this.buildURL(type.modelName), 'POST', { data: data });
  },

  /**
  * Implementation of a hasMany that provides a Relation query for Parse
  * objects.
  */
  findHasMany(store, record, relationship) {
    var related = JSON.parse(relationship);

    var query = {
      where: {
        '$relatedTo': {
          'object': {
            '__type': 'Pointer',
            'className': this.parseClassName(record.modelName),
            'objectId': record.id
          },
          key: related.key
        }
      },
      _method: 'GET'
    };

    // the request is to the related type and not the type for the record.
    // the query is where there is a pointer to this record.
    return this.ajax(
              this.buildURL(related.className), 'POST', { data: query });
  },

  /**
  * Implementation of findQuery that automatically wraps query in a
  * JSON string.
  *
  * @example
  *     this.store.find('comment', {
  *       where: {
  *         post: {
  *             "__type":  "Pointer",
  *             "className": "Post",
  *             "objectId": post.get('id')
  *         }
  *       }
  *     });
  */
  findQuery(store, type, query) {
    query._method = 'GET';
    return this.ajax(this.buildURL(type.modelName), 'POST', { data: query });
  },

  shouldReloadAll() {
    return false;
  },

  shouldBackgroundReloadRecord() {
    return false;
  }
});
<|endoftext|>