<commit_before>/* eslint-disable no-console */

var start = Date.now();

// ///////////////////////// //
//          IMPORTS          //
// ///////////////////////// //

var fs = require('fs-extra');
var path = require('path');
var exec = require('child_process').execSync;
var NwBuilder = require('nw-builder');
var rcedit = require('rcedit');
var zip = require('7zip-bin');

// ///////////////////////// //
//          SETTINGS         //
// ///////////////////////// //

var year = '2017';
var company = 'Open Indy Brigade';
var description = 'Find accessibility issues in webpages.';
var platforms = [ process.platform ];

var nwBuildSettings = {
    version: '0.14.7',
    flavor: 'normal',
    appName: 'Koa11y',
    appVersion: '3.0.0',
    files: [
        './_fonts/**/*',
        './_img/**/*',
        './_markup/**/*',
        './_scripts/**/*',
        './_style/style.css',
        './_style/ven.bootstrap.css',
        './_style/ven.bootstrap/bootstrap.css',
        './_style/ven.bootswatch/flatly.min.css',
        './index.htm',
        './package.json',
        './LICENSE'
    ],
    winIco: './_img/fav.ico',
    platforms: platforms,
    zip: false
};

var junk = [
    'd3dcompiler_47.dll',
    'dbghelp.dll',
    'libEGL.dll',
    'libexif.dll',
    'libGLESv2.dll',
    'nw_200_percent.pak',
    'snapshot_blob.bin',
    'package.nw/node_modules/vue/src/',
    'package.nw/node_modules/vue/types/',
    'package.nw/node_modules/vue/dist/vue.common.js',
    'package.nw/node_modules/vue/dist/vue.esm.js',
    'package.nw/node_modules/vue/dist/vue.js',
    'package.nw/node_modules/vue/dist/vue.runtime.common.js',
    'package.nw/node_modules/vue/dist/vue.runtime.esm.js',
    'package.nw/node_modules/vue/dist/vue.runtime.js',
    'package.nw/node_modules/vue/dist/vue.runtime.min.js',
    'package.nw/node_modules/performance-now/src/',
    'package.nw/node_modules/performance-now/test/',
    'package.nw/node_modules/ajv/dist/ajv.min.js.map',
    'package.nw/node_modules/ajv/scripts/',
    'package.nw/node_modules/ajv/lib/ajv.d.ts',
    'package.nw/node_modules/ajv/dist/',
    'package.nw/node_modules/truffler/example/',
    'package.nw/node_modules/truffler/test/',
    'package.nw/node_modules/hawk/example/',
    'package.nw/node_modules/hawk/test/',
    'package.nw/node_modules/hawk/images/',
    'package.nw/node_modules/pa11y/example/',
    'package.nw/node_modules/pa11y/test/',
    'package.nw/node_modules/es6-promise/es6-promise.d.ts',
    '_style'
];

// ///////////////////////// //
//         FUNCTIONS         //
// ///////////////////////// //

function updateExe (done) {
    var executable = './build/' + nwBuildSettings.appName + '/win32/' + nwBuildSettings.appName + '.exe';

    var options = {
        'version-string': {
            'CompanyName': company,
            'FileDescription': description,
            'LegalCopyright': 'Copyright ' + year + '. ' + company,
            'ProductName': nwBuildSettings.appName
        },
        'file-version': nwBuildSettings.appVersion,
        'product-version': nwBuildSettings.appVersion
    };

    if (process.platform !== 'win32') {
        done();
    } else {
        rcedit(executable, options, function (a, b, c) {
            if (a) {
                console.log(a);
            }
            if (b) {
                console.log(b);
            }
            if (c) {
                console.log(c);
            }
            if (typeof(done) === 'function') {
                done();
            }
        });
    }
}

function copyManifest () {
    var manifest = fs.readJsonSync('./package.json');
    manifest.devDependencies = {};
    var output = JSON.stringify(manifest, null, 2);
    if (process.platform === 'win32') {
        fs.writeFileSync('./build/' + nwBuildSettings.appName + '/win32/package.json', output);
    } else if (process.platform === 'linux') {
        fs.writeFileSync('./build/' + nwBuildSettings.appName + '/linux32/package.json', output);
        fs.writeFileSync('./build/' + nwBuildSettings.appName + '/linux64/package.json', output);
    } else if (process.platform === 'darwin') {
        // Stub
    }
}

function changeDirectoryToBuildFolder () {
    var subfolder = '';
    var sub
    if (process.platform === 'win32') {
        subfolder = 'win32';
    } else if (process.platform === 'linux' && process.arch === 'ia32') {
        subfolder = 'linux32';
    } else if (process.platform === 'linux' && process.arch === 'x64') {
        subfolder = 'linux64';
    }

    var buildFolder = path.join(process.cwd(), 'build', nwBuildSettings.appName, subfolder);
    process.chdir(buildFolder);
}

function npmInstallBuildFolder () {
    exec('npm install');
}

function moveFilesIntoPackageNW () {
    console.log(' ∙ Started moving files into package.nw');
    nwBuildSettings.files.forEach(function (file) {
        var src = file.replace('/**/*', '');
        var dest = path.join('package.nw', src);

        fs.moveSync(src, dest);
        console.log('  ∙ moved ' + src.replace('./', ''));
    });
    fs.moveSync('./node_modules', path.join('.', 'package.nw', 'node_modules'));
    console.log('  ∙ moved node_modules');
}

function removeJunk () {
    console.log(' ∙ Started removing junk files');
    junk.forEach(function (item) {
        var file = path.join(process.cwd(), item);
        fs.removeSync(file);
        console.log('  ∙ deleted ' + item);
    });
}

function runApp () {
    exec(nwBuildSettings.appName + '.exe');
}

function goUpOneDirectory () {
    process.chdir('..');
}

function renameBuiltFolder () {
    fs.renameSync('./win32/', './' + nwBuildSettings.appName);
}

function zipApp () {
    var zipExe = zip.path7za;
    var buildInput = '';
    var outputZip = '';
    var filename = nwBuildSettings.appName + '_' + nwBuildSettings.appVersion + '.zip';
    if (process.platform === 'win32') {
        buildInput = nwBuildSettings.appName;
        outputZip = path.join('WIN_' + filename);
    } else if (process.platform === 'darwin') {
        buildInput = path.join(nwBuildSettings.appName + '.app');
        outputZip = path.join('OSX_' + filename);
    } else if (process.platform === 'linux') {
        buildInput = path.join('linux64');
        outputZip = path.join('LIN64_' + filename);
    }

    fs.removeSync(outputZip);
    // a     = create archive
    // -bd   = do not display a progress bar in the CLI
    // -tzip = create a zip formatted file
    // -mx=9 = use maximum compression
    // -y    = auto answer yes to all prompts
    exec(zipExe + ' a -tzip -mx=9 -y "' + outputZip + '" "' + buildInput) + '"';
    if (process.platform === 'linux') {
        buildInput = path.join('linux32');
        outputZip = path.join('LIN32_' + filename);
        fs.removeSync(outputZip);
        exec(zipExe + ' a -tzip -mx=9 -y ' + outputZip + ' ' + buildInput);
    }
}

function totalBuildTime () {
    var done = Date.now() - start;
    var time = Math.round(done / 1000);
    var minutes = Math.floor(time / 60);
    var seconds = time - (minutes * 60);
    seconds = (new Array(3).join('0') + seconds).slice(-2);

    var finalTime = minutes + ':' + seconds;
    var timeMinutes = '\nBuild took ' + finalTime;
    return timeMinutes;
}

// ///////////////////////// //
//         EXECUTION         //
// ///////////////////////// //

var nw = new NwBuilder(nwBuildSettings);

nw.on('log', function (msg) {
    console.log(msg);
});

nw.build().then(function () {
    console.log(' ∙ NW-Builder Complete');
    updateExe(function () {
        if (process.platform === 'win32') {
            console.log(' ∙ Updated ' + nwBuildSettings.appName + '.exe');
        }

        copyManifest();
        console.log(' ∙ Copied package.json');

        changeDirectoryToBuildFolder();
        console.log(' ∙ cd to build folder');

        npmInstallBuildFolder();
        console.log(' ∙ npm install build folder');

        moveFilesIntoPackageNW();
        console.log(' ∙ Finished moving files into package.nw');

    /*
        removeJunk();
        console.log(' ∙ Finished removing junk files');

        runApp();
        console.log(' ∙ Ran app');

        goUpOneDirectory();
        console.log(' ∙ Went up one directory');

        renameBuiltFolder();
        console.log(' ∙ Renamed built folder');

        zipApp();
        console.log(' ∙ Zipped app');

        console.log(totalBuildTime());
    */
    });
}).catch(function (err) {
    console.log('nw-builder err', err);
});
<commit_msg>Updated linux buildscript<commit_after>/* eslint-disable no-console */

var start = Date.now();

// ///////////////////////// //
//          IMPORTS          //
// ///////////////////////// //

