<commit_before>const blobToDataUrl = (blob) => {
    return new Promise((resolve, reject) => {
        const fr = new FileReader();
        fr.onload = (e) => {
            resolve(e.target.result);
        };
        fr.onerror = (e) => {
            reject(e);
        };
        fr.readAsDataURL(blob);
    });
};

const blobToCanvas = async (blob) => {
    const url = await blobToDataUrl(blob);

    const img = new Image();
    await new Promise((resolve, reject) => {
        img.onload = resolve;
        img.onerror = reject;
        img.src = url;
    });

    const $canvas = document.createElement('canvas');
    $canvas.width = img.width;
    $canvas.height = img.height;
    const ctx = $canvas.getContext('2d');
    ctx.drawImage(img, 0, 0);

    return $canvas;
};

const canvasToBlob = (canvas, { type = 'image/webp', quality = 0.8 }) => {
    return new Promise((resolve, reject) => {
        try {
            canvas.toBlob(
                (blob) => {
                    if (blob) {
                        resolve(blob);
                    } else {
                        reject();
                    }
                },
                type,
                quality
            );
        } catch (e) {
            reject(e);
        }
    });
};

const exportCanvasSized = async (
    origCanvas,
    fileName,
    maxFileSize,
    type,
    quality,
    isIOS
) => {
    let tempCanvas;
    let scale = 1;
    let step = 1;
    let lastFitBlob = null;

    if (isIOS) {
        scale = Math.min(1200 / origCanvas.width, 1200 / origCanvas.height, 1);
    }

    for (let i = 0; i < 10; i += 1) {
        await new Promise((resolve) => {
            setTimeout(resolve, 10);
        });

        if (typeof document !== 'undefined') {
            if (!tempCanvas) {
                tempCanvas = document.createElement('canvas');
                // eslint-disable-next-line no-loop-func
                tempCanvas.convertToBlob = (options) =>
                    canvasToBlob(tempCanvas, options);
            }
            tempCanvas.width = origCanvas.width * scale;
            tempCanvas.height = origCanvas.height * scale;
        } else if (typeof OffscreenCanvas === 'function') {
            postMessage({
                width: Math.round(origCanvas.width * scale),
                height: Math.round(origCanvas.height * scale),
                scale,
            });
            tempCanvas = new OffscreenCanvas(
                Math.round(origCanvas.width * scale),
                Math.round(origCanvas.height * scale)
            );
        } else {
            throw Error('offscreen canvas not supported');
        }

        if (isIOS) {
            if (tempCanvas.width > 1200 || tempCanvas.height > 1200) break;
        }

        tempCanvas
            .getContext('2d')
            .drawImage(
                origCanvas,
                0,
                0,
                origCanvas.width,
                origCanvas.height,
                0,
                0,
                tempCanvas.width,
                tempCanvas.height
            );

        // eslint-disable-next-line no-await-in-loop
        const blob = await tempCanvas.convertToBlob({ type, quality });
        const blobSize = blob?.size ?? maxFileSize + 1;
        if (blobSize > maxFileSize) {
            step *= 0.5;
            scale -= step;
        } else if (blobSize < maxFileSize) {
            lastFitBlob = blob;

            if (scale === 1) {
                break;
            }

            if (blobSize > maxFileSize * 0.8) {
                break;
            }

            step *= 0.5;
            scale += step;
        }
    }

    if (!lastFitBlob) {
        throw new Error(
            'failed to compress image down to maxFileSize within 10 iterations'
        );
    }

    return new File([lastFitBlob], fileName, { type });
};

const workerThread = () => {
    // eslint-disable-next-line no-restricted-globals
    self.onmessage = async (ev) => {
        const { file, maxFileSize, quality, isIOS } = ev.data;

        const bitmap = await createImageBitmap(file);
        const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
        const ctx = canvas.getContext('2d');
        ctx.drawImage(bitmap, 0, 0);

        const type = file.type === 'image/jpg' ? 'image/jpg' : 'image/webp';

        try {
            const result = await exportCanvasSized(
                canvas,
                file.name,
                maxFileSize,
                type,
                quality,
                isIOS
            );
            postMessage({ success: true, file: result });
        } catch (e) {
            postMessage({ success: false, error: e });
        }
    };
};

const compressImage = async (file, maxFileSize, quality = 0.8) => {
    if (typeof OffscreenCanvas !== 'function') {
        const origCanvas = await blobToCanvas(file);
        const type = file.type === 'image/jpg' ? 'image/jpg' : 'image/webp';
        return exportCanvasSized(
            origCanvas,
            file.name,
            maxFileSize,
            type,
            quality,
            chayns.env.isIOS
        );
    }

    let workerScript = [exportCanvasSized]
        .map((f) => `const ${f.name} = ${f.toString()}`)
        .join('\n');

    workerScript += `\n(${workerThread.toString()})()`;

    const blob = new Blob([workerScript], { type: 'application/javascript' });
    const url = URL.createObjectURL(blob);

    return new Promise((resolve, reject) => {
        const worker = new Worker(url);
        worker.onmessage = (ev) => {
            if (typeof ev.data.success !== 'boolean') return;
            if (ev.data.success) {
                resolve(ev.data.file);
            } else {
                reject(ev.data.error);
            }
            worker.terminate();
        };

        worker.postMessage({
            file,
            maxFileSize,
            quality,
            isIOS: chayns.env.isIOS,
        });
    });
};

export default compressImage;
<commit_msg>:rotating_light: Fix eslint warnings<commit_after>const blobToDataUrl = (blob) =>
    new Promise((resolve, reject) => {
        const fr = new FileReader();
        fr.onload = (e) => {
            resolve(e.target.result);
        };
        fr.onerror = (e) => {
            reject(e);
        };
        fr.readAsDataURL(blob);
    });

const blobToCanvas = async (blob) => {
    const url = await blobToDataUrl(blob);

    const img = new Image();
    await new Promise((resolve, reject) => {
        img.onload = resolve;
        img.onerror = reject;
        img.src = url;
    });

    const $canvas = document.createElement('canvas');
    $canvas.width = img.width;
    $canvas.height = img.height;
    const ctx = $canvas.getContext('2d');
    ctx.drawImage(img, 0, 0);

    return $canvas;
};

const canvasToBlob = (canvas, { type = 'image/webp', quality = 0.8 }) => {
    return new Promise((resolve, reject) => {
        try {
            canvas.toBlob(
                (blob) => {
                    if (blob) {
                        resolve(blob);
                    } else {
                        reject();
                    }
                },
                type,
                quality
            );
        } catch (e) {
            reject(e);
        }
    });
};

const exportCanvasSized = async (
    origCanvas,
    fileName,
    maxFileSize,
    type,
    quality,
    isIOS
) => {
    let tempCanvas;
    let scale = 1;
    let step = 1;
    let lastFitBlob = null;

    if (isIOS) {
        scale = Math.min(1200 / origCanvas.width, 1200 / origCanvas.height, 1);
    }

    for (let i = 0; i < 10; i += 1) {
        // eslint-disable-next-line no-await-in-loop
        await new Promise((resolve) => {
            setTimeout(resolve, 10);
        });

        if (typeof document !== 'undefined') {
            if (!tempCanvas) {
                tempCanvas = document.createElement('canvas');
                // eslint-disable-next-line no-loop-func
                tempCanvas.convertToBlob = (options) =>
                    canvasToBlob(tempCanvas, options);
            }
            tempCanvas.width = origCanvas.width * scale;
            tempCanvas.height = origCanvas.height * scale;
        } else if (typeof OffscreenCanvas === 'function') {
            postMessage({
                width: Math.round(origCanvas.width * scale),
                height: Math.round(origCanvas.height * scale),
                scale,
            });
            tempCanvas = new OffscreenCanvas(
                Math.round(origCanvas.width * scale),
                Math.round(origCanvas.height * scale)
            );
        } else {
            throw Error('offscreen canvas not supported');
        }

        if (isIOS) {
            if (tempCanvas.width > 1200 || tempCanvas.height > 1200) break;
        }

        tempCanvas
            .getContext('2d')
            .drawImage(
                origCanvas,
                0,
                0,
                origCanvas.width,
                origCanvas.height,
                0,
                0,
                tempCanvas.width,
                tempCanvas.height
            );

        // eslint-disable-next-line no-await-in-loop
        const blob = await tempCanvas.convertToBlob({ type, quality });
        const blobSize = blob?.size ?? maxFileSize + 1;
        if (blobSize > maxFileSize) {
            step *= 0.5;
            scale -= step;
        } else if (blobSize < maxFileSize) {
            lastFitBlob = blob;

            if (scale === 1) {
                break;
            }

            if (blobSize > maxFileSize * 0.8) {
                break;
            }

            step *= 0.5;
            scale += step;
        }
    }

    if (!lastFitBlob) {
        throw new Error(
            'failed to compress image down to maxFileSize within 10 iterations'
        );
    }

    return new File([lastFitBlob], fileName, { type });
};

const workerThread = () => {
    // eslint-disable-next-line no-restricted-globals
    self.onmessage = async (ev) => {
        const { file, maxFileSize, quality, isIOS } = ev.data;

        const bitmap = await createImageBitmap(file);
        const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
        const ctx = canvas.getContext('2d');
        ctx.drawImage(bitmap, 0, 0);

        const type = file.type === 'image/jpg' ? 'image/jpg' : 'image/webp';

        try {
            const result = await exportCanvasSized(
                canvas,
                file.name,
                maxFileSize,
                type,
                quality,
                isIOS
            );
            postMessage({ success: true, file: result });
        } catch (e) {
            postMessage({ success: false, error: e });
        }
    };
};

const compressImage = async (file, maxFileSize, quality = 0.8) => {
    if (typeof OffscreenCanvas !== 'function') {
        const origCanvas = await blobToCanvas(file);
        const type = file.type === 'image/jpg' ? 'image/jpg' : 'image/webp';
        return exportCanvasSized(
            origCanvas,
            file.name,
            maxFileSize,
            type,
            quality,
            chayns.env.isIOS
        );
    }

    let workerScript = [exportCanvasSized]
        .map((f) => `const ${f.name} = ${f.toString()}`)
        .join('\n');

    workerScript += `\n(${workerThread.toString()})()`;

    const blob = new Blob([workerScript], { type: 'application/javascript' });
    const url = URL.createObjectURL(blob);

    return new Promise((resolve, reject) => {
        const worker = new Worker(url);
        worker.onmessage = (ev) => {
            if (typeof ev.data.success !== 'boolean') return;
            if (ev.data.success) {
                resolve(ev.data.file);
            } else {
                reject(ev.data.error);
            }
            worker.terminate();
        };

        worker.postMessage({
            file,
            maxFileSize,
            quality,
            isIOS: chayns.env.isIOS,
        });
    });
};

export default compressImage;
<|endoftext|>


<commit_before>// This source is the javascript needed to build a simple moving
// cube in **three.js** based on this
// [example](https://raw.github.com/mrdoob/three.js/master/examples/canvas_geometry_cube.html)
// It is the source about this [blog post](/blog/2011/08/06/lets-do-a-cube/).

// ## Now lets start

// declare a bunch of variable we will need later
var startTime	= Date.now();
var container;
var camera, scene, renderer, stats;
var cube;
var projector;

var mouse = { x: 0, y: 0 }

// ## bootstrap functions
// initialiaze everything
init();
// make it move
animate();