var fs = require('fs-extra');
var path = require('path');
var exec = require('child_process').execSync;
var NwBuilder = require('nw-builder');
var rcedit = require('rcedit');
var zip = require('7zip-bin');

// ///////////////////////// //
//          SETTINGS         //
// ///////////////////////// //

var year = '2017';
var company = 'Open Indy Brigade';
var description = 'Find accessibility issues in webpages.';

var platform = process.platform;
if (process.platform === 'linux' && process.arch === 'ia32') {
    platform = 'linux32';
} else if (process.platform === 'linux' && process.arch === 'x64') {
    platform = 'linux64';
} else if (process.platform === 'darwin') {
    platform = 'osx64';
}

var nwBuildSettings = {
    version: '0.14.7',
    flavor: 'normal',
    appName: 'Koa11y',
    appVersion: '3.0.0',
    files: [
        './_fonts/**/*',
        './_img/**/*',
        './_markup/**/*',
        './_scripts/**/*',
        './_style/style.css',
        './_style/ven.bootstrap.css',
        './_style/ven.bootstrap/bootstrap.css',
        './_style/ven.bootswatch/flatly.min.css',
        './index.htm',
        './package.json',
        './LICENSE'
    ],
    winIco: './_img/fav.ico',
    platforms: [ platform ],
    zip: false
};

var junk = [
    'd3dcompiler_47.dll',
    'dbghelp.dll',
    'libEGL.dll',
    'libexif.dll',
    'libGLESv2.dll',
    'nw_200_percent.pak',
    'snapshot_blob.bin',
    'package.nw/node_modules/vue/src/',
    'package.nw/node_modules/vue/types/',
    'package.nw/node_modules/vue/dist/vue.common.js',
    'package.nw/node_modules/vue/dist/vue.esm.js',
    'package.nw/node_modules/vue/dist/vue.js',
    'package.nw/node_modules/vue/dist/vue.runtime.common.js',
    'package.nw/node_modules/vue/dist/vue.runtime.esm.js',
    'package.nw/node_modules/vue/dist/vue.runtime.js',
    'package.nw/node_modules/vue/dist/vue.runtime.min.js',
    'package.nw/node_modules/performance-now/src/',
    'package.nw/node_modules/performance-now/test/',
    'package.nw/node_modules/ajv/dist/ajv.min.js.map',
    'package.nw/node_modules/ajv/scripts/',
    'package.nw/node_modules/ajv/lib/ajv.d.ts',
    'package.nw/node_modules/ajv/dist/',
    'package.nw/node_modules/truffler/example/',
    'package.nw/node_modules/truffler/test/',
    'package.nw/node_modules/hawk/example/',
    'package.nw/node_modules/hawk/test/',
    'package.nw/node_modules/hawk/images/',
    'package.nw/node_modules/pa11y/example/',
    'package.nw/node_modules/pa11y/test/',
    'package.nw/node_modules/es6-promise/es6-promise.d.ts',
    '_style'
];

// ///////////////////////// //
//         FUNCTIONS         //
// ///////////////////////// //

function updateExe (done) {
    var extension = '';
    if (platform === 'win32') {
        extension = '.exe';
    }
    var executable = './build/' + nwBuildSettings.appName + '/' + platform + '/' + nwBuildSettings.appName + extension;

    var options = {
        'version-string': {
            'CompanyName': company,
            'FileDescription': description,
            'LegalCopyright': 'Copyright ' + year + '. ' + company,
            'ProductName': nwBuildSettings.appName
        },
        'file-version': nwBuildSettings.appVersion,
        'product-version': nwBuildSettings.appVersion
    };

    if (platform === 'win32') {
        rcedit(executable, options, function (a, b, c) {
            if (a) {
                console.log(a);
            }
            if (b) {
                console.log(b);
            }
            if (c) {
                console.log(c);
            }
            if (typeof(done) === 'function') {
                done();
            }
        });
    } else if (platform === 'linux64' || platform === 'linux32') {
        exec('chmod +x ' + executable);

        var fileName = 'Koa11y.desktop';
        var fileData =
            '[Desktop Entry]\n' +
            'Type=Application\n' +
            'Terminal=true\n' +
            'Name=Run ' + nwBuildSettings.appName + '\n' +
            'Icon=utilities-terminal\n' +
            'Exec=gnome-terminal -e "bash -c \'./' + nwBuildSettings.appName + ';$SHELL\'"\n' +
            'Categories=Application;';
        var filePath = './build/' + nwBuildSettings.appName + '/' + platform + '/' + fileName;
        fs.writeFileSync(filePath, fileData);

        // TODO: On Linux auto-make the exe runnable, or a runnable .desktop file

        exec('chmod +x ' + filePath);
        done();
    } else {
        done();
    }
}

function copyManifest () {
    var manifest = fs.readJsonSync('./package.json');
    manifest.devDependencies = {};
    var output = JSON.stringify(manifest, null, 2);
    fs.writeFileSync('./build/' + nwBuildSettings.appName + '/' + platform + '/package.json', output);
}

function changeDirectoryToBuildFolder () {
    var buildFolder = path.join(process.cwd(), 'build', nwBuildSettings.appName, platform);
    process.chdir(buildFolder);
}

function npmInstallBuildFolder () {
    exec('npm install');
}

function moveFilesIntoPackageNW () {
    console.log(' ∙ Started moving files into package.nw');
    nwBuildSettings.files.forEach(function (file) {
        var src = file.replace('/**/*', '');
        var dest = path.join('package.nw', src);

        fs.moveSync(src, dest);
        console.log('  ∙ moved ' + src.replace('./', ''));
    });
    fs.moveSync('./node_modules', path.join('.', 'package.nw', 'node_modules'));
    console.log('  ∙ moved node_modules');
}

function removeJunk () {
    console.log(' ∙ Started removing junk files');
    junk.forEach(function (item) {
        var file = path.join(process.cwd(), item);
        fs.removeSync(file);
        console.log('  ∙ deleted ' + item);
    });
}

function runApp () {
    var extension = '';
    if (platform === 'win32') {
        extension = '.exe';
    }
    if (platform === 'win32') {
        exec(nwBuildSettings.appName + extension);
    } else {
        console.log('WARNING: WE STILL DON\'T KNOW HOW TO AUTO RUN EXE ON LINUX');
    }
}

function goUpOneDirectory () {
    process.chdir('..');
}

function renameBuiltFolder () {
    fs.renameSync('./win32/', './' + nwBuildSettings.appName);
}

function zipApp () {
    var zipExe = zip.path7za;
    var buildInput = '';
    var outputZip = '';
    var filename = nwBuildSettings.appName + '_' + nwBuildSettings.appVersion + '.zip';
    if (process.platform === 'win32') {
        buildInput = nwBuildSettings.appName;
        outputZip = path.join('WIN_' + filename);
    } else if (process.platform === 'darwin') {
        buildInput = path.join(nwBuildSettings.appName + '.app');
        outputZip = path.join('OSX_' + filename);
    } else if (process.platform === 'linux') {
        buildInput = path.join('linux64');
        outputZip = path.join('LIN64_' + filename);
    }

    fs.removeSync(outputZip);
    // a     = create archive
    // -bd   = do not display a progress bar in the CLI
    // -tzip = create a zip formatted file
    // -mx=9 = use maximum compression
    // -y    = auto answer yes to all prompts
    exec(zipExe + ' a -tzip -mx=9 -y "' + outputZip + '" "' + buildInput) + '"';
    if (process.platform === 'linux') {
        buildInput = path.join('linux32');
        outputZip = path.join('LIN32_' + filename);
        fs.removeSync(outputZip);
        exec(zipExe + ' a -tzip -mx=9 -y ' + outputZip + ' ' + buildInput);
    }
}

function totalBuildTime () {
    var done = Date.now() - start;
    var time = Math.round(done / 1000);
    var minutes = Math.floor(time / 60);
    var seconds = time - (minutes * 60);
    seconds = (new Array(3).join('0') + seconds).slice(-2);

    var finalTime = minutes + ':' + seconds;
    var timeMinutes = '\nBuild took ' + finalTime;
    return timeMinutes;
}

// ///////////////////////// //
//           CLEAN           //
// ///////////////////////// //

fs.removeSync('./build');

// ///////////////////////// //
//         EXECUTION         //
// ///////////////////////// //

var nw = new NwBuilder(nwBuildSettings);

nw.on('log', function (msg) {
    console.log(msg);
});

nw.build().then(function () {
    console.log(' ∙ NW-Builder Complete');
    updateExe(function () {
        if (process.platform === 'win32') {
            console.log(' ∙ Updated ' + nwBuildSettings.appName + '.exe');
        }

        copyManifest();
        console.log(' ∙ Copied package.json');

        changeDirectoryToBuildFolder();
        console.log(' ∙ cd to build folder');

        npmInstallBuildFolder();
        console.log(' ∙ npm install build folder');

        moveFilesIntoPackageNW();
        console.log(' ∙ Finished moving files into package.nw');

        removeJunk();
        console.log(' ∙ Finished removing junk files');

        runApp();
        console.log(' ∙ Ran app');

        goUpOneDirectory();
        console.log(' ∙ Went up one directory');

    /*
        renameBuiltFolder();
        console.log(' ∙ Renamed built folder');

        zipApp();
        console.log(' ∙ Zipped app');

        console.log(totalBuildTime());
    */
    });
}).catch(function (err) {
    console.log('nw-builder err', err);
});
<|endoftext|>