// ## Initialize everything
function init() {
	// test if webgl is supported
	if ( ! Detector.webgl ) Detector.addGetWebGLMessage();

	// create the camera
	camera = new THREE.Camera( 70, window.innerWidth / window.innerHeight, 1, 2000 );
	camera.position.y	= 50;
	camera.position.z	= 400;
	camera.target.position.y= 50;

	// create the Scene
	scene		= new THREE.Scene();

	projector	= new THREE.Projector();

	scene.fog	= new THREE.Fog( 0xf0f0f0, 250, 1200 );

	var dirLight	= new THREE.DirectionalLight( 0xffffff, 0.8 );
	dirLight.position.set( 0, 0, 1 );
	dirLight.position.normalize();
	scene.addLight( dirLight );

	var pointLight	= new THREE.PointLight( 0x8080f0, 1.5 );
	pointLight.position.set( 0, 100, 50 );
	scene.addLight( pointLight );

	//var textGeo	= new THREE.TextGeometry( "Hello");
	//var textMaterial= new THREE.MeshNormalMaterial();
	//var textMesh1	= new THREE.Mesh( textGeo, textMaterial );
	//scene.addChild( textMesh1 );

	var addPost	= function(postItem){
		// create the parent Object3D
		var parent	= new THREE.Object3D();

		var geometry	= new THREE.TextGeometry(postItem.title, {
			size		: 30,
			height		: 10,
			bezelThickness	: 2,
			bezelSize	: 1.5,
			bezelEnabled	: true
		});

		title	= new THREE.Mesh(geometry, new THREE.MeshPhongMaterial( { color: 0x4040a0 } ) );

		geometry.computeBoundingBox();
		title.position.x	= -0.5 * ( geometry.boundingBox.x[ 1 ] - geometry.boundingBox.x[ 0 ] );

		parent.addChild( title );

		title._userdata	= {
			type	: "title",
			data	: postItem
		}
		THREE.Collisions.colliders.push( THREE.CollisionUtils.MeshOBB( title ) );

		var geometry	= new THREE.TextGeometry("(+)", {
			size	: 30,
			height	: 10
		});
		comments	= new THREE.Mesh( geometry, new THREE.MeshPhongMaterial( { color: 0x808080 } ) );
		geometry.computeBoundingBox();
		comments.position.x	= -0.5 * ( geometry.boundingBox.x[ 1 ] - geometry.boundingBox.x[ 0 ] );
		comments.position.y	= -50;
		parent.addChild( comments );

		comments._userdata	= {
			type	: "comments",
			data	: postItem
		}
		THREE.Collisions.colliders.push( THREE.CollisionUtils.MeshOBB( comments ) );

		//parent.rotation.y	= 40*Math.PI/180;
		return parent;
	}

	mesh	= addPost({
		title	: "Large-scale Amazon EC2 Outage",
		comments: "http://news.ycombinator.com/item?id=2862566",
		link	: "http://status.aws.amazon.com/"
	})
	scene.addChild(mesh)

/**
 * - TODO find a simple way to complete this one
 * - issue in the visualisation
 *   - would be nice to have something beautifull
 *   - i dunno what to do at the moment
 *   - ok sure so find something simple which does the job
 * - simple visualisation
 *   - a function to clear the screen
 *   - a function to load the text from hn
 *   - the data are just happended to a big plan one topic below the other
 * - about putting the + on the right of the title
 *   - use the bounding box of both Mesh
 *   - the right of the title is the same as the right of the +
 *   - same principle for the appending the topic one below the others
 * - about organizing the clicability
 *   - about hover, use tempp javascript object like collision example
 *   - _actastag = {a : { href:"http://dsdfs", target: "dd"}}
 * - see about timeline.js
 *   - is that suitable for some this animation ?
 *   - what about a carefully crafted tween.js
 *   - tween.js would be so much more three.js mood
*/

if(false){
	var url = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20feed%20where%20url%3D'http%3A%2F%2Fnews.ycombinator.com%2Frss'&format=json&diagnostics=false&callback=?";
	jQuery.getJSON(url, function(json){
		var items	= json.query.results.item;
		var item	= items[0]
		console.log("data", json);
	});
}

	// create the container element
	container = document.createElement( 'div' );
	document.body.appendChild( container );

	container.onmousemove	= onDocumentMouseMove;
	container.onclick	= onDocumentClick;

(function(){
	var update	= function(){
		mesh.position.y = position.y;
	}

	var position	= {y: 450};
	var tween = new TWEEN.Tween(position)
		.to({y: 0}, 3000)
		.easing(TWEEN.Easing.Elastic.EaseInOut)
		.onUpdate(update);

	var tweenBack = new TWEEN.Tween(position)
		.to({y: 450}, 2000)
		.delay(1000)
		.easing(TWEEN.Easing.Elastic.EaseInOut)
		.onUpdate(update);

	tween.chain(tweenBack);
	tweenBack.chain(tween);

	tween.start();
})();

	// init the WebGL renderer and append it to the Dom
	renderer = new THREE.WebGLRenderer({
		antialias		: true,
		preserveDrawingBuffer	: true
	});
	renderer.setSize( window.innerWidth, window.innerHeight );
	container.appendChild( renderer.domElement );

	renderer = new THREE.WebGLRenderer({
		antialias		: true,
		preserveDrawingBuffer	: true
	});

	// init the Stats and append it to the Dom - performance vuemeter
	stats = new Stats();
	stats.domElement.style.position = 'absolute';
	stats.domElement.style.top = '0px';
	container.appendChild( stats.domElement );
}

function onDocumentMouseMove( event ){
	event.preventDefault();
	mouse.x	= ( event.clientX / window.innerWidth ) * 2 - 1;
	mouse.y	= - ( event.clientY / window.innerHeight ) * 2 + 1;
};

function onDocumentClick( event ){
	event.preventDefault();

	var mouseX	= (event.clientX / window.innerWidth) * 2 - 1;
	var mouseY	= -(event.clientY / window.innerHeight) * 2 + 1;

	var vector = new THREE.Vector3( mouseX, mouseY, 0.5 );
	projector.unprojectVector( vector, camera );
	var ray		= new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() );
	var collided	= THREE.Collisions.rayCastNearest( ray );

	if( collided ){
		console.log("collide", collided.mesh._userdata)
		var userdata	= collided.mesh._userdata;
		if( userdata.type === 'title' )
			var url	= userdata.data.link;
		else if( userdata.type === 'comments' )
			var url	= userdata.data.comments;
		else	console.assert(false);
		window.open(url);
	} else {
		console.log("not collide")
	}
};

// ## Animate and Display the Scene
function animate() {
	// render the 3D scene
	render();

	// relaunch the 'timer'
	requestAnimationFrame( animate );

	// update the stats
	stats.update();
	// update the tweens
	TWEEN.update();
}

// ## Render the 3D Scene
function render()
{
	var vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 );
	projector.unprojectVector( vector, camera );
	var ray		= new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() );
	var collided	= THREE.Collisions.rayCastNearest( ray );

	title.materials[0].color.setHex( 0x003300 );
	comments.materials[0].color.setHex( 0x003300 );

	if( collided ){
		document.body.style.cursor	= 'pointer';
		collided.mesh.materials[0].color.setHex( 0x000033 );
	} else {
		document.body.style.cursor	= '';
	}

	// animate the mesh
	//mesh.rotation.x += 0.02;
	//mesh.rotation.y += 0.0125;
	//mesh.rotation.z += 0.0175;

	// actually display the scene in the Dom element
	renderer.render( scene, camera );
}

// TODO remove it from here
var info	= THREEx.glCapability( renderer.getContext() );
console.dir(info)
<commit_msg>lets_write_something.js little cleanup<commit_after>// This source is the javascript needed to build a simple moving
// cube in **three.js** based on this
// [example](https://raw.github.com/mrdoob/three.js/master/examples/canvas_geometry_cube.html)
// It is the source about this [blog post](/blog/2011/08/06/lets-do-a-cube/).

// ## Now lets start

// declare a bunch of variable we will need later
var startTime	= Date.now();
var container;
var camera, scene, renderer, stats;
var cube;
var projector;

var mouse = { x: 0, y: 0 }

// ## bootstrap functions
// initialiaze everything
init();
// make it move
animate();

// ## Initialize everything
function init() {
	// test if webgl is supported
	if ( ! Detector.webgl ) Detector.addGetWebGLMessage();

	// create the camera
	camera = new THREE.Camera( 70, window.innerWidth / window.innerHeight, 1, 2000 );
	camera.position.y	= 50;
	camera.position.z	= 400;
	camera.target.position.y= 50;

	// create the Scene
	scene		= new THREE.Scene();

	projector	= new THREE.Projector();

	scene.fog	= new THREE.Fog( 0xf0f0f0, 250, 1200 );

	var dirLight	= new THREE.DirectionalLight( 0xffffff, 0.8 );
	dirLight.position.set( 0, 0, 1 );
	dirLight.position.normalize();
	scene.addLight( dirLight );

	var pointLight	= new THREE.PointLight( 0x8080f0, 1.5 );
	pointLight.position.set( 0, 100, 50 );
	scene.addLight( pointLight );

	//var textGeo	= new THREE.TextGeometry( "Hello");
	//var textMaterial= new THREE.MeshNormalMaterial();
	//var textMesh1	= new THREE.Mesh( textGeo, textMaterial );
	//scene.addChild( textMesh1 );

	var addPost	= function(postItem){
		// create the parent Object3D
		var parent	= new THREE.Object3D();

		var geometry	= new THREE.TextGeometry(postItem.title, {
			size		: 30,
			height		: 10,
			bezelThickness	: 2,
			bezelSize	: 1.5,
			bezelEnabled	: true
		});

		title	= new THREE.Mesh(geometry, new THREE.MeshPhongMaterial( { color: 0x4040a0 } ) );

		// to center the geometry
		geometry.computeBoundingBox();
		title.position.x	= -0.5 * ( geometry.boundingBox.x[ 1 ] - geometry.boundingBox.x[ 0 ] );

		parent.addChild( title );

		title._userdata	= {
			type	: "title",
			data	: postItem
		}
		THREE.Collisions.colliders.push( THREE.CollisionUtils.MeshOBB( title ) );

		var geometry	= new THREE.TextGeometry("(+)", {
			size	: 30,
			height	: 10
		});
		comments	= new THREE.Mesh( geometry, new THREE.MeshPhongMaterial( { color: 0x808080 } ) );
		geometry.computeBoundingBox();
		comments.position.x	= -0.5 * ( geometry.boundingBox.x[ 1 ] - geometry.boundingBox.x[ 0 ] );
		comments.position.y	= -50;
		parent.addChild( comments );

		comments._userdata	= {
			type	: "comments",
			data	: postItem
		}
		THREE.Collisions.colliders.push( THREE.CollisionUtils.MeshOBB( comments ) );

		//parent.rotation.y	= 40*Math.PI/180;
		return parent;
	}

	mesh	= addPost({
		title	: "Large-scale Amazon EC2 Outage",
		comments: "http://news.ycombinator.com/item?id=2862566",
		link	: "http://status.aws.amazon.com/"
	})
	scene.addChild(mesh)

/**
 * - TODO find a simple way to complete this one
 * - issue in the visualisation
 *   - would be nice to have something beautifull
 *   - i dunno what to do at the moment
 *   - ok sure so find something simple which does the job
 * - simple visualisation
 *   - a function to clear the screen
 *   - a function to load the text from hn
 *   - the data are just happended to a big plan one topic below the other
 * - about putting the + on the right of the title
 *   - use the bounding box of both Mesh
 *   - the right of the title is the same as the right of the +
 *   - same principle for the appending the topic one below the others
 * - about organizing the clicability
 *   - about hover, use tempp javascript object like collision example
 *   - _actastag = {a : { href:"http://dsdfs", target: "dd"}}
 * - see about timeline.js
 *   - is that suitable for some this animation ?
 *   - what about a carefully crafted tween.js
 *   - tween.js would be so much more three.js mood
*/

if(false){
	var url = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20feed%20where%20url%3D'http%3A%2F%2Fnews.ycombinator.com%2Frss'&format=json&diagnostics=false&callback=?";
	jQuery.getJSON(url, function(json){
		var items	= json.query.results.item;
		var item	= items[0]
		console.log("data", json);
	});
}

	// create the container element
	container = document.createElement( 'div' );
	document.body.appendChild( container );

	container.onmousemove	= onDocumentMouseMove;
	container.onclick	= onDocumentClick;

(function(){
	var update	= function(){
		mesh.position.y = position.y;
	}

	var position	= {y: 450};
	var tween = new TWEEN.Tween(position)
		.to({y: 0}, 3000)
		.easing(TWEEN.Easing.Elastic.EaseInOut)
		.onUpdate(update);

	var tweenBack = new TWEEN.Tween(position)
		.to({y: 450}, 2000)
		.delay(1000)
		.easing(TWEEN.Easing.Elastic.EaseInOut)
		.onUpdate(update);

	tween.chain(tweenBack);
	tweenBack.chain(tween);

	tween.start();
})();

	// init the WebGL renderer and append it to the Dom
	renderer = new THREE.WebGLRenderer({
		antialias		: true,
		preserveDrawingBuffer	: true
	});
	renderer.setSize( window.innerWidth, window.innerHeight );
	container.appendChild( renderer.domElement );

	renderer = new THREE.WebGLRenderer({
		antialias		: true,
		preserveDrawingBuffer	: true
	});

	// init the Stats and append it to the Dom - performance vuemeter
	stats = new Stats();
	stats.domElement.style.position = 'absolute';
	stats.domElement.style.top = '0px';
	container.appendChild( stats.domElement );
}

function onDocumentMouseMove( event ){
	event.preventDefault();
	mouse.x	= ( event.clientX / window.innerWidth ) * 2 - 1;
	mouse.y	= - ( event.clientY / window.innerHeight ) * 2 + 1;
};

function onDocumentClick( event ){
	event.preventDefault();

	var mouseX	= (event.clientX / window.innerWidth) * 2 - 1;
	var mouseY	= -(event.clientY / window.innerHeight) * 2 + 1;

	var vector = new THREE.Vector3( mouseX, mouseY, 0.5 );
	projector.unprojectVector( vector, camera );
	var ray		= new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() );
	var collided	= THREE.Collisions.rayCastNearest( ray );

	if( collided ){
		console.log("collide", collided.mesh._userdata)
		var userdata	= collided.mesh._userdata;
		if( userdata.type === 'title' )
			var url	= userdata.data.link;
		else if( userdata.type === 'comments' )
			var url	= userdata.data.comments;
		else	console.assert(false);
		window.open(url);
	} else {
		console.log("not collide")
	}
};

// ## Animate and Display the Scene
function animate() {
	// render the 3D scene
	render();

	// relaunch the 'timer'
	requestAnimationFrame( animate );

	// update the stats
	stats.update();
	// update the tweens
	TWEEN.update();
}

// ## Render the 3D Scene
function render()
{
	var vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 );
	projector.unprojectVector( vector, camera );
	var ray		= new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() );
	var collided	= THREE.Collisions.rayCastNearest( ray );

	title.materials[0].color.setHex( 0x003300 );
	comments.materials[0].color.setHex( 0x003300 );

	if( collided ){
		document.body.style.cursor	= 'pointer';
		collided.mesh.materials[0].color.setHex( 0x000033 );
	} else {
		document.body.style.cursor	= '';
	}

	// animate the mesh
	//mesh.rotation.x += 0.02;
	//mesh.rotation.y += 0.0125;
	//mesh.rotation.z += 0.0175;

	// actually display the scene in the Dom element
	renderer.render( scene, camera );
}

// TODO remove it from here
var info	= THREEx.glCapability( renderer.getContext() );
console.dir(info)
<|endoftext|>


<commit_before>import { accessor as get } from './accessors'
import dates from './dates'

export function startsBefore(date, min) {
  return dates.lt(dates.merge(min, date), min, 'minutes')
}

export function positionFromDate(date, min, total) {
  if (startsBefore(date, min))
    return 0

  let diff = dates.diff(min, dates.merge(min, date), 'minutes')
  return Math.min(diff, total)
}

/**
 * Events will be sorted primarily according to earliest start time.
 * If two events start at the same time, the one with the longest duration will
 * be placed first.
 */
let sort = (events, { startAccessor, endAccessor }) => events.sort((a, b) => {
  let startA = +get(a, startAccessor)
  let startB = +get(b, startAccessor)

  if (startA === startB) {
    return +get(b, endAccessor) - +get(a, endAccessor)
  }

  return startA - startB
})

let getSlot = (event, accessor, min, totalMin) => event && positionFromDate(
  get(event, accessor), min, totalMin
)

/**
 * Two events are considered siblings if their times are overlapping
 */
var isSibling = function isSibling(idx1, idx2, _ref2) {
  var events = _ref2.events,
      startAccessor = _ref2.startAccessor,
      endAccessor = _ref2.endAccessor,
      min = _ref2.min,
      totalMin = _ref2.totalMin;

  var event1 = events[idx1];
  var event2 = events[idx2];

  if (!event1 || !event2) return false;

  var start1 = getSlot(event1, startAccessor, min, totalMin);
  var start2 = getSlot(event2, startAccessor, min, totalMin);
  var end1 = getSlot(event1, endAccessor, min, totalMin);
  var end2 = getSlot(event2, endAccessor, min, totalMin);

  return (start2 <= start1 && end2 > start1) || (start2 >= start1 && start2 < end1);
};

/**
 * An event is considered a child of another event if its start time is
 * more than 1 hour later than the other event's start time,
 * but before its end time.
 */
let isChild = (parentIdx, childIdx, {
  events, startAccessor, endAccessor, min, totalMin
}) => {
  if (isSibling(
    parentIdx, childIdx,
    { events, startAccessor, endAccessor, min, totalMin }
  )) return false

  let parentEnd = getSlot(events[parentIdx], endAccessor, min, totalMin)
  let childStart = getSlot(events[childIdx], startAccessor, min, totalMin)

  return parentEnd > childStart
}

/**
 * Given an event index, siblings directly following it will be found and
 * returned as an array of indexes.
 */
let getSiblings = (idx, {
  events, startAccessor, endAccessor, min, totalMin
}) => {
  let nextIdx = idx
  let siblings = []

  while (isSibling(
    idx, ++nextIdx, { events, startAccessor, endAccessor, min, totalMin })
  ) {
    siblings.push(nextIdx)
  }

  return siblings
}

/**
 * Given an event index, and a start search position, all child events to that
 * event will be found and placed into groups of siblings.
 * The return value is an array of child group arrays, as well as the largest
 * size of the child groups.
 */
let getChildGroups = (idx, nextIdx, {
  events, startAccessor, endAccessor, min, totalMin
}) => {
  let groups = []
  let nbrOfColumns = 0

  while (isChild(
    idx, nextIdx,
    { events, startAccessor, endAccessor, min, totalMin }
  )) {
    let childGroup = [nextIdx]
    let siblingIdx = nextIdx

    while (isSibling(
      nextIdx, ++siblingIdx,
      { events, startAccessor, endAccessor, min, totalMin }
    )) {
      childGroup.push(siblingIdx)
    }

    nbrOfColumns = Math.max(nbrOfColumns, childGroup.length)
    groups.push(childGroup)
    nextIdx = siblingIdx
  }

  return { childGroups: groups, nbrOfChildColumns: nbrOfColumns }
}

/**
 * Returns height and top offset, both in percentage, for an event at
 * the specified index.
 */
let getYStyles = (idx, {
  events, startAccessor, endAccessor, min, totalMin, step
}) => {
  let event = events[idx]
  let start = getSlot(event, startAccessor, min, totalMin)
  let end = Math.max(getSlot(event, endAccessor, min, totalMin), start + step)
  let top = start / totalMin * 100
  let bottom = end / totalMin * 100

  return {
    top,
    height: bottom - top
  }
}

/**
 * Takes an array of unsorted events, and returns a sorted array
 * containing the same events, but with an additional style property.
 * These styles will position the events similarly to Google Calendar.
 *
 * The algorithm will start by sorting the array, and then iterating over it.
 * Starting at the first event, each of its siblings and children, placed in
 * groups of siblings, will be found. Both are needed in order to calculate the
 * width of the first event. When the width is known, its siblings will be
 * given the same width, but with an incremental x-offset.
 *
 * Each group of children will be looking to move as far away from its original
 * parent as possible. A move can be made to one of the parent's siblings, if
 * that sibling is also a parent to the child group. This will make room for
 * more events.
 *
 * When a child group knows its parent, it looks at the space occupied by that
 * parent, and calculates the remaning available space and divides that among
 * each other.
 *
 * All widths and x-offsets are calculated without taking overlapping into
 * account. Overlapping is added in the end according to the OVERLAP_MULTIPLIER.
 * If that is set to 0, the events won't overlap or grow.
 *
 * When one of these rounds are finished, all events connected have been
 * traversed, so the cursor will be moved past all of them.
 */
export default function getStyledEvents ({
  events: unsortedEvents, startAccessor, endAccessor, min, totalMin, step
}) {
  let OVERLAP_MULTIPLIER = 0.3
  let events = sort(unsortedEvents, { startAccessor, endAccessor })
  let helperArgs = { events, startAccessor, endAccessor, min, totalMin, step }
  let styledEvents = []
  let idx = 0

  // One iteration will cover all connected events.
  while (idx < events.length) {
    let siblings = getSiblings(idx, helperArgs)
    let { childGroups, nbrOfChildColumns } = getChildGroups(
      idx, idx + siblings.length + 1, helperArgs
    )
    let nbrOfColumns = Math.max(nbrOfChildColumns, siblings.length) + 1;

    // Set styles to top level events.
    [idx, ...siblings].forEach((eventIdx, siblingIdx) => {
      let width = 100 / nbrOfColumns
      let xAdjustment = width * (nbrOfColumns > 1 ? OVERLAP_MULTIPLIER : 0)
      let { top, height } = getYStyles(eventIdx, helperArgs)

      styledEvents[eventIdx] = {
        event: events[eventIdx],
        style: {
          top,
          height,
          width: width + xAdjustment,
          xOffset: (width * siblingIdx) - xAdjustment
        }
      }
    })

    childGroups.forEach(group => {
      let parentIdx = idx
      let siblingIdx = 0

      // Move child group to sibling if possible, since this will makes
      // room for more events.
      while (isChild(siblings[siblingIdx], group[0], helperArgs)) {
        parentIdx = siblings[siblingIdx]
        siblingIdx++
      }

      // Set styles to child events.
      group.forEach((eventIdx, i) => {
        let { style: parentStyle } = styledEvents[parentIdx]
        let spaceOccupiedByParent = parentStyle.width + parentStyle.xOffset
        let columns = Math.min(group.length, nbrOfColumns)
        let width = (100 - spaceOccupiedByParent) / columns
        let xAdjustment = spaceOccupiedByParent * OVERLAP_MULTIPLIER
        let { top, height } = getYStyles(eventIdx, helperArgs)

        styledEvents[eventIdx] = {
          event: events[eventIdx],
          style: {
            top,
            height,
            width: width + xAdjustment,
            xOffset: spaceOccupiedByParent + (width * i) - xAdjustment
          }
        }
      })
    })

    // Move past all events we just went through
    idx += 1 + siblings.length + childGroups.reduce(
      (total, group) => total + group.length, 0
    )
  }

  return styledEvents
}
<commit_msg>Update dayViewLayout.js<commit_after>import { accessor as get } from './accessors'
import dates from './dates'