<commit_before>import { getThumbnailPath } from '@vue-storefront/core/helpers'

export const thumbnail = {
  methods: {
    /**
     * Return thumbnail URL for specific base url and path
     * @param {string} relativeUrl
     * @param {number} width
     * @param {number} height
     * @param {string} pathType
     */
    getThumbnail: (relativeUrl, width, height, pathType) => getThumbnailPath(relativeUrl, width, height, pathType),

    /**
     * Return thumbnail URL for specific base url using media path
     * @param {string} relativeUrl
     * @param {number} width
     * @param {number} height
     */
    getMediaThumbnail: (relativeUrl, width, height) => getThumbnailPath(relativeUrl, width, height, 'media')
  }
}
<commit_msg>Add doc comments to thumbail.js<commit_after>import { getThumbnailPath } from '@vue-storefront/core/helpers'

export const thumbnail = {
  methods: {
    /**
     * Return thumbnail URL for specific base url and path
     * @param {string} relativeUrl
     * @param {number} width
     * @param {number} height
     * @param {string} pathType
     * @returns {string}
     */
    getThumbnail: (relativeUrl, width, height, pathType) => getThumbnailPath(relativeUrl, width, height, pathType),

    /**
     * Return thumbnail URL for specific base url using media path
     * @param {string} relativeUrl
     * @param {number} width
     * @param {number} height
     * @returns {string}
     */
    getMediaThumbnail: (relativeUrl, width, height) => getThumbnailPath(relativeUrl, width, height, 'media')
  }
}
<|endoftext|>


<commit_before>
var debug = require('debug')('rf:renderMiddleware'),
	React = require('react/addons'),
	RequestContext = require('./context/RequestContext'),
	ClientCssHelper = require('./util/ClientCssHelper'),
	Q = require('q'),
	config = require('./config'),
	ExpressServerRequest = require("./ExpressServerRequest"),
	PageUtil = require("./util/PageUtil"),
	PromiseUtil = require("./util/PromiseUtil");

// TODO FIXME ??
// It *might* be worthwhile to get rid of all the closure-y things in render()
// https://developers.google.com/speed/articles/optimizing-javascript

var DATA_LOAD_WAIT = 250;

class Renderer {

	constructor (context) {
		this.context = context;
		this.router = context.router;
		this._userDataPromise = context.loadUserData();
	}

	render (handlerResult) {
		debug("Triggering userData load");
		beginRender(req, res, start, context, this._userDataPromise, handlerResult);
	}

}

/**
 * renderMiddleware entrypoint. Called by express for every request.
 */
module.exports = function(routes) {

	return function (req, res, next) {

		var start = new Date();

		debug('request: ', req.path);

		// TODO? pull this context building into its own middleware
		var context = new RequestContext.Builder()
				.setRoutes(routes)
				.setLoaderOpts({}) // TODO FIXME
				.setDefaultXhrHeadersFromRequest(req)
				.create({
					// TODO: context opts?
				});

		// setup navigation handler (TODO: should we have a 'once' version?)
		context.onNavigate( (err, page) => {

			if (err) {
				debug("There was an error:", err);
				if (err.status && err.status === 404) {
					next();
				} else if (err.status === 301 || err.status === 302) {
					res.redirect(err.status, err.redirectUrl);
				} else {
					next(err);
				}
				return;
			}

			debug('Executing navigate action');

			var userDataPromise = context.loadUserData();
			beginRender(req, res, start, context, userDataPromise, page);

		});

		context.navigate(new ExpressServerRequest(req));

	}
}

function beginRender(req, res, start, context, userDataDfd, page) {

	var routeName = context.navigator.getCurrentRoute().name;

	debug("Route Name", routeName);

	// regardless of what happens, write out the header part
	// TODO: should this include the common.js file? seems like it
	// would give it a chance to download and parse while we're loading
	// data
	writeHeader(req, res, routeName, page);

	var doRenderCallback = function () {
		// user data should never be the long pole here.
		userDataDfd.done(function () {
			writeBody(req, res, context, start, page).then(() => {
				writeData(req, res, context, start)
				setupLateArrivals(req, res, context, start);
			});
		});
	}

	// TODO: we probably want a "we're not waiting any longer for this"
	// timeout as well, and cancel the waiting deferreds

	var timeoutExceeded = false;

	// set up a maximum wait time for data loading.
	// if this timeout fires, we render with whatever we have,
	// as best as possible
	var loadWaitHdl = setTimeout(function () {
		debug("Timeout Exceeeded. Rendering...");
		timeoutExceeded = true;
		doRenderCallback();
	}, DATA_LOAD_WAIT);

	// if we happen to load all data before the timeout is exceeded,
	// we clear the timeout, and just render. If this fires after the
	// timeout is exceeded, it's a no-op
	// use .done(...) to propagate caught exceptions properly
	var loader = context.loader;
	loader.whenAllPendingResolve().done(function () {
		if (!timeoutExceeded) {
			debug("Data loaded. Rendering...");
			clearTimeout(loadWaitHdl);
			doRenderCallback();
		}
	});

}

function writeHeader(req, res, routeName, pageObject) {
	debug('Sending header');
	res.type('html');

	var pageHeader = "<!DOCTYPE html>"
		// TODO: we should allow title to be a deferred
		+ renderTitle(pageObject) + "\n"
		+ renderStylesheets(pageObject) + "\n"
		+ renderScripts(pageObject) + "\n"
		+ renderMetaTags(pageObject) + "\n"
		+ "
";
	res.write(pageHeader);
}

function renderTitle (pageObject) {
	if (!pageObject.getTitle) return "";

	return "<title>" + (pageObject.getTitle() || "") + "</title>";
}

function renderMetaTags (pageObject) {
	var metaTags = [ {charset: 'utf-8'} ];

	if (pageObject.getMetaTags) {
		var pageMetaTags = pageObject.getMetaTags();
		if (pageMetaTags.length > 0) {
			metaTags = metaTags.concat(pageMetaTags);
		}
	}

	return metaTags.map( tagData => {
		// TODO: escaping
		var tag = '<meta ';
		tag += Object.keys(tagData).map( metaAttrName => {
			return metaAttrName + '="' + tagData[metaAttrName] + '"';
		}).join(' ');
		tag += '>';
		return tag;
	}).join("\n");
}

function renderScripts(pageObject) {
	if (!pageObject.getHeadScriptFiles) return "";

	// default script
	var scripts = pageObject.getHeadScriptFiles();
	if (scripts && !Array.isArray(scripts)) {
		scripts = [scripts];
	}

	return scripts.map( (scriptPath) => {
		// make sure there's a leading '/'
		return ''
	}).join("\n");

}

function renderStylesheets (pageObject) {
	if (!pageObject.getHeadStylesheet) return "";

	var stylesheet = pageObject.getHeadStylesheet();
	if (!stylesheet) {
		return "";
	}
	return '<link rel="stylesheet" type="text/css" href="' + stylesheet + '" id="'+ ClientCssHelper.PAGE_CSS_NODE_ID + '">'
}

/**
 * Writes out the ReactElements to the response. Returns a promise that fulfills when
 * all the ReactElements have been written out.
 */
function writeBody(req, res, context, start, page) {

	debug("React Rendering");
	// standardize to an array of EarlyPromises of ReactElements
	var elementPromises = PageUtil.standardizeElements(page.getElements());

	// a boolean array to keep track of which elements have already been rendered. this is useful
	// bookkeeping when the timeout happens so we don't double-render anything.
	var rendered = [];

	// I learned how to chain an array of promises from http://bahmutov.calepin.co/chaining-promises.html
	var noTimeoutRenderPromise =  elementPromises.reduce((chain, next, index) => {
		return chain.then((element) => {
	 		if (!rendered[index-1]) renderElement(res, element, context, index - 1);
	 		rendered[index - 1] = true;
			return next;
		})
	}).then((element) => {
		// reduce is called length - 1 times. we need to call one final time here to make sure we
		// chain the final promise.
 		if (!rendered[elementPromises.length - 1]) renderElement(res, element, context, elementPromises.length - 1);
 		rendered[elementPromises.length - 1] = true;
	}).catch((err) => {
		debug("Error while rendering", err);
	});

	// set a loading timeout after which we will render everything we have completely synchronously.
	var timeoutRenderDeferred = Q.defer();
	setTimeout(() => {
		elementPromises.forEach((promise, index) => {
			if (!rendered[index]) {
				renderElement(res, promise.getValue(), context, index);
				rendered[index] = true;
			}
		});
		timeoutRenderDeferred.resolve();
	}, DATA_LOAD_WAIT);

	// return a promise that resolves when either the async render OR the timeout sync
	// render happens.
	return PromiseUtil.race(noTimeoutRenderPromise, timeoutRenderDeferred.promise);
}