export function startsBefore(date, min) {
  return dates.lt(dates.merge(min, date), min, 'minutes')
}

export function positionFromDate(date, min, total) {
  if (startsBefore(date, min))
    return 0

  let diff = dates.diff(min, dates.merge(min, date), 'minutes')
  return Math.min(diff, total)
}

/**
 * Events will be sorted primarily according to earliest start time.
 * If two events start at the same time, the one with the longest duration will
 * be placed first.
 */
let sort = (events, { startAccessor, endAccessor }) => events.sort((a, b) => {
  let startA = +get(a, startAccessor)
  let startB = +get(b, startAccessor)

  if (startA === startB) {
    return +get(b, endAccessor) - +get(a, endAccessor)
  }

  return startA - startB
})

let getSlot = (event, accessor, min, totalMin) => event && positionFromDate(
  get(event, accessor), min, totalMin
)

/**
 * Two events are considered siblings if their times are overlapping
 */
let isSibling = (idx1, idx2, { events, startAccessor, min, totalMin }) => {
  let event1 = events[idx1]
  let event2 = events[idx2]

  if (!event1 || !event2) return false

  var start1 = getSlot(event1, startAccessor, min, totalMin)
  var start2 = getSlot(event2, startAccessor, min, totalMin)
  var end1 = getSlot(event1, endAccessor, min, totalMin)
  var end2 = getSlot(event2, endAccessor, min, totalMin)

  return (start2 <= start1 && end2 > start1) || (start2 >= start1 && start2 < end1)
}

/**
 * An event is considered a child of another event if its start time is
 * more than 1 hour later than the other event's start time,
 * but before its end time.
 */
let isChild = (parentIdx, childIdx, {
  events, startAccessor, endAccessor, min, totalMin
}) => {
  if (isSibling(
    parentIdx, childIdx,
    { events, startAccessor, endAccessor, min, totalMin }
  )) return false

  let parentEnd = getSlot(events[parentIdx], endAccessor, min, totalMin)
  let childStart = getSlot(events[childIdx], startAccessor, min, totalMin)

  return parentEnd > childStart
}

/**
 * Given an event index, siblings directly following it will be found and
 * returned as an array of indexes.
 */
let getSiblings = (idx, {
  events, startAccessor, endAccessor, min, totalMin
}) => {
  let nextIdx = idx
  let siblings = []

  while (isSibling(
    idx, ++nextIdx, { events, startAccessor, endAccessor, min, totalMin })
  ) {
    siblings.push(nextIdx)
  }

  return siblings
}

/**
 * Given an event index, and a start search position, all child events to that
 * event will be found and placed into groups of siblings.
 * The return value is an array of child group arrays, as well as the largest
 * size of the child groups.
 */
let getChildGroups = (idx, nextIdx, {
  events, startAccessor, endAccessor, min, totalMin
}) => {
  let groups = []
  let nbrOfColumns = 0

  while (isChild(
    idx, nextIdx,
    { events, startAccessor, endAccessor, min, totalMin }
  )) {
    let childGroup = [nextIdx]
    let siblingIdx = nextIdx

    while (isSibling(
      nextIdx, ++siblingIdx,
      { events, startAccessor, endAccessor, min, totalMin }
    )) {
      childGroup.push(siblingIdx)
    }

    nbrOfColumns = Math.max(nbrOfColumns, childGroup.length)
    groups.push(childGroup)
    nextIdx = siblingIdx
  }

  return { childGroups: groups, nbrOfChildColumns: nbrOfColumns }
}

/**
 * Returns height and top offset, both in percentage, for an event at
 * the specified index.
 */
let getYStyles = (idx, {
  events, startAccessor, endAccessor, min, totalMin, step
}) => {
  let event = events[idx]
  let start = getSlot(event, startAccessor, min, totalMin)
  let end = Math.max(getSlot(event, endAccessor, min, totalMin), start + step)
  let top = start / totalMin * 100
  let bottom = end / totalMin * 100

  return {
    top,
    height: bottom - top
  }
}

/**
 * Takes an array of unsorted events, and returns a sorted array
 * containing the same events, but with an additional style property.
 * These styles will position the events similarly to Google Calendar.
 *
 * The algorithm will start by sorting the array, and then iterating over it.
 * Starting at the first event, each of its siblings and children, placed in
 * groups of siblings, will be found. Both are needed in order to calculate the
 * width of the first event. When the width is known, its siblings will be
 * given the same width, but with an incremental x-offset.
 *
 * Each group of children will be looking to move as far away from its original
 * parent as possible. A move can be made to one of the parent's siblings, if
 * that sibling is also a parent to the child group. This will make room for
 * more events.
 *
 * When a child group knows its parent, it looks at the space occupied by that
 * parent, and calculates the remaning available space and divides that among
 * each other.
 *
 * All widths and x-offsets are calculated without taking overlapping into
 * account. Overlapping is added in the end according to the OVERLAP_MULTIPLIER.
 * If that is set to 0, the events won't overlap or grow.
 *
 * When one of these rounds are finished, all events connected have been
 * traversed, so the cursor will be moved past all of them.
 */
export default function getStyledEvents ({
  events: unsortedEvents, startAccessor, endAccessor, min, totalMin, step
}) {
  let OVERLAP_MULTIPLIER = 0.3
  let events = sort(unsortedEvents, { startAccessor, endAccessor })
  let helperArgs = { events, startAccessor, endAccessor, min, totalMin, step }
  let styledEvents = []
  let idx = 0

  // One iteration will cover all connected events.
  while (idx < events.length) {
    let siblings = getSiblings(idx, helperArgs)
    let { childGroups, nbrOfChildColumns } = getChildGroups(
      idx, idx + siblings.length + 1, helperArgs
    )
    let nbrOfColumns = Math.max(nbrOfChildColumns, siblings.length) + 1;

    // Set styles to top level events.
    [idx, ...siblings].forEach((eventIdx, siblingIdx) => {
      let width = 100 / nbrOfColumns
      let xAdjustment = width * (nbrOfColumns > 1 ? OVERLAP_MULTIPLIER : 0)
      let { top, height } = getYStyles(eventIdx, helperArgs)

      styledEvents[eventIdx] = {
        event: events[eventIdx],
        style: {
          top,
          height,
          width: width + xAdjustment,
          xOffset: (width * siblingIdx) - xAdjustment
        }
      }
    })

    childGroups.forEach(group => {
      let parentIdx = idx
      let siblingIdx = 0

      // Move child group to sibling if possible, since this will makes
      // room for more events.
      while (isChild(siblings[siblingIdx], group[0], helperArgs)) {
        parentIdx = siblings[siblingIdx]
        siblingIdx++
      }

      // Set styles to child events.
      group.forEach((eventIdx, i) => {
        let { style: parentStyle } = styledEvents[parentIdx]
        let spaceOccupiedByParent = parentStyle.width + parentStyle.xOffset
        let columns = Math.min(group.length, nbrOfColumns)
        let width = (100 - spaceOccupiedByParent) / columns
        let xAdjustment = spaceOccupiedByParent * OVERLAP_MULTIPLIER
        let { top, height } = getYStyles(eventIdx, helperArgs)

        styledEvents[eventIdx] = {
          event: events[eventIdx],
          style: {
            top,
            height,
            width: width + xAdjustment,
            xOffset: spaceOccupiedByParent + (width * i) - xAdjustment
          }
        }
      })
    })

    // Move past all events we just went through
    idx += 1 + siblings.length + childGroups.reduce(
      (total, group) => total + group.length, 0
    )
  }

  return styledEvents
}
<|endoftext|>


<commit_before>/* eslint-disable max-nested-callbacks */
/*!
 * ${copyright}
 */

sap.ui.define([
	"sap/ui/core/util/reflection/JsControlTreeModifier",
	"sap/ui/core/Component",
	"sap/ui/fl/util/ManagedObjectModel" // used implicitly by oModifier.createControl() function
], function (
	JsControlTreeModifier,
	Component
) {
	"use strict";

	/**
	 * Change handler for splitting sap.m.MenuButton into sap.m.Button(s).
	 *
	 * @alias sap.m.changeHandler.SplitMenuButton
	 * @author SAP SE
	 * @version ${version}
	 * @experimental Since 1.48
	 */
	var SplitMenuButton = {};

	var SOURCE_CONTROL = "sourceControl";

	/**
	 * Split a MenuButton into separate Buttons
	 *
	 * @param {sap.ui.fl.Change} oChange Change wrapper object with instructions to be applied on the control map
	 * @param {sap.m.IBar} oControl Bar control that matches the change selector for applying the change
	 * @param {object} mPropertyBag Map of properties
	 * @param {object} mPropertyBag.modifier Modifier for the controls
	 * @return {Promise} Promise resolving when change was applied
	 *
	 * @public
	 */
	SplitMenuButton.applyChange = function(oChange, oControl, mPropertyBag) {
		if (mPropertyBag.modifier.targets !== "jsControlTree") {
			return Promise.reject(new Error("Split change can't be applied on XML tree"));
		}

		var oChangeDefinition = oChange.getDefinition();
		var oModifier = mPropertyBag.modifier;
		var oView = mPropertyBag.view;
		var oAppComponent = mPropertyBag.appComponent;
		var oSourceControl = oChange.getDependentControl(SOURCE_CONTROL, mPropertyBag);
		var oMenu;
		var aMenuItems;
		var sParentAggregation;
		var iAggregationIndex;
		var aNewElementSelectors;
		var oRevertData;
		var sModelName;
		var oManagedObjectModel;
		var oParent;

		return Promise.resolve()
			.then(function() {
				return oModifier.getAggregation(oSourceControl, "menu");
			})
			.then(function(oRetrievedMenu) {
				oMenu = oRetrievedMenu;
				return oModifier.getAggregation(oMenu, "items");
			})
			.then(function(aReturnedMenuItems) {
				aMenuItems = aReturnedMenuItems;
				oParent = oModifier.getParent(oSourceControl);
				return oModifier.getParentAggregationName(oSourceControl, oParent);
			})
			.then(function(sRetrievedParentAggregation) {
				sParentAggregation = sRetrievedParentAggregation;
				return oModifier.findIndexInParentAggregation(oSourceControl);
			})
			.then(function(iRetrievedAggregationIndex) {
				iAggregationIndex = iRetrievedAggregationIndex;
				aNewElementSelectors = oChangeDefinition.content.newElementIds;
				oRevertData = {
					parentAggregation: sParentAggregation,
					insertIndex: iAggregationIndex,
					insertedButtons: []
				};
				return aMenuItems.reduce(function (oPreviousPromise, oMenuItem, index) {
					var oSelector;
					var iIndex;
					var oButton;

					return oPreviousPromise
						.then(function() {
							iIndex = index;
							oSelector = aNewElementSelectors[iIndex];
							return oModifier.createControl("sap.m.Button", oAppComponent, oView, oSelector);
						})
						.then(function(oCreatedButton){
							oButton = oCreatedButton;
							oRevertData.insertedButtons.push(oSelector);
							sModelName = "$sap.m.flexibility.SplitButtonsModel";
							return oModifier.createControl(
								"sap.ui.fl.util.ManagedObjectModel",
								oAppComponent,
								oView,
								Object.assign({}, oSelector, {
									id: oSelector.id + '-managedObjectModel'
								}),
								{
									object: oMenuItem,
									name: sModelName
								}
							);
						})
						.then(function(oCreatedManagedObjectModel) {
							oManagedObjectModel = oCreatedManagedObjectModel;
							// ManagedObjectModel should be placed in `dependents` aggregation of the Button
							return oModifier.insertAggregation(oButton, "dependents", oManagedObjectModel, 0, oView);
						})
						.then(function(){
							oModifier.bindProperty(oButton, "text", sModelName + ">/text");
							oModifier.bindProperty(oButton, "icon", sModelName + ">/icon");
							oModifier.bindProperty(oButton, "enabled", sModelName + ">/enabled");
							oModifier.bindProperty(oButton, "visible", sModelName + ">/visible");
							return oModifier.bindAggregation(oButton, "customData", {
								path: sModelName + ">/customData",
								template: oModifier.createControl(
									"sap.ui.core.CustomData",
									oAppComponent,
									oView,
									Object.assign({}, oSelector, {
										id: oSelector.id + '-customData'
									}),
									{
										key: {
											path: sModelName + ">key"
										},
										value: {
											path: sModelName + ">value"
										}
									}
								),
								templateShareable: false
							});
						})
						.then(function(){
							return oModifier.attachEvent(
								oButton,
								"press",
								"sap.m.changeHandler.SplitMenuButton.pressHandler",
								{
									selector: oModifier.getSelector(oMenuItem, oAppComponent),
									appComponentId: oAppComponent.getId()
								}
							);
						})
						.then(function(){
							return oModifier.insertAggregation(oParent, sParentAggregation, oButton, iAggregationIndex + iIndex, oView);
						});
				}, Promise.resolve());
			})
			.then(function() {
				return Promise.resolve()
					.then(oModifier.removeAggregation.bind(oModifier, oParent, sParentAggregation, oSourceControl))
					.then(oModifier.insertAggregation.bind(oModifier, oParent, "dependents", oSourceControl, 0, oView))
					.then(function() {
						oChange.setRevertData(oRevertData);
					});
				});
	};

	/**
	 * Reverts applied change
	 *
	 * @param {sap.ui.fl.Change} oChange Change wrapper object with instructions to be applied on the control map
	 * @param {sap.m.IBar} oControl Bar control that matches the change selector for applying the change
	 * @param {object} mPropertyBag Map of properties
	 * @param {object} mPropertyBag.modifier Modifier for the controls
	 * @return {Promise} Promise resolving when change was reverted
	 *
	 * @public
	 */
	SplitMenuButton.revertChange = function(oChange, oControl, mPropertyBag) {
		var oModifier = mPropertyBag.modifier;
		var oRevertData =  oChange.getRevertData();
		var oSourceControl = oChange.getDependentControl(SOURCE_CONTROL, mPropertyBag);
		var oAppComponent = mPropertyBag.appComponent;
		var oView = mPropertyBag.view;
		var oParent = oModifier.getParent(oSourceControl);
		var sParentAggregation = oRevertData.parentAggregation;
		var iAggregationIndex = oRevertData.insertIndex;

		var aInsertedButtons = [];

		return Promise.resolve()
			.then(function() {
				oRevertData.insertedButtons.forEach(function(oSelector) {
					aInsertedButtons.push(oModifier.bySelector(oSelector, oAppComponent, oView));
				});
				return aInsertedButtons.reduce(function(oPreviousPromise, oButton) {
					return oPreviousPromise
						.then(function() {
							return oModifier.removeAggregation(oParent, sParentAggregation, oButton);
						})
						.then(function() {
							return oModifier.destroy(oButton);
						});
				}, Promise.resolve());
			})
			.then(oModifier.insertAggregation.bind(oModifier, oParent, sParentAggregation, oSourceControl, iAggregationIndex, oView))
			.then(function() {
				oChange.resetRevertData();
			});
	};

	/**
	 * Completes the change by adding change handler specific content
	 *
	 * @param {sap.ui.fl.Change} oChange change wrapper object to be completed
	 * @param {object} oSpecificChangeInfo Specific change info containing parentId
	 * @param {object} mPropertyBag Map of properties
	 *
	 * @public
	 */
	SplitMenuButton.completeChangeContent = function(oChange, oSpecificChangeInfo, mPropertyBag) {
		var oModifier = mPropertyBag.modifier;
		var oAppComponent = mPropertyBag.appComponent;
		var oChangeDefinition = oChange.getDefinition();

		if (!oSpecificChangeInfo.newElementIds) {
			throw new Error("Split of MenuButton cannot be applied : oSpecificChangeInfo.newElementIds attribute required");
		}

		if (!oSpecificChangeInfo.sourceControlId) {
			throw new Error("Split of MenuButton cannot be applied : oSpecificChangeInfo.sourceControlId attribute required");
		}

		oChange.addDependentControl(oSpecificChangeInfo.sourceControlId, SOURCE_CONTROL, mPropertyBag);
		oChangeDefinition.content.sourceSelector = oModifier.getSelector(oSpecificChangeInfo.sourceControlId, oAppComponent);
		oChangeDefinition.content.newElementIds = oSpecificChangeInfo.newElementIds.map(function (sElementId) {
			return oModifier.getSelector(sElementId, oAppComponent);
		});
	};

	/**
	 * Callback function which is attached via modifier in applyChange
	 *
	 * @param {sap.ui.base.Event} oEvent Event object
	 * @param {object} mSelector Selector object
	 * @param {string} mSelector.id ID used for determination of the flexibility target
	 * @param {boolean} mSelector.idIsLocal flag if the selector.id has to be concatenated with the application component ID
	 * while applying the change.
	 */
	SplitMenuButton.pressHandler = function (oEvent, mParameters) {
		var oMenuItem = JsControlTreeModifier.bySelector(mParameters.selector, Component.get(mParameters.appComponentId));
		oMenuItem.firePress();
	};

	return SplitMenuButton;
}, /* bExport= */true);
<commit_msg>[INTERNAL][FIX] sap.ui.rta: Modifier Button Split Async Fix<commit_after>/* eslint-disable max-nested-callbacks */
/*!
 * ${copyright}
 */