function renderElement(res, element, context, index) {
	res.write(`<div data-triton-root-id=${index}>`);
	if (element !== null) {
		element = React.addons.cloneWithProps(element, { context: context });
		res.write(React.renderToString(element));
	}
	res.write("
");
}

function writeData(req, res, context, start) {
	debug('Exposing context state');
	res.expose(context.dehydrate(), 'InitialContext');
	res.expose(getNonInternalConfigs(), "Config");

	res.write("
"); //

	var pageFooter = ""
		+ "";

	res.write(pageFooter);

	debug("Content Written: " + (new Date().getTime() - start.getTime()) + "ms");
}

function setupLateArrivals(req, res, context, start) {
	var loader = context.loader;
	var notLoaded = loader.getPendingRequests();

	notLoaded.forEach( pendingRequest => {
		pendingRequest.entry.dfd.promise.then( data => {
			debug("Late arrival: " + pendingRequest.url + ". Arrived " + (new Date().getTime() - start.getTime()) + "ms after page start.");
			res.write("");
		})
	});

	// TODO: maximum-wait-time-exceeded-so-cancel-pending-requests code
	var promises = notLoaded.map( result => result.entry.dfd.promise );
	Q.allSettled(promises).then(function () {
		res.end("");
		debug("All Done: " + (new Date().getTime() - start.getTime()) + "ms");
	});
}

function getNonInternalConfigs() {
	var nonInternal = {};
	var fullConfig = config();
	Object.keys(fullConfig).forEach( configKey => {
		if (configKey !== 'internal') {
			nonInternal[configKey] = fullConfig[configKey];
		}
	});
	return nonInternal;
}
<commit_msg>Triton API Refactor: took out the timeout that's fired by the loader to signal the server to render. Now triton listens to the promises returned from getElements, with a timeout to render partial when needed.<commit_after>
var debug = require('debug')('rf:renderMiddleware'),
	React = require('react/addons'),
	RequestContext = require('./context/RequestContext'),
	ClientCssHelper = require('./util/ClientCssHelper'),
	Q = require('q'),
	config = require('./config'),
	ExpressServerRequest = require("./ExpressServerRequest"),
	PageUtil = require("./util/PageUtil"),
	PromiseUtil = require("./util/PromiseUtil");

// TODO FIXME ??
// It *might* be worthwhile to get rid of all the closure-y things in render()
// https://developers.google.com/speed/articles/optimizing-javascript

var DATA_LOAD_WAIT = 250;

class Renderer {

	constructor (context) {
		this.context = context;
		this.router = context.router;
		this._userDataPromise = context.loadUserData();
	}

	render (handlerResult) {
		debug("Triggering userData load");
		beginRender(req, res, start, context, this._userDataPromise, handlerResult);
	}

}

/**
 * renderMiddleware entrypoint. Called by express for every request.
 */
module.exports = function(routes) {

	return function (req, res, next) {

		var start = new Date();

		debug('request: ', req.path);

		// TODO? pull this context building into its own middleware
		var context = new RequestContext.Builder()
				.setRoutes(routes)
				.setLoaderOpts({}) // TODO FIXME
				.setDefaultXhrHeadersFromRequest(req)
				.create({
					// TODO: context opts?
				});

		// setup navigation handler (TODO: should we have a 'once' version?)
		context.onNavigate( (err, page) => {

			if (err) {
				debug("There was an error:", err);
				if (err.status && err.status === 404) {
					next();
				} else if (err.status === 301 || err.status === 302) {
					res.redirect(err.status, err.redirectUrl);
				} else {
					next(err);
				}
				return;
			}

			debug('Executing navigate action');

			var userDataPromise = context.loadUserData();
			beginRender(req, res, start, context, userDataPromise, page);

		});

		context.navigate(new ExpressServerRequest(req));

	}
}

function beginRender(req, res, start, context, userDataDfd, page) {

	var routeName = context.navigator.getCurrentRoute().name;

	debug("Route Name", routeName);

	// regardless of what happens, write out the header part
	// TODO: should this include the common.js file? seems like it
	// would give it a chance to download and parse while we're loading
	// data
	writeHeader(req, res, routeName, page);

	// user data should never be the long pole here.
	userDataDfd.done(function () {
		writeBody(req, res, context, start, page).then(() => {
			writeData(req, res, context, start)
			setupLateArrivals(req, res, context, start);
		});
	});

	// TODO: we probably want a "we're not waiting any longer for this"
	// timeout as well, and cancel the waiting deferreds

}

function writeHeader(req, res, routeName, pageObject) {
	debug('Sending header');
	res.type('html');

	var pageHeader = "<!DOCTYPE html>"
		// TODO: we should allow title to be a deferred
		+ renderTitle(pageObject) + "\n"
		+ renderStylesheets(pageObject) + "\n"
		+ renderScripts(pageObject) + "\n"
		+ renderMetaTags(pageObject) + "\n"
		+ "
";
	res.write(pageHeader);
}

function renderTitle (pageObject) {
	if (!pageObject.getTitle) return "";

	return "<title>" + (pageObject.getTitle() || "") + "</title>";
}

function renderMetaTags (pageObject) {
	var metaTags = [ {charset: 'utf-8'} ];

	if (pageObject.getMetaTags) {
		var pageMetaTags = pageObject.getMetaTags();
		if (pageMetaTags.length > 0) {
			metaTags = metaTags.concat(pageMetaTags);
		}
	}

	return metaTags.map( tagData => {
		// TODO: escaping
		var tag = '<meta ';
		tag += Object.keys(tagData).map( metaAttrName => {
			return metaAttrName + '="' + tagData[metaAttrName] + '"';
		}).join(' ');
		tag += '>';
		return tag;
	}).join("\n");
}

function renderScripts(pageObject) {
	if (!pageObject.getHeadScriptFiles) return "";

	// default script
	var scripts = pageObject.getHeadScriptFiles();
	if (scripts && !Array.isArray(scripts)) {
		scripts = [scripts];
	}

	return scripts.map( (scriptPath) => {
		// make sure there's a leading '/'
		return ''
	}).join("\n");

}

function renderStylesheets (pageObject) {
	if (!pageObject.getHeadStylesheet) return "";

	var stylesheet = pageObject.getHeadStylesheet();
	if (!stylesheet) {
		return "";
	}
	return '<link rel="stylesheet" type="text/css" href="' + stylesheet + '" id="'+ ClientCssHelper.PAGE_CSS_NODE_ID + '">'
}

/**
 * Writes out the ReactElements to the response. Returns a promise that fulfills when
 * all the ReactElements have been written out.
 */
function writeBody(req, res, context, start, page) {

	debug("React Rendering");
	// standardize to an array of EarlyPromises of ReactElements
	var elementPromises = PageUtil.standardizeElements(page.getElements());

	// a boolean array to keep track of which elements have already been rendered. this is useful
	// bookkeeping when the timeout happens so we don't double-render anything.
	var rendered = [];

	// render the elements in sequence when they resolve (i.e. tell us they are ready).
	// I learned how to chain an array of promises from http://bahmutov.calepin.co/chaining-promises.html
	var noTimeoutRenderPromise =  elementPromises.reduce((chain, next, index) => {
		return chain.then((element) => {
	 		if (!rendered[index-1]) renderElement(res, element, context, index - 1);
	 		rendered[index - 1] = true;
			return next;
		})
	}).then((element) => {
		// reduce is called length - 1 times. we need to call one final time here to make sure we
		// chain the final promise.
 		if (!rendered[elementPromises.length - 1]) renderElement(res, element, context, elementPromises.length - 1);
 		rendered[elementPromises.length - 1] = true;
	}).catch((err) => {
		debug("Error while rendering", err);
	});

	// set up a maximum wait time for data loading. if this timeout fires, we render with whatever we have,
	// as best as possible. note that once the timeout fires, we render everything that's left
	// synchronously.
	var timeoutRenderPromise = Q.delay(DATA_LOAD_WAIT).then(() => {
		// if we rendered everything up to the last element already, just return.
		if (rendered[elementPromises.length - 1]) return;

		debug("Timeout Exceeeded. Rendering...");
		elementPromises.forEach((promise, index) => {
			if (!rendered[index]) {
				renderElement(res, promise.getValue(), context, index);
				rendered[index] = true;
			}
		});
	});

	// return a promise that resolves when either the async render OR the timeout sync
	// render happens.
	return PromiseUtil.race(noTimeoutRenderPromise, timeoutRenderPromise);
}

function renderElement(res, element, context, index) {
	res.write(`<div data-triton-root-id=${index}>`);
	if (element !== null) {
		element = React.addons.cloneWithProps(element, { context: context });
		res.write(React.renderToString(element));
	}
	res.write("
");
}

function writeData(req, res, context, start) {
	debug('Exposing context state');
	res.expose(context.dehydrate(), 'InitialContext');
	res.expose(getNonInternalConfigs(), "Config");

	res.write("
"); //

	var pageFooter = ""
		+ "";

	res.write(pageFooter);

	debug("Content Written: " + (new Date().getTime() - start.getTime()) + "ms");
}

function setupLateArrivals(req, res, context, start) {
	var loader = context.loader;
	var notLoaded = loader.getPendingRequests();

	notLoaded.forEach( pendingRequest => {
		pendingRequest.entry.dfd.promise.then( data => {
			debug("Late arrival: " + pendingRequest.url + ". Arrived " + (new Date().getTime() - start.getTime()) + "ms after page start.");
			res.write("");
		})
	});

	// TODO: maximum-wait-time-exceeded-so-cancel-pending-requests code
	var promises = notLoaded.map( result => result.entry.dfd.promise );
	Q.allSettled(promises).then(function () {
		res.end("");
		debug("All Done: " + (new Date().getTime() - start.getTime()) + "ms");
	});
}

function getNonInternalConfigs() {
	var nonInternal = {};
	var fullConfig = config();
	Object.keys(fullConfig).forEach( configKey => {
		if (configKey !== 'internal') {
			nonInternal[configKey] = fullConfig[configKey];
		}
	});
	return nonInternal;
}
<|endoftext|>


<commit_before>/*
 * Copyright 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
 * Please see distribution for license.
 */
$.register_module({
    name: 'og.common.gadgets.GadgetsContainer',
    dependencies: ['og.common.gadgets.manager', 'og.api.text'],
    obj: function () {
        var api = og.api, tabs_template, overflow_template, dropbox_template, counter = 1,
            header = ' .ui-layout-header';
        var constructor = function (selector_prefix, pane) {
            var initialized = false, loading, gadgets = [], container = this, highlight_timer,
                selector = selector_prefix + pane, $selector = $(selector),
                class_prefix = selector_prefix.substring(1),
                live_id, // active tab id
                overflow = {}, // document offset of overflow panel
                $overflow_panel; // panel that houses non visible tabs
            var extract_id = function (str) {return +str.replace(/^og\-tab\-(\d+)\s(?:.*)$/, '$1');};
            var extract_index = function (id) {
                return gadgets.reduce(function (acc, val, idx) {return acc + (val.id === id ? idx : 0);}, 0)
            };
            /**
             * @param {Number|Null} id
             *        if id is a Number set the active tab to that ID
             *        if id is undefined use the current Live ID and reflow tabs
             *        if id is null set tabs to a single empty tab
             */
            var update_tabs = function (id) {
                var $header = $(selector + header), tabs;
                /**
                 * @param id Id of gadget to show, hide all others
                 */
                var show_gadget = function (id) {
                    $selector.find('.OG-gadget-container [class*=OG-gadget-]').hide()
                        .filter('.OG-gadget-' + id).show();
                    live_id = id;
                };
                /** Manages the widths of the tabs when the panel is resized, the following stages exist:
                 *  0 - all tabs are full size
                 *  1 - all but the active tab are truncated
                 *  2 - tabs are truncated plus some/all are moved to an overflow panel
                 *  3 - same as above with active tab being truncated
                 */
                var reflow = function () {
                    var overflow_buffer = 25, // space for the overflow button
                        buttons_buffer = {'south': 23}, // add extra buffer space if the tabs area has buttons
                        min_tab_width = 50,
                        $tabs_container = $(selector + ' .OG-gadget-tabs'),
                        $tabs = $tabs_container.find('li[class^=og-tab-]'),
                        $active_tab = $(selector + ' .og-active'),
                        $overflow_button = $(selector + ' .og-overflow'),
                        active_tab_width = $active_tab.outerWidth(),
                        num_tabs = $tabs.length,
                        num_inactive_tabs = num_tabs - 1,
                        new_tab_width, // new truncated width of a tab
                        compressed_tabs_width, // new full width of all tabs
                        space_needed, // number of pixles needed to reclaim
                        num_tabs_to_hide, // number of tabs to move to overflow
                        $tabs_to_move, // the actual tabs we are moving
                        // full available width
                        full_width = $tabs_container.width() - (overflow_buffer + (buttons_buffer[pane] || 0)),
                        // the full width of all the tabs
                        tabs_width = Array.prototype.reduce.apply($tabs
                            .map(function () {return $(this).outerWidth();}), [function (a, b) {return a + b;}, 0]),
                        new_window = function (i, dropped) {
                            var prefix = 'gadget.ftl#/gadgetscontainer/', url,
                                index = extract_index(extract_id($(this).attr('class'))),
                                id = gadgets[index].config.options.id,
                                type = gadgets[index].type;
                            switch (type) {
                                case 'timeseries': url = prefix + type + ':' + id; break;
                                case 'surface': url = prefix + type + ':' + id; break;
                                case 'curve': url = prefix + type + ':' + id; break;
                                case 'grid': url = prefix + type + ':' + id; break;
                                case 'data': url = prefix + type + ':' + id; break;
                                case 'depgraph': url = prefix + type + ':' + id; break;
                            }
                            if (!dropped) {
                                window.open(url);
                                setTimeout(container.del.partial(gadgets[i]));
                            }
                        };
                    // stage 1
                    if (tabs_width > full_width) {
                        new_tab_width = ~~((full_width - active_tab_width) / num_inactive_tabs);
                        new_tab_width = new_tab_width < min_tab_width ? min_tab_width : new_tab_width;
                        compressed_tabs_width = (num_inactive_tabs * new_tab_width) + active_tab_width;
                        // stage 2
                        if (compressed_tabs_width > full_width) {
                            if (num_tabs > 1) {
                                $overflow_button.show().on('click', function (e) {
                                    e.stopPropagation();
                                    $overflow_panel.toggle();
                                    var wins = [window].concat(Array.prototype.slice.call(window.frames));
                                    if ($overflow_panel.is(':visible'))
                                        $(wins).on('click.overflow_handler', function () {
                                            $overflow_panel.hide();
                                            $(wins).off('click.overflow_handler');
                                        });
                                    else $(wins).off('click.overflow_handler');
                                });
                            }
                            space_needed = full_width - compressed_tabs_width;
                            num_tabs_to_hide = Math.ceil(Math.abs(space_needed) / new_tab_width);
                            if (num_tabs_to_hide) $overflow_panel.find('ul').html(
                                $tabs_to_move = $tabs.filter(':not(.og-active):lt(' + num_tabs_to_hide + ')')
                            );
                            // stage 3
                            if (num_tabs_to_hide >= num_tabs) $active_tab.width(
                                active_tab_width
                                + (space_needed + ((num_inactive_tabs * min_tab_width) - overflow_buffer))
                                + 'px'
                            );
                        } else {
                            $overflow_panel.hide();
                        }
                        // set inactive tab widths to calculated value
                        $tabs.each(function () {if (!$(this).hasClass('og-active'))
                            $(this).outerWidth(new_tab_width)});
                        // unset width of tabs in overflow panel
                        if ($tabs_to_move) $tabs_to_move.each(function () {$(this).attr('style', '');});
                        // set position of overflow panel
                        overflow.right = $(document).width() - ($overflow_button.offset().left + 25 - 5);
                        overflow.height = $overflow_button.height();
                        overflow.top = $overflow_button.offset().top + overflow.height + 1;
                        $overflow_panel.css({'right': overflow.right + 'px', 'top': overflow.top + 'px'});
                        $tabs.each(function () { // add tooltips to truncated tabs only
                            var $this = $(this);
                            if (!!$this.attr('style')) $this.attr('title', $this.text().trim());
                        });
                    }
                    // implement drag
                    $tabs.each(function (i) {
                        $(this).draggable({
                            cursor: 'move', zIndex: 3, cursorAt: {top: 25}, scroll: false,
                            iframeFix: true, appendTo: 'body', distance: 20,
                            revert: new_window.partial(i),
                            stop: function () {$(this).draggable('option','revert', new_window.partial(i));},
                            helper: function() {return dropbox_template({label: $(this).text().trim()});}
                        }).data({gadget: gadgets[i], handler: function () {container.del(gadgets[i]);}});
                    });
                };
                if (id === null) $header.html(tabs_template({'tabs': [{'name': 'empty'}]})); // empty tabs
                else {
                    if (id === void 0) id = live_id;
                    tabs = gadgets.reduce(function (acc, val) {
                        return acc.push({
                            'type': val.config.type, 'row_name': val.config.row_name, 'col_name': val.config.col_name,
                            'active': (id === val.id), 'delete': true, 'id': val.id
                        }) && acc;
                    }, []);
                    $header.html(tabs_template({'tabs': tabs}));
                    reflow();
                    show_gadget(id);
                }
            };
            /**
             * @param {String|Array} data A String that defines what gadgets to load, or an Array of gadgets to load
             * @param {Number} idx location to add new gadgets
             *
             * The data Array is a list of objects that describe the gadgets to load
             *     obj.gadget   Function
             *     obj.options  Object
             *     obj.name     String
             *     obj.margin   Boolean
             */
            container.add = function (data, index, fingerprint) {
                var panel_container = selector + ' .OG-gadget-container', new_gadgets;
                if (!loading && !initialized)
                    return container.init(), setTimeout(container.add.partial(data, index, fingerprint), 10), container;
                if (!initialized) return setTimeout(container.add.partial(data, index, fingerprint), 10), container;
                if (!data) return container; // no gadgets for this container
                if (!selector) throw new TypeError('GadgetsContainer has not been initialized');
                new_gadgets = data.map(function (obj, idx) {
                    var id, gadget_class = 'OG-gadget-' + (id = counter++), gadget,
                        options = $.extend(true, obj.options || {}, {selector: panel_container + ' .' + gadget_class}),
                        constructor = obj.gadget.split('.').reduce(function (acc, val) {return acc[val];}, window),
                        type = obj.gadget.replace(/^[a-z0-9.-_]+\.([a-z0-9.-_]+?)$/, '$1').toLowerCase();
                    $(panel_container).append('<div class="' + gadget_class + '" />').find('.' + gadget_class)
                        .css({
                            position: 'absolute', top: 0, bottom: 0, left: 0, right: 0,
                            display: idx === data.length - 1 ? 'block' : 'none'
                        });
                    gadgets.splice(index || gadgets.length, 0,
                        gadget = {id: id, config: obj, type: type, gadget: new constructor(options)});
                    if (fingerprint) gadget.fingerprint = fingerprint;
                    return gadget;
                });
                update_tabs(new_gadgets[new_gadgets.length - 1].id);
                return container;
            };
            container.alive = function () {
                gadgets.forEach(function (obj) {obj.gadget.alive();});
                return !!$selector.length;
            };
            container.del = function (obj, silent) {
                var id, index = gadgets.indexOf(obj);
                $(selector + ' .OG-gadget-container .OG-gadget-' + obj.id).remove();
                gadgets[gadgets.length - 1].gadget.alive();
                gadgets.splice(index, 1);
                id = gadgets.length
                    ? live_id === obj.id ? gadgets[gadgets.length - 1].id : live_id
                    : null;
                if (id) gadgets[extract_index(id)].gadget.resize();
                update_tabs(id); // new active tab or empty
                if (!silent) og.common.events.fire(container.events.del, index);
            };
            container.events = {del: [], drop: []};
            container.gadgets = function () {return gadgets;};
            /**
             * Highlight gadget panel with number
             * @param show {Boolean} turn on / off
             * @param strong {Boolean} set to active panel
             */
            container.highlight = function (show, strong) {
                var number = ({'south': 1, 'dock-north': 2, 'dock-center': 3, 'dock-south': 4})[pane],
                    query = $selector.find('.og-highlight'), $html = query.length ? query
                        : $('<div />').text(number).addClass('og-highlight').appendTo($selector);
                clearTimeout(highlight_timer);
                if (show) (strong ? $html.addClass('strong') : $html.removeClass('strong')).show();
                else clearTimeout(highlight_timer), highlight_timer = setTimeout(function () {$html.hide()}, 250);
            };
            container.init = function (data) {
                var toggle_dropbox = function () {
                        var $db = $('.og-drop').length, $dbs_span = $('.OG-dropbox span');
                        if ($db) $dbs_span.removeClass('og-icon-new-window').addClass('og-icon-drop');
                        else $dbs_span.removeClass('og-icon-drop').addClass('og-icon-new-window');
                    };
                loading = true;
                $.when(
                    api.text({module: 'og.analytics.tabs_tash'}),
                    api.text({module: 'og.analytics.tabs_overflow_tash'}),
                    api.text({module: 'og.analytics.dropbox_tash'})
                ).then(function (tabs_tmpl, overflow_tmpl, dropbox_tmpl) {
                    if (!tabs_template) tabs_template = Handlebars.compile(tabs_tmpl);
                    if (!overflow_template) overflow_template = Handlebars.compile(overflow_tmpl);
                    if (!dropbox_template) dropbox_template = Handlebars.compile(dropbox_tmpl);
                    if (!$overflow_panel) $overflow_panel = $(overflow_template({pane: pane})).appendTo('body');
                    initialized = true;
                    loading = false;
                    // setup click handlers
                    $(selector + header + ' , .og-js-overflow-' + pane)
                        // handler for tabs (including the ones in the overflow pane)
                        .on('click', 'li[class^=og-tab-]', function (e) {
                            var id = extract_id($(this).attr('class')), index = extract_index(id);
                            if ($(e.target).hasClass('og-delete')) container.del(gadgets[index]);
                            else if (!$(this).hasClass('og-active')) {
                                update_tabs(id || null);
                                if (id) gadgets[index].gadget.resize();
                            }
                        });
                    if (!data) update_tabs(null); else container.add(data);
                    // implement drop
                    $selector.droppable({
                        hoverClass: 'og-drop',
                        accept: function (draggable) {return $(draggable).is('li[class^=og-tab-]')}, // is it a tab...
                        over: function () {setTimeout(toggle_dropbox)}, // can't guarantee over and out fire in correct
                        out: function () {setTimeout(toggle_dropbox)},  // order, toggle function seems to solve issue
                        drop: function(e, ui) {
                            var has_ancestor = function (elm, sel) {return $(elm).closest('.' + sel).length},
                                pane_class = class_prefix + pane,
                                overflow_class = 'og-js-overflow-' + pane,
                                data = ui.draggable.data(),
                                gadget = data.gadget.config.options,
                                re = new RegExp(selector_prefix + '(.*?)\\s');
                            if (has_ancestor(ui.draggable, pane_class) || has_ancestor(ui.draggable, overflow_class)) {
                                ui.draggable.draggable('option', 'revert', true);
                            } else {
                                ui.draggable.draggable('option', 'revert', false);
                                gadget.selector = gadget.selector.replace(re, selector_prefix + pane + ' ');
                                if (og.common.events.fire(container.events.drop, data.gadget.config))
                                    container.add([data.gadget.config]);
                                setTimeout(data.handler); // setTimeout to ensure handler is called after drag evt ends
                            }
                        }
                    });
                    og.common.gadgets.manager.register(container);
                });
                return container;
            };
            container.resize = function () {
                gadgets.forEach(function (obj) {
                    if (obj.gadget.alive()) {
                        $(selector + ' .ui-layout-content').show();
                        update_tabs();
                        obj.gadget.resize();
                    }
                });
            }
            container.verify = function (fingerprints) {
                if (!initialized) return setTimeout(container.verify.partial(fingerprints), 10), container;
                var gadget_prints = gadgets.pluck('fingerprint'), keep;
                keep = (fingerprints || []).map(function (fingerprint) {
                    var index;
                    if (!fingerprint) return null;
                    if (~(index = gadget_prints.indexOf(fingerprint))) return index; else return null;
                }).reduce(function (acc, val) {if (val !== null) acc[val] = null; return acc;}, {});
                gadgets.forEach(function (gadget, index) {if (!(index in keep)) container.del(gadgets[index], true);});
                return container;
            };
        };
        constructor.prototype.on = og.common.events.on;
        return constructor;
    }
});<commit_msg>only short circuit on explicit false returns in gadget container drop listener<commit_after>/*
 * Copyright 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
 * Please see distribution for license.
 */
$.register_module({
    name: 'og.common.gadgets.GadgetsContainer',
    dependencies: ['og.common.gadgets.manager', 'og.api.text'],
    obj: function () {
        var api = og.api, tabs_template, overflow_template, dropbox_template, counter = 1,
            header = ' .ui-layout-header';
        var constructor = function (selector_prefix, pane) {
            var initialized = false, loading, gadgets = [], container = this, highlight_timer,
                selector = selector_prefix + pane, $selector = $(selector),
                class_prefix = selector_prefix.substring(1),
                live_id, // active tab id
                overflow = {}, // document offset of overflow panel
                $overflow_panel; // panel that houses non visible tabs
            var extract_id = function (str) {return +str.replace(/^og\-tab\-(\d+)\s(?:.*)$/, '$1');};
            var extract_index = function (id) {
                return gadgets.reduce(function (acc, val, idx) {return acc + (val.id === id ? idx : 0);}, 0)
            };
            /**
             * @param {Number|Null} id
             *        if id is a Number set the active tab to that ID
             *        if id is undefined use the current Live ID and reflow tabs
             *        if id is null set tabs to a single empty tab
             */
            var update_tabs = function (id) {
                var $header = $(selector + header), tabs;
                /**
                 * @param id Id of gadget to show, hide all others
                 */
                var show_gadget = function (id) {
                    $selector.find('.OG-gadget-container [class*=OG-gadget-]').hide()
                        .filter('.OG-gadget-' + id).show();
                    live_id = id;
                };
                /** Manages the widths of the tabs when the panel is resized, the following stages exist:
                 *  0 - all tabs are full size
                 *  1 - all but the active tab are truncated
                 *  2 - tabs are truncated plus some/all are moved to an overflow panel
                 *  3 - same as above with active tab being truncated
                 */
                var reflow = function () {
                    var overflow_buffer = 25, // space for the overflow button
                        buttons_buffer = {'south': 23}, // add extra buffer space if the tabs area has buttons
                        min_tab_width = 50,
                        $tabs_container = $(selector + ' .OG-gadget-tabs'),
                        $tabs = $tabs_container.find('li[class^=og-tab-]'),
                        $active_tab = $(selector + ' .og-active'),
                        $overflow_button = $(selector + ' .og-overflow'),
                        active_tab_width = $active_tab.outerWidth(),
                        num_tabs = $tabs.length,
                        num_inactive_tabs = num_tabs - 1,
                        new_tab_width, // new truncated width of a tab
                        compressed_tabs_width, // new full width of all tabs
                        space_needed, // number of pixles needed to reclaim
                        num_tabs_to_hide, // number of tabs to move to overflow
                        $tabs_to_move, // the actual tabs we are moving
                        // full available width
                        full_width = $tabs_container.width() - (overflow_buffer + (buttons_buffer[pane] || 0)),
                        // the full width of all the tabs
                        tabs_width = Array.prototype.reduce.apply($tabs
                            .map(function () {return $(this).outerWidth();}), [function (a, b) {return a + b;}, 0]),
                        new_window = function (i, dropped) {
                            var prefix = 'gadget.ftl#/gadgetscontainer/', url,
                                index = extract_index(extract_id($(this).attr('class'))),
                                id = gadgets[index].config.options.id,
                                type = gadgets[index].type;
                            switch (type) {
                                case 'timeseries': url = prefix + type + ':' + id; break;
                                case 'surface': url = prefix + type + ':' + id; break;
                                case 'curve': url = prefix + type + ':' + id; break;
                                case 'grid': url = prefix + type + ':' + id; break;
                                case 'data': url = prefix + type + ':' + id; break;
                                case 'depgraph': url = prefix + type + ':' + id; break;
                            }
                            if (!dropped) {
                                window.open(url);
                                setTimeout(container.del.partial(gadgets[i]));
                            }
                        };
                    // stage 1
                    if (tabs_width > full_width) {
                        new_tab_width = ~~((full_width - active_tab_width) / num_inactive_tabs);
                        new_tab_width = new_tab_width < min_tab_width ? min_tab_width : new_tab_width;
                        compressed_tabs_width = (num_inactive_tabs * new_tab_width) + active_tab_width;
                        // stage 2
                        if (compressed_tabs_width > full_width) {
                            if (num_tabs > 1) {
                                $overflow_button.show().on('click', function (e) {
                                    e.stopPropagation();
                                    $overflow_panel.toggle();
                                    var wins = [window].concat(Array.prototype.slice.call(window.frames));
                                    if ($overflow_panel.is(':visible'))
                                        $(wins).on('click.overflow_handler', function () {
                                            $overflow_panel.hide();
                                            $(wins).off('click.overflow_handler');
                                        });
                                    else $(wins).off('click.overflow_handler');
                                });
                            }
                            space_needed = full_width - compressed_tabs_width;
                            num_tabs_to_hide = Math.ceil(Math.abs(space_needed) / new_tab_width);
                            if (num_tabs_to_hide) $overflow_panel.find('ul').html(
                                $tabs_to_move = $tabs.filter(':not(.og-active):lt(' + num_tabs_to_hide + ')')
                            );
                            // stage 3
                            if (num_tabs_to_hide >= num_tabs) $active_tab.width(
                                active_tab_width
                                + (space_needed + ((num_inactive_tabs * min_tab_width) - overflow_buffer))
                                + 'px'
                            );
                        } else {
                            $overflow_panel.hide();
                        }
                        // set inactive tab widths to calculated value
                        $tabs.each(function () {if (!$(this).hasClass('og-active'))
                            $(this).outerWidth(new_tab_width)});
                        // unset width of tabs in overflow panel
                        if ($tabs_to_move) $tabs_to_move.each(function () {$(this).attr('style', '');});
                        // set position of overflow panel
                        overflow.right = $(document).width() - ($overflow_button.offset().left + 25 - 5);
                        overflow.height = $overflow_button.height();
                        overflow.top = $overflow_button.offset().top + overflow.height + 1;
                        $overflow_panel.css({'right': overflow.right + 'px', 'top': overflow.top + 'px'});
                        $tabs.each(function () { // add tooltips to truncated tabs only
                            var $this = $(this);
                            if (!!$this.attr('style')) $this.attr('title', $this.text().trim());
                        });
                    }
                    // implement drag
                    $tabs.each(function (i) {
                        $(this).draggable({
                            cursor: 'move', zIndex: 3, cursorAt: {top: 25}, scroll: false,
                            iframeFix: true, appendTo: 'body', distance: 20,
                            revert: new_window.partial(i),
                            stop: function () {$(this).draggable('option','revert', new_window.partial(i));},
                            helper: function() {return dropbox_template({label: $(this).text().trim()});}
                        }).data({gadget: gadgets[i], handler: function () {container.del(gadgets[i]);}});
                    });
                };
                if (id === null) $header.html(tabs_template({'tabs': [{'name': 'empty'}]})); // empty tabs
                else {
                    if (id === void 0) id = live_id;
                    tabs = gadgets.reduce(function (acc, val) {
                        return acc.push({
                            'type': val.config.type, 'row_name': val.config.row_name, 'col_name': val.config.col_name,
                            'active': (id === val.id), 'delete': true, 'id': val.id
                        }) && acc;
                    }, []);
                    $header.html(tabs_template({'tabs': tabs}));
                    reflow();
                    show_gadget(id);
                }
            };
            /**
             * @param {String|Array} data A String that defines what gadgets to load, or an Array of gadgets to load
             * @param {Number} idx location to add new gadgets
             *
             * The data Array is a list of objects that describe the gadgets to load
             *     obj.gadget   Function
             *     obj.options  Object
             *     obj.name     String
             *     obj.margin   Boolean
             */
            container.add = function (data, index, fingerprint) {
                var panel_container = selector + ' .OG-gadget-container', new_gadgets;
                if (!loading && !initialized)
                    return container.init(), setTimeout(container.add.partial(data, index, fingerprint), 10), container;
                if (!initialized) return setTimeout(container.add.partial(data, index, fingerprint), 10), container;
                if (!data) return container; // no gadgets for this container
                if (!selector) throw new TypeError('GadgetsContainer has not been initialized');
                new_gadgets = data.map(function (obj, idx) {
                    var id, gadget_class = 'OG-gadget-' + (id = counter++), gadget,
                        options = $.extend(true, obj.options || {}, {selector: panel_container + ' .' + gadget_class}),
                        constructor = obj.gadget.split('.').reduce(function (acc, val) {return acc[val];}, window),
                        type = obj.gadget.replace(/^[a-z0-9.-_]+\.([a-z0-9.-_]+?)$/, '$1').toLowerCase();
                    $(panel_container).append('<div class="' + gadget_class + '" />').find('.' + gadget_class)
                        .css({
                            position: 'absolute', top: 0, bottom: 0, left: 0, right: 0,
                            display: idx === data.length - 1 ? 'block' : 'none'
                        });
                    gadgets.splice(index || gadgets.length, 0,
                        gadget = {id: id, config: obj, type: type, gadget: new constructor(options)});
                    if (fingerprint) gadget.fingerprint = fingerprint;
                    return gadget;
                });
                update_tabs(new_gadgets[new_gadgets.length - 1].id);
                return container;
            };
            container.alive = function () {
                gadgets.forEach(function (obj) {obj.gadget.alive();});
                return !!$selector.length;
            };
            container.del = function (obj, silent) {
                var id, index = gadgets.indexOf(obj);
                $(selector + ' .OG-gadget-container .OG-gadget-' + obj.id).remove();
                gadgets[gadgets.length - 1].gadget.alive();
                gadgets.splice(index, 1);
                id = gadgets.length
                    ? live_id === obj.id ? gadgets[gadgets.length - 1].id : live_id
                    : null;
                if (id) gadgets[extract_index(id)].gadget.resize();
                update_tabs(id); // new active tab or empty
                if (!silent) og.common.events.fire(container.events.del, index);
            };
            container.events = {del: [], drop: []};
            container.gadgets = function () {return gadgets;};
            /**
             * Highlight gadget panel with number
             * @param show {Boolean} turn on / off
             * @param strong {Boolean} set to active panel
             */
            container.highlight = function (show, strong) {
                var number = ({'south': 1, 'dock-north': 2, 'dock-center': 3, 'dock-south': 4})[pane],
                    query = $selector.find('.og-highlight'), $html = query.length ? query
                        : $('<div />').text(number).addClass('og-highlight').appendTo($selector);
                clearTimeout(highlight_timer);
                if (show) (strong ? $html.addClass('strong') : $html.removeClass('strong')).show();
                else clearTimeout(highlight_timer), highlight_timer = setTimeout(function () {$html.hide()}, 250);
            };
            container.init = function (data) {
                var toggle_dropbox = function () {
                        var $db = $('.og-drop').length, $dbs_span = $('.OG-dropbox span');
                        if ($db) $dbs_span.removeClass('og-icon-new-window').addClass('og-icon-drop');
                        else $dbs_span.removeClass('og-icon-drop').addClass('og-icon-new-window');
                    };
                loading = true;
                $.when(
                    api.text({module: 'og.analytics.tabs_tash'}),
                    api.text({module: 'og.analytics.tabs_overflow_tash'}),
                    api.text({module: 'og.analytics.dropbox_tash'})
                ).then(function (tabs_tmpl, overflow_tmpl, dropbox_tmpl) {
                    if (!tabs_template) tabs_template = Handlebars.compile(tabs_tmpl);
                    if (!overflow_template) overflow_template = Handlebars.compile(overflow_tmpl);
                    if (!dropbox_template) dropbox_template = Handlebars.compile(dropbox_tmpl);
                    if (!$overflow_panel) $overflow_panel = $(overflow_template({pane: pane})).appendTo('body');
                    initialized = true;
                    loading = false;
                    // setup click handlers
                    $(selector + header + ' , .og-js-overflow-' + pane)
                        // handler for tabs (including the ones in the overflow pane)
                        .on('click', 'li[class^=og-tab-]', function (e) {
                            var id = extract_id($(this).attr('class')), index = extract_index(id);
                            if ($(e.target).hasClass('og-delete')) container.del(gadgets[index]);
                            else if (!$(this).hasClass('og-active')) {
                                update_tabs(id || null);
                                if (id) gadgets[index].gadget.resize();
                            }
                        });
                    if (!data) update_tabs(null); else container.add(data);
                    // implement drop
                    $selector.droppable({
                        hoverClass: 'og-drop',
                        accept: function (draggable) {return $(draggable).is('li[class^=og-tab-]')}, // is it a tab...
                        over: function () {setTimeout(toggle_dropbox)}, // can't guarantee over and out fire in correct
                        out: function () {setTimeout(toggle_dropbox)},  // order, toggle function seems to solve issue
                        drop: function(e, ui) {
                            var has_ancestor = function (elm, sel) {return $(elm).closest('.' + sel).length},
                                pane_class = class_prefix + pane,
                                overflow_class = 'og-js-overflow-' + pane,
                                data = ui.draggable.data(),
                                gadget = data.gadget.config.options,
                                re = new RegExp(selector_prefix + '(.*?)\\s');
                            if (has_ancestor(ui.draggable, pane_class) || has_ancestor(ui.draggable, overflow_class)) {
                                ui.draggable.draggable('option', 'revert', true);
                            } else {
                                ui.draggable.draggable('option', 'revert', false);
                                gadget.selector = gadget.selector.replace(re, selector_prefix + pane + ' ');
                                if (false !== og.common.events.fire(container.events.drop, data.gadget.config))
                                    container.add([data.gadget.config]);
                                setTimeout(data.handler); // setTimeout to ensure handler is called after drag evt ends
                            }
                        }
                    });
                    og.common.gadgets.manager.register(container);
                });
                return container;
            };
            container.resize = function () {
                gadgets.forEach(function (obj) {
                    if (obj.gadget.alive()) {
                        $(selector + ' .ui-layout-content').show();
                        update_tabs();
                        obj.gadget.resize();
                    }
                });
            }
            container.verify = function (fingerprints) {
                if (!initialized) return setTimeout(container.verify.partial(fingerprints), 10), container;
                var gadget_prints = gadgets.pluck('fingerprint'), keep;
                keep = (fingerprints || []).map(function (fingerprint) {
                    var index;
                    if (!fingerprint) return null;
                    if (~(index = gadget_prints.indexOf(fingerprint))) return index; else return null;
                }).reduce(function (acc, val) {if (val !== null) acc[val] = null; return acc;}, {});
                gadgets.forEach(function (gadget, index) {if (!(index in keep)) container.del(gadgets[index], true);});
                return container;
            };
        };
        constructor.prototype.on = og.common.events.on;
        return constructor;
    }
});<|endoftext|>


<commit_before>/**
 * @copyright 2009 - present by OpenGamma Inc
 * @license See distribution for license
 * @colordef #fffee5; light yellow for edit mode (on hover)
 */
$.register_module({
    name: 'og.common.util.ui.content_editable',
    dependencies: ['og.common.util.ui', 'og.common.routes', 'og.api.rest'],
    obj: function () {
        var ui = og.common.util.ui;
        return function (config) {
            var attr = 'data-og-editable', $attr = $('[' + attr + ']'), rest_properties = {}, handler = config.handler,
                css_edit = {'background-color': '#fffee5'}, css_not_edit = {'background-color': 'transparent'};
            if (typeof handler !== 'function') throw new TypeError(': config.handler must be a function');
            $attr.hover(function () {$(this).css(css_edit);}, function () {$(this).css(css_not_edit);});
            $attr.click(function (e) {
                e.preventDefault();
                var target = e.target, value = $(target).html(), type = $(target).attr(attr);
                // get any additional information from data attributes on the element (like node, id, quantity)
                $.outer(target).split(' ').map(function (val) {
                    if (/^data-og/.test(val))
                        rest_properties[val.replace(/^data-og-(.+?)=.+$/, '$1')] = val.replace(/^.+="(.+?)".*/, '$1');
                });
                delete rest_properties.editable;
                ui.dialog({
                    type: 'input',
                    title: 'Edit ' + type + ': "' + value + '"',
                    fields: [{type: 'input', name: 'New Value', id: type, value: value}],
                    buttons: {
                        'OK': function () {
                            ui.dialog({action: 'close', type: 'input'});
                            rest_properties[type] = ui.dialog({return_field_value: type});
                            rest_properties.handler = function (result) {
                                if (result.error) return ui.dialog({type: 'error', message: result.message});
                                handler();
                            };
                            og.api.rest[og.common.routes.current().page.substring(1)].put(rest_properties);
                        }
                    }
                });
            });
        };

    }
});<commit_msg>bound breadcrumb click using live<commit_after>/**
 * @copyright 2009 - present by OpenGamma Inc
 * @license See distribution for license
 * @colordef #fffee5; light yellow for edit mode (on hover)
 */
$.register_module({
    name: 'og.common.util.ui.content_editable',
    dependencies: ['og.common.util.ui', 'og.common.routes', 'og.api.rest'],
    obj: function () {
        var ui = og.common.util.ui;
        return function (config) {
            var attr = 'data-og-editable', $attr = $('[' + attr + ']'), rest_properties = {}, handler = config.handler,
                css_edit = {'background-color': '#fffee5'}, css_not_edit = {'background-color': 'transparent'};
            if (typeof handler !== 'function') throw new TypeError(': config.handler must be a function');
            $attr.hover(function () {$(this).css(css_edit);}, function () {$(this).css(css_not_edit);});
            $attr.die().live('click', function (e) {
                e.preventDefault();
                var target = e.target, value = $(target).html(), type = $(target).attr(attr);
                // get any additional information from data attributes on the element (like node, id, quantity)
                $.outer(target).split(' ').map(function (val) {
                    if (/^data-og/.test(val))
                        rest_properties[val.replace(/^data-og-(.+?)=.+$/, '$1')] = val.replace(/^.+="(.+?)".*/, '$1');
                });
                delete rest_properties.editable;
                ui.dialog({
                    type: 'input',
                    title: 'Edit ' + type + ': "' + value + '"',
                    fields: [{type: 'input', name: 'New Value', id: type, value: value}],
                    buttons: {
                        'OK': function () {
                            ui.dialog({action: 'close', type: 'input'});
                            rest_properties[type] = ui.dialog({return_field_value: type});
                            rest_properties.handler = function (result) {
                                if (result.error) return ui.dialog({type: 'error', message: result.message});
                                handler();
                            };
                            og.api.rest[og.common.routes.current().page.substring(1)].put(rest_properties);
                        }
                    }
                });
            });
        };

    }
});<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */
const mongoose = require('mongoose');
const Schema   = mongoose.Schema
// create a schema
const playerSchema = new Schema({
  name: String,
  username: { type: String, required: true, unique: true, minlength: 1 },
  team: Schema.Types.ObjectId,
  created_at: Date,
});

// create a model using schema
const Player = mongoose.model('Player', playerSchema);

// on every save, add the date
playerSchema.pre('save', (next) => {
  if (!this.created_at) {
      this.created_at = new Date();
  }
  next();
});

// make this available to our users in our Node applications
module.exports = Player;
<commit_msg>Add name length validation<commit_after>/**
 * Module dependencies.
 */
const mongoose = require('mongoose');
const Schema   = mongoose.Schema
// create a schema
const playerSchema = new Schema({
  name: { type: String, minlength: 1 },
  username: { type: String, required: true, unique: true, minlength: 1 },
  team: Schema.Types.ObjectId,
  created_at: Date,
});

// create a model using schema
const Player = mongoose.model('Player', playerSchema);

// on every save, add the date
playerSchema.pre('save', (next) => {
  if (!this.created_at) {
    this.created_at = new Date();
  }
  next();
});

// make this available to our users in our Node applications
module.exports = Player;
<|endoftext|>