sap.ui.define([
	"sap/ui/core/util/reflection/JsControlTreeModifier",
	"sap/ui/core/Component",
	"sap/ui/fl/util/ManagedObjectModel" // used implicitly by oModifier.createControl() function
], function (
	JsControlTreeModifier,
	Component
) {
	"use strict";

	/**
	 * Change handler for splitting sap.m.MenuButton into sap.m.Button(s).
	 *
	 * @alias sap.m.changeHandler.SplitMenuButton
	 * @author SAP SE
	 * @version ${version}
	 * @experimental Since 1.48
	 */
	var SplitMenuButton = {};

	var SOURCE_CONTROL = "sourceControl";

	/**
	 * Split a MenuButton into separate Buttons
	 *
	 * @param {sap.ui.fl.Change} oChange Change wrapper object with instructions to be applied on the control map
	 * @param {sap.m.IBar} oControl Bar control that matches the change selector for applying the change
	 * @param {object} mPropertyBag Map of properties
	 * @param {object} mPropertyBag.modifier Modifier for the controls
	 * @return {Promise} Promise resolving when change was applied
	 *
	 * @public
	 */
	SplitMenuButton.applyChange = function(oChange, oControl, mPropertyBag) {
		if (mPropertyBag.modifier.targets !== "jsControlTree") {
			return Promise.reject(new Error("Split change can't be applied on XML tree"));
		}

		var oChangeDefinition = oChange.getDefinition();
		var oModifier = mPropertyBag.modifier;
		var oView = mPropertyBag.view;
		var oAppComponent = mPropertyBag.appComponent;
		var oSourceControl = oChange.getDependentControl(SOURCE_CONTROL, mPropertyBag);
		var oMenu;
		var aMenuItems;
		var sParentAggregation;
		var iAggregationIndex;
		var aNewElementSelectors;
		var oRevertData;
		var sModelName;
		var oManagedObjectModel;
		var oParent;

		return Promise.resolve()
			.then(function() {
				return oModifier.getAggregation(oSourceControl, "menu");
			})
			.then(function(oRetrievedMenu) {
				oMenu = oRetrievedMenu;
				return oModifier.getAggregation(oMenu, "items");
			})
			.then(function(aReturnedMenuItems) {
				aMenuItems = aReturnedMenuItems;
				oParent = oModifier.getParent(oSourceControl);
				return oModifier.getParentAggregationName(oSourceControl, oParent);
			})
			.then(function(sRetrievedParentAggregation) {
				sParentAggregation = sRetrievedParentAggregation;
				return oModifier.findIndexInParentAggregation(oSourceControl);
			})
			.then(function(iRetrievedAggregationIndex) {
				iAggregationIndex = iRetrievedAggregationIndex;
				aNewElementSelectors = oChangeDefinition.content.newElementIds;
				oRevertData = {
					parentAggregation: sParentAggregation,
					insertIndex: iAggregationIndex,
					insertedButtons: []
				};
				return aMenuItems.reduce(function (oPreviousPromise, oMenuItem, index) {
					var oSelector;
					var iIndex;
					var oButton;

					return oPreviousPromise
						.then(function() {
							iIndex = index;
							oSelector = aNewElementSelectors[iIndex];
							return oModifier.createControl("sap.m.Button", oAppComponent, oView, oSelector);
						})
						.then(function(oCreatedButton){
							oButton = oCreatedButton;
							oRevertData.insertedButtons.push(oSelector);
							sModelName = "$sap.m.flexibility.SplitButtonsModel";
							return oModifier.createControl(
								"sap.ui.fl.util.ManagedObjectModel",
								oAppComponent,
								oView,
								Object.assign({}, oSelector, {
									id: oSelector.id + '-managedObjectModel'
								}),
								{
									object: oMenuItem,
									name: sModelName
								}
							);
						})
						.then(function(oCreatedManagedObjectModel) {
							oManagedObjectModel = oCreatedManagedObjectModel;
							// ManagedObjectModel should be placed in `dependents` aggregation of the Button
							return oModifier.insertAggregation(oButton, "dependents", oManagedObjectModel, 0, oView);
						})
						.then(function(){
							oModifier.bindProperty(oButton, "text", sModelName + ">/text");
							oModifier.bindProperty(oButton, "icon", sModelName + ">/icon");
							oModifier.bindProperty(oButton, "enabled", sModelName + ">/enabled");
							oModifier.bindProperty(oButton, "visible", sModelName + ">/visible");
							return oModifier.createControl(
									"sap.ui.core.CustomData",
									oAppComponent,
									oView,
									Object.assign({}, oSelector, {
										id: oSelector.id + '-customData'
									}),
									{
										key: {
											path: sModelName + ">key"
										},
										value: {
											path: sModelName + ">value"
										}
									}
								);
						})
						.then(function(oTemplate){
							return oModifier.bindAggregation(oButton, "customData", {
								path: sModelName + ">/customData",
								template: oTemplate,
								templateShareable: false
							});
						})
						.then(function(){
							return oModifier.attachEvent(
								oButton,
								"press",
								"sap.m.changeHandler.SplitMenuButton.pressHandler",
								{
									selector: oModifier.getSelector(oMenuItem, oAppComponent),
									appComponentId: oAppComponent.getId()
								}
							);
						})
						.then(function(){
							return oModifier.insertAggregation(oParent, sParentAggregation, oButton, iAggregationIndex + iIndex, oView);
						});
				}, Promise.resolve());
			})
			.then(function() {
				return Promise.resolve()
					.then(oModifier.removeAggregation.bind(oModifier, oParent, sParentAggregation, oSourceControl))
					.then(oModifier.insertAggregation.bind(oModifier, oParent, "dependents", oSourceControl, 0, oView))
					.then(function() {
						oChange.setRevertData(oRevertData);
					});
				});
	};

	/**
	 * Reverts applied change
	 *
	 * @param {sap.ui.fl.Change} oChange Change wrapper object with instructions to be applied on the control map
	 * @param {sap.m.IBar} oControl Bar control that matches the change selector for applying the change
	 * @param {object} mPropertyBag Map of properties
	 * @param {object} mPropertyBag.modifier Modifier for the controls
	 * @return {Promise} Promise resolving when change was reverted
	 *
	 * @public
	 */
	SplitMenuButton.revertChange = function(oChange, oControl, mPropertyBag) {
		var oModifier = mPropertyBag.modifier;
		var oRevertData =  oChange.getRevertData();
		var oSourceControl = oChange.getDependentControl(SOURCE_CONTROL, mPropertyBag);
		var oAppComponent = mPropertyBag.appComponent;
		var oView = mPropertyBag.view;
		var oParent = oModifier.getParent(oSourceControl);
		var sParentAggregation = oRevertData.parentAggregation;
		var iAggregationIndex = oRevertData.insertIndex;

		var aInsertedButtons = [];

		return Promise.resolve()
			.then(function() {
				oRevertData.insertedButtons.forEach(function(oSelector) {
					aInsertedButtons.push(oModifier.bySelector(oSelector, oAppComponent, oView));
				});
				return aInsertedButtons.reduce(function(oPreviousPromise, oButton) {
					return oPreviousPromise
						.then(function() {
							return oModifier.removeAggregation(oParent, sParentAggregation, oButton);
						})
						.then(function() {
							return oModifier.destroy(oButton);
						});
				}, Promise.resolve());
			})
			.then(oModifier.insertAggregation.bind(oModifier, oParent, sParentAggregation, oSourceControl, iAggregationIndex, oView))
			.then(function() {
				oChange.resetRevertData();
			});
	};

	/**
	 * Completes the change by adding change handler specific content
	 *
	 * @param {sap.ui.fl.Change} oChange change wrapper object to be completed
	 * @param {object} oSpecificChangeInfo Specific change info containing parentId
	 * @param {object} mPropertyBag Map of properties
	 *
	 * @public
	 */
	SplitMenuButton.completeChangeContent = function(oChange, oSpecificChangeInfo, mPropertyBag) {
		var oModifier = mPropertyBag.modifier;
		var oAppComponent = mPropertyBag.appComponent;
		var oChangeDefinition = oChange.getDefinition();

		if (!oSpecificChangeInfo.newElementIds) {
			throw new Error("Split of MenuButton cannot be applied : oSpecificChangeInfo.newElementIds attribute required");
		}

		if (!oSpecificChangeInfo.sourceControlId) {
			throw new Error("Split of MenuButton cannot be applied : oSpecificChangeInfo.sourceControlId attribute required");
		}

		oChange.addDependentControl(oSpecificChangeInfo.sourceControlId, SOURCE_CONTROL, mPropertyBag);
		oChangeDefinition.content.sourceSelector = oModifier.getSelector(oSpecificChangeInfo.sourceControlId, oAppComponent);
		oChangeDefinition.content.newElementIds = oSpecificChangeInfo.newElementIds.map(function (sElementId) {
			return oModifier.getSelector(sElementId, oAppComponent);
		});
	};

	/**
	 * Callback function which is attached via modifier in applyChange
	 *
	 * @param {sap.ui.base.Event} oEvent Event object
	 * @param {object} mSelector Selector object
	 * @param {string} mSelector.id ID used for determination of the flexibility target
	 * @param {boolean} mSelector.idIsLocal flag if the selector.id has to be concatenated with the application component ID
	 * while applying the change.
	 */
	SplitMenuButton.pressHandler = function (oEvent, mParameters) {
		var oMenuItem = JsControlTreeModifier.bySelector(mParameters.selector, Component.get(mParameters.appComponentId));
		oMenuItem.firePress();
	};

	return SplitMenuButton;
}, /* bExport= */true);
<|endoftext|>


<commit_before>(function(){
	'use strict';

	angular
	    .module('shipyard.images')
            .factory('ImagesService', ImagesService);

	ImagesService.$inject = ['$http'];
        function ImagesService($http) {
            return {
                list: function() {
                    var promise = $http
                        .get('/images/json?all=1')
                        .then(function(response) {
                            return response.data;
                        });
                    return promise;
                },
                remove: function(image) {
                    var promise = $http
                        .delete('/images/' + image.Id + '?force=1')
                        .then(function(response) {
                            return response.data;
                        });
                    return promise;
                }
            }
        }
})();
<commit_msg>don't list intermediate images<commit_after>(function(){
	'use strict';

	angular
	    .module('shipyard.images')
            .factory('ImagesService', ImagesService);

	ImagesService.$inject = ['$http'];
        function ImagesService($http) {
            return {
                list: function() {
                    var promise = $http
                        .get('/images/json')
                        .then(function(response) {
                            return response.data;
                        });
                    return promise;
                },
                remove: function(image) {
                    var promise = $http
                        .delete('/images/' + image.Id + '?force=1')
                        .then(function(response) {
                            return response.data;
                        });
                    return promise;
                }
            }
        }
})();
<|endoftext|>


<commit_before>'use strict'

if(typeof require !== 'undefined') {
    var EventDispatcher = require('./eventdispatcher')
}

function Reactant(value) {
    EventDispatcher.call(this)
    this.value = value
    this.breakHierarchy = undefined
}
Reactant.prototype = {
    __proto__: EventDispatcher.prototype,
    setFunc: function(func, onMod) {
        var removal = this.setFuncSilent(func, onMod)
        this.proc()
        return removal
    },
    super_proc: EventDispatcher.prototype.proc,
    proc: function() {
        var prev = this.lastValue
        var curr = this.value
        if(this.equals(prev, curr)) return

        this.lastValue = curr // must be saved here because it might be altered during event propagation
        this.super_proc(prev, curr)
    },
    listen_pc: function(callback) {
        var result = this.listen(callback)  /// TODO: test for removal called during first call of callback
        callback(undefined, this.value)
        return result
    },
    and: function(other) {
        return this.compose(other, function(a, b) { return a && b })
    },
    or: function(other) {
        return this.compose(other, function(a, b) { return a || b })
    },
    transform: function(func) {
        var result = new Reactant()
        result.setFunc(function() { return func(this.value) }.bind(this))

        ////////////////////////////////////////////////////// TODO: This listener needs to be removed when `result` has none of its own listeners, and re-added when it does
        //var remove = this.listen(function() { result.proc() })
        var remove = this.listen(result.proc.bind(result))

        return result
    },
    onCondition: function(predicate) {
        return this.transform(predicate).on(true)
    },
    on: function(target) {
        var result = new EventDispatcher()

        ////////////////////////////////////////////////////// TODO: This listener needs to be removed when `result` has none of its own listeners, and re-added when it does
        this.listen(function(prev, curr) {
            if(this.equals(curr, target))
                result.proc()
        }.bind(this))
        return result
    },
    onNot: function(target) {
        var result = new EventDispatcher()

        ////////////////////////////////////////////////////// TODO: This listener needs to be removed when `result` has none of its own listeners, and re-added when it does
        this.listen(function(prev, curr) {
            if(!this.equals(curr, target))
                result.proc()
        }.bind(this))
        return result
    },
    /////////////////////////////////////////////// TODO: reactant.filter

    setFuncSilent: function(func, onMod) {
        if(this.breakHierarchy) this.breakHierarchy()
        this.breakHierarchy = undefined

        this.valueFunc = func

        if(onMod)
            this.breakHierarchy = onMod.listen(this.proc.bind(this))
        return this.breakHierarchy
    },
    compose: function(b, func) {
        var a = this
        var result = new Reactant()
        result.setFunc(function() {
            return func(a.value, b.value)
        })

        ////////////////////////////////////////////////////// TODO: These listeners need to be removed when `result` has none of its own listeners, and re-added when it does
        var removeA = a.listen(result.proc.bind(result))
        var removeB = b.listen(result.proc.bind(result))

        return result
    },
    assignment: function(value) {
        this.setFunc(function() { return value })
    },
    equals: function(a, b) {
        if(a === b)
            return true
        if(a && a.equals)
            return a.equals(b)
        if(b && b.equals)
            return b.equals(a)
        return false
    },
    depend: function(transformation, reactant) {
        this.setFunc(function() {
            return transformation(reactant.value)
        }, reactant)
    }
}
Object.defineProperties(
    Reactant.prototype, {
        value: {
            get: function() {
                if(this.valueFunc) return this.valueFunc()
                return undefined
            },
            set: function(value) {
                this.assignment(value)
            }
        },
        not: {
            get: function() { return this.transform(function(a) { return !a }) }
        },
        negative: {
            get: function() { return this.transform(function(a) { return -a }) }
        },
        anyTruthy: {
            get: function() {
                var result = new EventDispatcher()

                this.listen(function(prev, curr) {
                    if(curr)
                        result.proc()
                })
                return result
            }
        },
        anyFalsy: {
            get: function() {
                // TODO: EventDispatcher.transform
                var result = new EventDispatcher()

                this.listen(function(prev, curr) {
                    if(!curr)
                        result.proc()
                })
                return result
            }
        }
    }
)

if(typeof module !== 'undefined') {
    module.exports = Reactant
}
<commit_msg>Changed Reactant.equals to Reactant.equality, fixing a bug. Putting a reactant in a reactant would cause the equality function of the contained reactant to be called when the intended behavior was to just use the === operator.<commit_after>'use strict'

if(typeof require !== 'undefined') {
    var EventDispatcher = require('./eventdispatcher')
}

function Reactant(value) {
    EventDispatcher.call(this)
    this.value = value
    this.breakHierarchy = undefined
}
Reactant.prototype = {
    __proto__: EventDispatcher.prototype,
    setFunc: function(func, onMod) {
        var removal = this.setFuncSilent(func, onMod)
        this.proc()
        return removal
    },
    super_proc: EventDispatcher.prototype.proc,
    proc: function() {
        var prev = this.lastValue
        var curr = this.value
        if(this.equality(prev, curr)) return

        this.lastValue = curr // must be saved here because it might be altered during event propagation
        this.super_proc(prev, curr)
    },
    listen_pc: function(callback) {
        var result = this.listen(callback)  /// TODO: test for removal called during first call of callback
        callback(undefined, this.value)
        return result
    },
    and: function(other) {
        return this.compose(other, function(a, b) { return a && b })
    },
    or: function(other) {
        return this.compose(other, function(a, b) { return a || b })
    },
    transform: function(func) {
        var result = new Reactant()
        result.setFunc(function() { return func(this.value) }.bind(this))

        ////////////////////////////////////////////////////// TODO: This listener needs to be removed when `result` has none of its own listeners, and re-added when it does
        //var remove = this.listen(function() { result.proc() })
        var remove = this.listen(result.proc.bind(result))

        return result
    },
    onCondition: function(predicate) {
        return this.transform(predicate).on(true)
    },
    on: function(target) {
        var result = new EventDispatcher()

        ////////////////////////////////////////////////////// TODO: This listener needs to be removed when `result` has none of its own listeners, and re-added when it does
        this.listen(function(prev, curr) {
            if(this.equality(curr, target))
                result.proc()
        }.bind(this))
        return result
    },
    onNot: function(target) {
        var result = new EventDispatcher()

        ////////////////////////////////////////////////////// TODO: This listener needs to be removed when `result` has none of its own listeners, and re-added when it does
        this.listen(function(prev, curr) {
            if(!this.equality(curr, target))
                result.proc()
        }.bind(this))
        return result
    },
    /////////////////////////////////////////////// TODO: reactant.filter

    setFuncSilent: function(func, onMod) {
        if(this.breakHierarchy) this.breakHierarchy()
        this.breakHierarchy = undefined

        this.valueFunc = func

        if(onMod)
            this.breakHierarchy = onMod.listen(this.proc.bind(this))
        return this.breakHierarchy
    },
    compose: function(b, func) {
        var a = this
        var result = new Reactant()
        result.setFunc(function() {
            return func(a.value, b.value)
        })

        ////////////////////////////////////////////////////// TODO: These listeners need to be removed when `result` has none of its own listeners, and re-added when it does
        var removeA = a.listen(result.proc.bind(result))
        var removeB = b.listen(result.proc.bind(result))

        return result
    },
    assignment: function(value) {
        this.setFunc(function() { return value })
    },
    equality: function(a, b) {
        if(a === b)
            return true
        if(a && a.equals)
            return a.equals(b)
        if(b && b.equals)
            return b.equals(a)
        return false
    },
    depend: function(transformation, reactant) {
        this.setFunc(function() {
            return transformation(reactant.value)
        }, reactant)
    }
}
Object.defineProperties(
    Reactant.prototype, {
        value: {
            get: function() {
                if(this.valueFunc) return this.valueFunc()
                return undefined
            },
            set: function(value) {
                this.assignment(value)
            }
        },
        not: {
            get: function() { return this.transform(function(a) { return !a }) }
        },
        negative: {
            get: function() { return this.transform(function(a) { return -a }) }
        },
        anyTruthy: {
            get: function() {
                var result = new EventDispatcher()

                this.listen(function(prev, curr) {
                    if(curr)
                        result.proc()
                })
                return result
            }
        },
        anyFalsy: {
            get: function() {
                // TODO: EventDispatcher.transform
                var result = new EventDispatcher()

                this.listen(function(prev, curr) {
                    if(!curr)
                        result.proc()
                })
                return result
            }
        }
    }
)

if(typeof module !== 'undefined') {
    module.exports = Reactant
}
<|endoftext|>


<commit_before>/*!
 * Copyright 2014 Apereo Foundation (AF) Licensed under the
 * Educational Community License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 *
 *     http://opensource.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

var _ = require('underscore');
var OAE = require('oae-util/lib/oae');

var TenantsAPI = require('oae-tenants/lib/api');
var TenantNetworksAPI = require('oae-tenants/lib/api.networks');

//////////////////////////////////
// GLOBAL ADMIN TENANT NETWORKS //
//////////////////////////////////

/**
 * @REST getTenantNetworks
 *
 * Get all the tenant networks in the system
 *
 * @Server      admin
 * @Method      GET
 * @Path        /tenantNetworks
 * @Return      {TenantNetworks}                Returns an object of tenant networks and their associated tenants
 */
OAE.globalAdminRouter.on('get', '/api/tenantNetworks', function(req, res) {
    TenantNetworksAPI.getTenantNetworks(req.ctx, function(err, tenantNetworks) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        return res.send(200, tenantNetworks);
    });
});

/**
 * @REST postTenantNetworkCreate
 *
 * Create a tenant network
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenantNetwork/create
 * @FormParam   {string}        displayName     Display name for tenant network to create
 * @Return      {TenantNetwork}                 Returns (via HTTP status 201) object representing newly created network
 */
OAE.globalAdminRouter.on('post', '/api/tenantNetwork/create', function(req, res) {
    TenantNetworksAPI.createTenantNetwork(req.ctx, req.body.displayName, function(err, tenantNetwork) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        return res.send(201, tenantNetwork);
    });
});

/**
 * @REST postTenantNetworkId
 *
 * Update a tenant network
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenantNetwork/{id}
 * @PathParam   {string}                id              Identifier of network to update
 * @FormParam   {string}                displayName     New display name for tenant network
 * @Return      {TenantNetwork}                         Returns object representing updated network
 */
OAE.globalAdminRouter.on('post', '/api/tenantNetwork/:id', function(req, res) {
    TenantNetworksAPI.updateTenantNetwork(req.ctx, req.params.id, req.body.displayName, function(err, tenantNetwork) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        return res.send(200, tenantNetwork);
    });
});

/**
 * @REST deleteTenantNetworkId
 *
 * Delete a tenant network
 *
 * @Server      admin
 * @Method      DELETE
 * @Path        /tenantNetwork/{id}
 * @PathParam   {string}                id      Identifier of network to delete
 */
OAE.globalAdminRouter.on('delete', '/api/tenantNetwork/:id', function(req, res) {
    TenantNetworksAPI.deleteTenantNetwork(req.ctx, req.params.id, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        return res.send(200);
    });
});

/**
 * @REST postTenantNetworkIdAddTenants
 *
 * Add tenants to a tenant network
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenantNetwork/{id}/addTenants
 * @PathParam   {string}                        id          Identifier of network for tenants
 * @FormParam   {string}                        alias       Tenant to add to network
 */
OAE.globalAdminRouter.on('post', '/api/tenantNetwork/:id/addTenants', function(req, res) {
    var tenantAliases = _.isString(req.body.alias) ? [req.body.alias] : req.body.alias;
    TenantNetworksAPI.addTenantAliases(req.ctx, req.params.id, tenantAliases, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        return res.send(200);
    });
});

/**
 * @REST postTenantNetworkIdRemoveTenants
 *
 * Remove tenants from a tenant network
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenantNetwork/{id}/removeTenants
 * @PathParam   {string}                        id          Identifier of network for tenants
 * @FormParam   {string}                        alias       Tenant to remove from network
 */
OAE.globalAdminRouter.on('post', '/api/tenantNetwork/:id/removeTenants', function(req, res) {
    var tenantAliases = _.isString(req.body.alias) ? [req.body.alias] : req.body.alias;
    TenantNetworksAPI.removeTenantAliases(req.ctx, req.params.id, tenantAliases, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        return res.send(200);
    });
});

//////////////////////////
// GLOBAL ADMIN TENANTS //
//////////////////////////

/**
 * @REST getTenants
 *
 * Get a list of all available tenants
 *
 * @Server      admin,tenant
 * @Method      GET
 * @Path        /tenants
 * @Return      {Tenants}                Returns an object of tenant networks and their associated tenants
 */
OAE.globalAdminRouter.on('get', '/api/tenants', function(req, res, next) {
    var tenants = TenantsAPI.getTenants();
    res.send(200, tenants);
});

/**
 * @REST getTenant
 *
 * Get the metadata of the current tenant
 *
 * @Server      admin
 * @Method      GET
 * @Path        /tenant
 * @Return      {Tenant}                        Returns information about the tenant
 */
OAE.globalAdminRouter.on('get', '/api/tenant', function(req, res) {
    res.send(200, req.ctx.tenant());
});

/**
 * @REST getTenantAlias
 *
 * Get a tenant's metadata
 *
 * @Server      admin
 * @Method      GET
 * @Path        /tenant/{alias}
 * @PathParam   {string}           alias        Unique name of tenant to return
 * @Return      {Tenant}                        Returns information about the tenant
 */
OAE.globalAdminRouter.on('get', '/api/tenant/:alias', function(req, res) {
    var tenant = TenantsAPI.getTenant(req.params.alias);
    if (!tenant) {
        return res.send(404, 'There is no tenant with alias ' + req.params.alias);
    }
    res.send(200, tenant);
});

/**
 * @REST postTenantCreate
 *
 * Create a new tenant
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenant/create
 * @FormParam   {string}            alias           Unique short name for tenant in URLs (should be lowercase)
 * @FormParam   {string}            displayName     Display name for tenant
 * @FormParam   {string}            host            Host for tenant
 */
OAE.globalAdminRouter.on('post', '/api/tenant/create', function(req, res) {
    TenantsAPI.createTenant(req.ctx, req.body.alias, req.body.displayName, req.body.host, function(err, tenant) {
        if (err) {
            return res.send(err.code, err.msg);
        }
        res.send(200, tenant);
    });
});

/**
 * @REST postTenantStart
 *
 * Start an existing tenant or tenants
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenant/start
 * @FormParam   {string|string[]}       aliases     Unique name(s) of tenant(s) to stop
 */
OAE.globalAdminRouter.on('post', '/api/tenant/start', function(req, res) {
    // Sets the tenant to be ENABLED by passing in 'false'
    TenantsAPI.disableTenants(req.ctx, req.body.aliases, false, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }
        res.send(200);
    });
});

/**
 * @REST postTenantStop
 *
 * Stop an existing tenant or tenants
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenant/stop
 * @FormParam   {string|string[]}       aliases     Unique name(s) of tenant(s) to stop
 */
OAE.globalAdminRouter.on('post', '/api/tenant/stop', function(req, res) {
    // Sets the tenant to be disabled by passing in 'true'
    TenantsAPI.disableTenants(req.ctx, req.body.aliases, true, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }
        res.send(200);
    });
});

/**
 * @REST postTenantDelete
 *
 * Delete an existing tenant or tenants
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenant/delete
 * @FormParam   {string|string[]}       aliases     Unique name(s) of tenant(s) to delete
 */
OAE.globalAdminRouter.on('post', '/api/tenant/delete', function(req, res) {
    TenantsAPI.deleteTenants(req.ctx, req.body.aliases, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }
        res.send(200);
    });
});

/**
 * @REST postTenantAlias
 *
 * Update a tenant's metadata
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenant/{alias}
 * @PathParam   {string}            alias           Unique name of tenant to modify
 * @BodyParam   {TenantUpdate}      tenantInfo      New tenant information (only include changed properties)
 * @Return      {Tenant}                            Returns updated information about the tenant
 */
OAE.globalAdminRouter.on('post', '/api/tenant/:alias', function(req, res) {
    TenantsAPI.updateTenant(req.ctx, req.params.alias, req.body, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }
        res.send(200);
    });
});

/////////////
// Tenants //
/////////////

/* Documented on admin server */
OAE.tenantRouter.on('get', '/api/tenant', function(req, res) {
    var tenant = TenantsAPI.getTenant(req.ctx.tenant().alias);
    if (!tenant) {
        return res.send(404, 'There is no tenant with alias ' + req.ctx.tenant().alias);
    }
    res.send(200, tenant);
});

/**
 * @REST postTenant
 *
 * Update the current tenant's metadata
 *
 * @Server      tenant
 * @Method      POST
 * @Path        /tenant
 * @BodyParam   {TenantUpdate}      tenantInfo      New tenant information (only include changed properties)
 * @Return      {Tenant}                            Returns updated information about the tenant
 */
OAE.tenantRouter.on('post', '/api/tenant', function(req, res) {
    TenantsAPI.updateTenant(req.ctx, req.ctx.tenant().alias, req.body, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }
        res.send(200);
    });
});

// Add the REST models for Tenants

/**
 * @RESTModel TenantNetworks
 *
 * @Required  []
 * @Property  {TenantNetwork}       {tenantId}          Tenant network for named tenant Identifier
 */

/**
 * @RESTModel TenantNetwork
 *
 * @Required  [displayName, id]
 * @Property  {string}              displayName         Display name for tenant network
 * @Property  {string}              id                  Unique identifier for tenant network
 * @Property  {Tenant[]}            tenants             Tenants associated with tenant network
 */

/**
 * @RESTModel Tenants
 *
 * @Required  []
 * @Property  {Tenant[]}        tenants         Tenants
 */

/**
 * @RESTModel Tenant
 *
 * @Required  [alias,displayName,host]
 * @Property  {string}          alias                   Unique short name for tenant in URLs (should be lowercase)
 * @Property  {string}          displayName             Display name for tenant
 * @Property  {string}          host                    Host address for tenant
 * @Property  {boolean}         active                  Is tenant active?
 * @Property  {boolean}         deleted                 Is tenant deleted?
 * @Property  {boolean}         isGlobalAdminServer     Is "tenant" the global administrative server?
 */

/**
 * @RESTModel TenantUpdate
 *
 * @Required  []
 * @Property  {string}          displayName             Display name for tenant
 * @Property  {string}          host                    Host address for tenant
 */

<commit_msg>move Tenant model to common models<commit_after>/*!
 * Copyright 2014 Apereo Foundation (AF) Licensed under the
 * Educational Community License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 *
 *     http://opensource.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

var _ = require('underscore');
var OAE = require('oae-util/lib/oae');

var TenantsAPI = require('oae-tenants/lib/api');
var TenantNetworksAPI = require('oae-tenants/lib/api.networks');

//////////////////////////////////
// GLOBAL ADMIN TENANT NETWORKS //
//////////////////////////////////

/**
 * @REST getTenantNetworks
 *
 * Get all the tenant networks in the system
 *
 * @Server      admin
 * @Method      GET
 * @Path        /tenantNetworks
 * @Return      {TenantNetworks}                Returns an object of tenant networks and their associated tenants
 */
OAE.globalAdminRouter.on('get', '/api/tenantNetworks', function(req, res) {
    TenantNetworksAPI.getTenantNetworks(req.ctx, function(err, tenantNetworks) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        return res.send(200, tenantNetworks);
    });
});

/**
 * @REST postTenantNetworkCreate
 *
 * Create a tenant network
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenantNetwork/create
 * @FormParam   {string}        displayName     Display name for tenant network to create
 * @Return      {TenantNetwork}                 Returns (via HTTP status 201) object representing newly created network
 */
OAE.globalAdminRouter.on('post', '/api/tenantNetwork/create', function(req, res) {
    TenantNetworksAPI.createTenantNetwork(req.ctx, req.body.displayName, function(err, tenantNetwork) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        return res.send(201, tenantNetwork);
    });
});

/**
 * @REST postTenantNetworkId
 *
 * Update a tenant network
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenantNetwork/{id}
 * @PathParam   {string}                id              Identifier of network to update
 * @FormParam   {string}                displayName     New display name for tenant network
 * @Return      {TenantNetwork}                         Returns object representing updated network
 */
OAE.globalAdminRouter.on('post', '/api/tenantNetwork/:id', function(req, res) {
    TenantNetworksAPI.updateTenantNetwork(req.ctx, req.params.id, req.body.displayName, function(err, tenantNetwork) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        return res.send(200, tenantNetwork);
    });
});

/**
 * @REST deleteTenantNetworkId
 *
 * Delete a tenant network
 *
 * @Server      admin
 * @Method      DELETE
 * @Path        /tenantNetwork/{id}
 * @PathParam   {string}                id      Identifier of network to delete
 */
OAE.globalAdminRouter.on('delete', '/api/tenantNetwork/:id', function(req, res) {
    TenantNetworksAPI.deleteTenantNetwork(req.ctx, req.params.id, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        return res.send(200);
    });
});

/**
 * @REST postTenantNetworkIdAddTenants
 *
 * Add tenants to a tenant network
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenantNetwork/{id}/addTenants
 * @PathParam   {string}                        id          Identifier of network for tenants
 * @FormParam   {string}                        alias       Tenant to add to network
 */
OAE.globalAdminRouter.on('post', '/api/tenantNetwork/:id/addTenants', function(req, res) {
    var tenantAliases = _.isString(req.body.alias) ? [req.body.alias] : req.body.alias;
    TenantNetworksAPI.addTenantAliases(req.ctx, req.params.id, tenantAliases, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        return res.send(200);
    });
});

/**
 * @REST postTenantNetworkIdRemoveTenants
 *
 * Remove tenants from a tenant network
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenantNetwork/{id}/removeTenants
 * @PathParam   {string}                        id          Identifier of network for tenants
 * @FormParam   {string}                        alias       Tenant to remove from network
 */
OAE.globalAdminRouter.on('post', '/api/tenantNetwork/:id/removeTenants', function(req, res) {
    var tenantAliases = _.isString(req.body.alias) ? [req.body.alias] : req.body.alias;
    TenantNetworksAPI.removeTenantAliases(req.ctx, req.params.id, tenantAliases, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }

        return res.send(200);
    });
});

//////////////////////////
// GLOBAL ADMIN TENANTS //
//////////////////////////

/**
 * @REST getTenants
 *
 * Get a list of all available tenants
 *
 * @Server      admin,tenant
 * @Method      GET
 * @Path        /tenants
 * @Return      {Tenants}                Returns an object of tenant networks and their associated tenants
 */
OAE.globalAdminRouter.on('get', '/api/tenants', function(req, res, next) {
    var tenants = TenantsAPI.getTenants();
    res.send(200, tenants);
});

/**
 * @REST getTenant
 *
 * Get the metadata of the current tenant
 *
 * @Server      admin
 * @Method      GET
 * @Path        /tenant
 * @Return      {Tenant}                        Returns information about the tenant
 */
OAE.globalAdminRouter.on('get', '/api/tenant', function(req, res) {
    res.send(200, req.ctx.tenant());
});

/**
 * @REST getTenantAlias
 *
 * Get a tenant's metadata
 *
 * @Server      admin
 * @Method      GET
 * @Path        /tenant/{alias}
 * @PathParam   {string}           alias        Unique name of tenant to return
 * @Return      {Tenant}                        Returns information about the tenant
 */
OAE.globalAdminRouter.on('get', '/api/tenant/:alias', function(req, res) {
    var tenant = TenantsAPI.getTenant(req.params.alias);
    if (!tenant) {
        return res.send(404, 'There is no tenant with alias ' + req.params.alias);
    }
    res.send(200, tenant);
});

/**
 * @REST postTenantCreate
 *
 * Create a new tenant
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenant/create
 * @FormParam   {string}            alias           Unique short name for tenant in URLs (should be lowercase)
 * @FormParam   {string}            displayName     Display name for tenant
 * @FormParam   {string}            host            Host for tenant
 */
OAE.globalAdminRouter.on('post', '/api/tenant/create', function(req, res) {
    TenantsAPI.createTenant(req.ctx, req.body.alias, req.body.displayName, req.body.host, function(err, tenant) {
        if (err) {
            return res.send(err.code, err.msg);
        }
        res.send(200, tenant);
    });
});

/**
 * @REST postTenantStart
 *
 * Start an existing tenant or tenants
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenant/start
 * @FormParam   {string|string[]}       aliases     Unique name(s) of tenant(s) to stop
 */
OAE.globalAdminRouter.on('post', '/api/tenant/start', function(req, res) {
    // Sets the tenant to be ENABLED by passing in 'false'
    TenantsAPI.disableTenants(req.ctx, req.body.aliases, false, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }
        res.send(200);
    });
});

/**
 * @REST postTenantStop
 *
 * Stop an existing tenant or tenants
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenant/stop
 * @FormParam   {string|string[]}       aliases     Unique name(s) of tenant(s) to stop
 */
OAE.globalAdminRouter.on('post', '/api/tenant/stop', function(req, res) {
    // Sets the tenant to be disabled by passing in 'true'
    TenantsAPI.disableTenants(req.ctx, req.body.aliases, true, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }
        res.send(200);
    });
});

/**
 * @REST postTenantDelete
 *
 * Delete an existing tenant or tenants
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenant/delete
 * @FormParam   {string|string[]}       aliases     Unique name(s) of tenant(s) to delete
 */
OAE.globalAdminRouter.on('post', '/api/tenant/delete', function(req, res) {
    TenantsAPI.deleteTenants(req.ctx, req.body.aliases, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }
        res.send(200);
    });
});

/**
 * @REST postTenantAlias
 *
 * Update a tenant's metadata
 *
 * @Server      admin
 * @Method      POST
 * @Path        /tenant/{alias}
 * @PathParam   {string}            alias           Unique name of tenant to modify
 * @BodyParam   {TenantUpdate}      tenantInfo      New tenant information (only include changed properties)
 * @Return      {Tenant}                            Returns updated information about the tenant
 */
OAE.globalAdminRouter.on('post', '/api/tenant/:alias', function(req, res) {
    TenantsAPI.updateTenant(req.ctx, req.params.alias, req.body, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }
        res.send(200);
    });
});

/////////////
// Tenants //
/////////////

/* Documented on admin server */
OAE.tenantRouter.on('get', '/api/tenant', function(req, res) {
    var tenant = TenantsAPI.getTenant(req.ctx.tenant().alias);
    if (!tenant) {
        return res.send(404, 'There is no tenant with alias ' + req.ctx.tenant().alias);
    }
    res.send(200, tenant);
});

/**
 * @REST postTenant
 *
 * Update the current tenant's metadata
 *
 * @Server      tenant
 * @Method      POST
 * @Path        /tenant
 * @BodyParam   {TenantUpdate}      tenantInfo      New tenant information (only include changed properties)
 * @Return      {Tenant}                            Returns updated information about the tenant
 */
OAE.tenantRouter.on('post', '/api/tenant', function(req, res) {
    TenantsAPI.updateTenant(req.ctx, req.ctx.tenant().alias, req.body, function(err) {
        if (err) {
            return res.send(err.code, err.msg);
        }
        res.send(200);
    });
});

// Add the REST models for Tenants

/**
 * @RESTModel TenantNetworks
 *
 * @Required  []
 * @Property  {TenantNetwork}       {tenantId}          Tenant network for named tenant Identifier
 */

/**
 * @RESTModel TenantNetwork
 *
 * @Required  [displayName, id]
 * @Property  {string}              displayName         Display name for tenant network
 * @Property  {string}              id                  Unique identifier for tenant network
 * @Property  {Tenant[]}            tenants             Tenants associated with tenant network
 */

/**
 * @RESTModel Tenants
 *
 * @Required  []
 * @Property  {Tenant[]}        tenants         Tenants
 */

/**
 * @RESTModel TenantUpdate
 *
 * @Required  []
 * @Property  {string}          displayName             Display name for tenant
 * @Property  {string}          host                    Host address for tenant
 */

<|endoftext|>