<commit_before>

////////////////////////////////////////////////////////
// Spectrum
////////////////////////////////////////////////////////

function Spectrum(name, desc)
{
	this._name = name;
	this._desc = desc;
}

Spectrum.prototype.getName = function()
{
	return this._name;
}

Spectrum.prototype.getDesc = function()
{
	return this._desc;
}

Spectrum.prototype.spectrum = function(wavelength)
{
	console.log('Spectrum.prototype.spectrum, error');
}

Spectrum.prototype.inverseCDF = function(minwavelength, maxwavelength, numsamples)
{
    // Compute CDF of spectrum
	var cdf  = new Float32Array(numsamples+1);
	var dl = (maxwavelength - minwavelength)/numsamples;
	cdf[0] = 0.0;
    for (var n=0; n<numsamples; ++n)
    {
    	var wavelength = minwavelength + dl*n;
    	cdf[n+1] = cdf[n] + this.spectrum(wavelength);
    }

    // Normalize CDF
    var cdfSum = cdf[numsamples];
    for (var n=0; n<numsamples; ++n) cdf[n+1] /= cdfSum;
    cdf[numsamples] = 1.0;

	// Numerically invert CDF
	var icdf = new Float32Array(4*numsamples);
	var cdfIdx = 0;
	for (var n=0; n<4*numsamples; ++n)
    {
    	var spectrumOffset = Math.min((n+1)/(4*numsamples), 1.0);
    	while (cdf[cdfIdx] < spectrumOffset) cdfIdx++;
    	icdf[n] = (cdfIdx-1.0)/(numsamples);
    }
    return icdf;
}

////////////////////////////////////////////////////////
// Monochromatic
////////////////////////////////////////////////////////

function MonochromaticSpectrum(name, desc, wavelength)
{
	Spectrum.call(this, name, desc);
	this.wavelength = wavelength;
}

MonochromaticSpectrum.prototype = Object.create(Spectrum.prototype);

// A delta function spectrum has specialized inverse CDF generation
MonochromaticSpectrum.prototype.inverseCDF = function(minwavelength, maxwavelength, numsamples)
{
	var wavelength = Math.max(minwavelength, Math.min(this.wavelength, maxwavelength));
	var spectrumOffset = (wavelength - minwavelength) / (maxwavelength - minwavelength);
	var icdf = new Float32Array(4*numsamples);
	for (var n=0; n<4*numsamples; ++n)
    {
    	icdf[n] = spectrumOffset;
    }
    return icdf;
}

// set up gui and callbacks for this spectrum
MonochromaticSpectrum.prototype.initGui = function(parentFolder)
{
	ME = this;
	this.wavelengthItem = parentFolder.add(this, 'wavelength', 360.0, 750.0);
	this.wavelengthItem.onChange( function(value) { renderer.loadSpectrum(ME.getName()); } );
}

MonochromaticSpectrum.prototype.eraseGui = function(parentFolder)
{
	parentFolder.remove(this.wavelengthItem);
}

////////////////////////////////////////////////////////
// Flat
////////////////////////////////////////////////////////

function FlatSpectrum(name, desc, bandmin, bandmax)
{
	Spectrum.call(this, name, desc);
	this['Minimum wavelength'] = bandmin;
	this['Maximum wavelength'] = bandmax;
}

FlatSpectrum.prototype = Object.create(Spectrum.prototype);

FlatSpectrum.prototype.spectrum = function(wavelength)
{
	if (wavelength<this['Minimum wavelength']) return 0.0;
	if (wavelength>this['Maximum wavelength']) return 0.0;
	return 1.0;
}

// set up gui and callbacks for this spectrum
FlatSpectrum.prototype.initGui = function(parentFolder)
{
	ME = this;
	var dw = (750.0-360.0)/256.0;
	this.minItem = parentFolder.add(this, 'Minimum wavelength', 360.0, 750.0);
	this.minItem.onChange( function(value)
	{
		if (ME['Minimum wavelength'] > ME['Maximum wavelength']-dw)
			ME['Minimum wavelength'] = ME['Maximum wavelength']-dw;
		snelly.getLightTracer().loadSpectrum(ME.getName());
	});

	this.maxItem = parentFolder.add(this, 'Maximum wavelength', 360.0, 750.0);
	this.maxItem.onChange( function(value)
	{
		if (ME['Maximum wavelength'] < ME['Minimum wavelength']+dw)
			ME['Maximum wavelength'] = ME['Minimum wavelength']+dw;
		snelly.getLightTracer().loadSpectrum(ME.getName());
	});
}

FlatSpectrum.prototype.eraseGui = function(parentFolder)
{
	parentFolder.remove(this.minItem);
	parentFolder.remove(this.maxItem);
}

////////////////////////////////////////////////////////
// Blackbody
////////////////////////////////////////////////////////

function BlackbodySpectrum(name, desc, temperature) // temperature in Kelvin
{
	Spectrum.call(this, name, desc);
	this.temperature = temperature;
}

BlackbodySpectrum.prototype = Object.create(Spectrum.prototype);

BlackbodySpectrum.prototype.spectrum = function(wavelength)  // wavelength in nm
{
    var boltzmann_factor = 1.43877737467e7 / (wavelength*this.temperature);
    var l = wavelength/360.0; // wavelength relative to 360nm
    return 1.0 / (l*l*l*l*l*(Math.exp(boltzmann_factor) - 1.0));
}

// set up gui and callbacks for this spectrum
BlackbodySpectrum.prototype.initGui = function(parentFolder)
{
	ME = this;
	this.temperatureItem = parentFolder.add(this, 'temperature', 300.0, 15000.0);
	this.temperatureItem.onChange( function(value) { renderer.loadSpectrum(ME.getName()); } );
}

BlackbodySpectrum.prototype.eraseGui = function(parentFolder)
{
	parentFolder.remove(this.temperatureItem);
}

<commit_msg>Fix UI bug.<commit_after>

////////////////////////////////////////////////////////
// Spectrum
////////////////////////////////////////////////////////

function Spectrum(name, desc)
{
	this._name = name;
	this._desc = desc;
}

Spectrum.prototype.getName = function()
{
	return this._name;
}

Spectrum.prototype.getDesc = function()
{
	return this._desc;
}

Spectrum.prototype.spectrum = function(wavelength)
{
	console.log('Spectrum.prototype.spectrum, error');
}

Spectrum.prototype.inverseCDF = function(minwavelength, maxwavelength, numsamples)
{
    // Compute CDF of spectrum
	var cdf  = new Float32Array(numsamples+1);
	var dl = (maxwavelength - minwavelength)/numsamples;
	cdf[0] = 0.0;
    for (var n=0; n<numsamples; ++n)
    {
    	var wavelength = minwavelength + dl*n;
    	cdf[n+1] = cdf[n] + this.spectrum(wavelength);
    }

    // Normalize CDF
    var cdfSum = cdf[numsamples];
    for (var n=0; n<numsamples; ++n) cdf[n+1] /= cdfSum;
    cdf[numsamples] = 1.0;

	// Numerically invert CDF
	var icdf = new Float32Array(4*numsamples);
	var cdfIdx = 0;
	for (var n=0; n<4*numsamples; ++n)
    {
    	var spectrumOffset = Math.min((n+1)/(4*numsamples), 1.0);
    	while (cdf[cdfIdx] < spectrumOffset) cdfIdx++;
    	icdf[n] = (cdfIdx-1.0)/(numsamples);
    }
    return icdf;
}

////////////////////////////////////////////////////////
// Monochromatic
////////////////////////////////////////////////////////

function MonochromaticSpectrum(name, desc, wavelength)
{
	Spectrum.call(this, name, desc);
	this.wavelength = wavelength;
}

MonochromaticSpectrum.prototype = Object.create(Spectrum.prototype);

// A delta function spectrum has specialized inverse CDF generation
MonochromaticSpectrum.prototype.inverseCDF = function(minwavelength, maxwavelength, numsamples)
{
	var wavelength = Math.max(minwavelength, Math.min(this.wavelength, maxwavelength));
	var spectrumOffset = (wavelength - minwavelength) / (maxwavelength - minwavelength);
	var icdf = new Float32Array(4*numsamples);
	for (var n=0; n<4*numsamples; ++n)
    {
    	icdf[n] = spectrumOffset;
    }
    return icdf;
}

// set up gui and callbacks for this spectrum
MonochromaticSpectrum.prototype.initGui = function(parentFolder)
{
	ME = this;
	this.wavelengthItem = parentFolder.add(this, 'wavelength', 360.0, 750.0);
	this.wavelengthItem.onChange( function(value) { snelly.getLightTracer().loadSpectrum(ME.getName()); } );
}

MonochromaticSpectrum.prototype.eraseGui = function(parentFolder)
{
	parentFolder.remove(this.wavelengthItem);
}

////////////////////////////////////////////////////////
// Flat
////////////////////////////////////////////////////////

function FlatSpectrum(name, desc, bandmin, bandmax)
{
	Spectrum.call(this, name, desc);
	this['Minimum wavelength'] = bandmin;
	this['Maximum wavelength'] = bandmax;
}

FlatSpectrum.prototype = Object.create(Spectrum.prototype);

FlatSpectrum.prototype.spectrum = function(wavelength)
{
	if (wavelength<this['Minimum wavelength']) return 0.0;
	if (wavelength>this['Maximum wavelength']) return 0.0;
	return 1.0;
}

// set up gui and callbacks for this spectrum
FlatSpectrum.prototype.initGui = function(parentFolder)
{
	ME = this;
	var dw = (750.0-360.0)/256.0;
	this.minItem = parentFolder.add(this, 'Minimum wavelength', 360.0, 750.0);
	this.minItem.onChange( function(value)
	{
		if (ME['Minimum wavelength'] > ME['Maximum wavelength']-dw)
			ME['Minimum wavelength'] = ME['Maximum wavelength']-dw;
		snelly.getLightTracer().loadSpectrum(ME.getName());
	});

	this.maxItem = parentFolder.add(this, 'Maximum wavelength', 360.0, 750.0);
	this.maxItem.onChange( function(value)
	{
		if (ME['Maximum wavelength'] < ME['Minimum wavelength']+dw)
			ME['Maximum wavelength'] = ME['Minimum wavelength']+dw;
		snelly.getLightTracer().loadSpectrum(ME.getName());
	});
}

FlatSpectrum.prototype.eraseGui = function(parentFolder)
{
	parentFolder.remove(this.minItem);
	parentFolder.remove(this.maxItem);
}

////////////////////////////////////////////////////////
// Blackbody
////////////////////////////////////////////////////////

function BlackbodySpectrum(name, desc, temperature) // temperature in Kelvin
{
	Spectrum.call(this, name, desc);
	this.temperature = temperature;
}

BlackbodySpectrum.prototype = Object.create(Spectrum.prototype);

BlackbodySpectrum.prototype.spectrum = function(wavelength)  // wavelength in nm
{
    var boltzmann_factor = 1.43877737467e7 / (wavelength*this.temperature);
    var l = wavelength/360.0; // wavelength relative to 360nm
    return 1.0 / (l*l*l*l*l*(Math.exp(boltzmann_factor) - 1.0));
}

// set up gui and callbacks for this spectrum
BlackbodySpectrum.prototype.initGui = function(parentFolder)
{
	ME = this;
	this.temperatureItem = parentFolder.add(this, 'temperature', 300.0, 15000.0);
	this.temperatureItem.onChange( function(value) { snelly.getLightTracer().loadSpectrum(ME.getName()); } );
}

BlackbodySpectrum.prototype.eraseGui = function(parentFolder)
{
	parentFolder.remove(this.temperatureItem);
}

<|endoftext|>


<commit_before>/*!
 * SWIF-OpenSTC
 * Copyright 2013-2014 Siclic <contact@siclic.fr>
 * Licensed under AGPL-3.0 (https://www.gnu.org/licenses/agpl.txt)
 */

/******************************************
* Require JS Configuration
*/
requirejs.config({

	paths: {

		/* #############
		* Libs
		*/
		jquery                : 'libs/jQuery-2.1.0-min',
		printElement          : 'libs/jquery.printElement-2.0.0',
		underscore            : 'libs/underscore-1.6.0-min',
		backbone              : 'libs/backbone-1.1.1-min',

		jqueryui              : 'libs/jquery-ui-1.10.3.custom.min',
		datatables            : 'libs/jquery-dataTables-1.9.4-min',
		moment                : 'libs/moment-2.5.0-min',
		'moment-timezone'     : 'libs/moment-timezone-0.0.1-min',
		'moment-timezone-data': 'i18n/moment-timezone-data',

		'underscore.string'   : 'libs/underscore-string-2.3.2-min',
		nprogress             : 'libs/NProgress-0.1.2',
		pnotify               : 'libs/pnotify-1.2.0',
		bootstrap             : 'libs/bootstrap-3.1.1-min',
		bsDatepicker          : 'libs/bootstrap-datepicker-1.1.2',
		bsSwitch              : 'libs/bootstrap-switch-2.0.0',
		bsTimepicker          : 'libs/bootstrap-timepicker-0.2.3',
		fullcalendar          : 'libs/fullcalendar-1.6.4-min',
		select2               : 'libs/select2-3.4.5-min',

		less                  : 'libs/less-1.6.3-min',

		/* #############
		* i18n Libs
		*/
		'select2-lang'         : 'i18n/select2-lang',
		'bsDatepicker-lang'    : 'i18n/bootstrap-datepicker-lang',

		app                   : 'app',
		main                  : 'main',

		/* #############
		* Helpers
		*/
		appHelpers                : 'helpers/main',

		/* #############
		* Routers
		*/
		appRouter                 : 'routers/AppRouter',

		/* #############
		* Collections
		*/
		genericCollection          : 'collections/GenericCollection',
		usersCollection            : 'collections/UsersCollection',
		claimersCollection         : 'collections/ClaimersCollection',
		claimersContactsCollection : 'collections/ClaimersContactsCollection',
		claimersServicesCollection : 'collections/ClaimersServicesCollection',
		claimersTypesCollection    : 'collections/ClaimersTypesCollection',
		equipmentsCollection       : 'collections/EquipmentsCollection',
		equipmentsTypesCollection  : 'collections/EquipmentsTypesCollection',
		placesCollection           : 'collections/PlacesCollection',
		placeTypesCollection       : 'collections/PlaceTypesCollection',
		stcGroupsCollection        : 'collections/STCGroupsCollection',
		teamsCollection            : 'collections/TeamsCollection',
		officersCollection         : 'collections/OfficersCollection',
		metaDatasCollection        : 'collections/MetaDatasCollection',
		filtersCollection          : 'collections/FiltersCollection',

		/* #############
		* Models
		*/
		genericModel            : 'models/GenericModel',
		userModel               : 'models/UserModel',
		claimerModel            : 'models/ClaimerModel',
		claimerTypeModel        : 'models/ClaimerTypeModel',
		claimerContactModel     : 'models/ClaimerContactModel',
		claimerServiceModel     : 'models/ClaimerServiceModel',
		equipmentModel          : 'models/EquipmentModel',
		equipmentTypeModel      : 'models/EquipmentTypeModel',
		placeModel              : 'models/PlaceModel',
		teamModel               : 'models/TeamModel',
		officerModel            : 'models/OfficerModel',
		metaDataModel           : 'models/MetaDataModel',
		filterModel             : 'models/FilterModel',

		/* #############
		* Views
		*/
		headerView                  : 'views/others/HeaderView',
		footerView                  : 'views/others/FooterView',
		loginView                   : 'views/others/LoginView',
		paginationView              : 'views/others/PaginationView',
		notFoundView                : 'views/others/NotFoundView',
		aboutView                   : 'views/others/AboutView',
		teamMembersAndServices      : 'views/others/TeamMembersAndServices',
		advanceSearchView           : 'views/others/AdvanceSearchView',
		fieldContainerView          : 'views/others/FieldContainerView',
		recordFilterView            : 'views/others/RecordFilterView',

		// Lists //
		genericListView         : 'views/lists/GenericListView',
		placesListView          : 'views/lists/PlacesListView',
		claimersListView        : 'views/lists/ClaimersListView',
		claimerContactsListView : 'views/lists/ClaimerContactsListView',
		servicesListView        : 'views/lists/ServicesListView',
		officersListView        : 'views/lists/OfficersListView',
		teamsListView           : 'views/lists/TeamsListView',
		claimersTypesListView   : 'views/lists/ClaimersTypesListView',
		equipmentsListView      : 'views/lists/EquipmentsListView',
		usersListView           : 'views/lists/UsersListView',

		// Items //
		itemPlaceView           : 'views/items/ItemPlaceView',
		claimerView             : 'views/items/ClaimerView',
		itemTeamView            : 'views/items/ItemTeamView',
		itemServiceView         : 'views/items/ItemServiceView',
		itemOfficerView         : 'views/items/ItemOfficerView',
		claimerContactView      : 'views/items/ClaimerContactView',
		itemEquipmentView       : 'views/items/ItemEquipmentView',
		itemClaimerTypeView     : 'views/items/ItemClaimerTypeView',
		itemUserView            : 'views/items/ItemUserView',

		// Modals //
		genericModalView        : 'views/modals/GenericModalView',
		modalPlaceView          : 'views/modals/ModalPlaceView',
		modalClaimerEdit        : 'views/modals/ModalClaimerEdit',
		modalTeamView           : 'views/modals/ModalTeamView',
		modalDeleteView         : 'views/modals/ModalDeleteView',
		modalServiceView        : 'views/modals/ModalServiceView',
		modalOfficerView        : 'views/modals/ModalOfficerView',
		modalContactEdit        : 'views/modals/ModalContactEdit',
		modalEquipmentView      : 'views/modals/ModalEquipmentView',
		modalClaimerTypeView    : 'views/modals/ModalClaimerTypeView',
		modalResetPasswordView  : 'views/modals/ModalResetPasswordView',
		modalSaveFilterView     : 'views/modals/ModalSaveFilterView',

		// Form Components //
		advancedSelectBoxView   : 'views/form-components/AdvancedSelectBoxView',
		inputFieldView          : 'views/form-components/InputFieldView',
		dateFieldView           : 'views/form-components/DateFieldView',
		numberFieldView         : 'views/form-components/NumberFieldView'

	},

	packages: [
		{
			name    : 'app-interventions',
			location: '../app-interventions',
			main    : 'main'
		},
		{
			name    : 'app-reservations',
			location: '../app-reservations',
			main    : 'main'
		}
	],

	shim: {
		'nprogress': {
			deps   : ['jquery'],
			exports: 'NProgress'
		},
		'pnotify': {
			deps   : ['jquery'],
			exports: 'pnotify'
		},
		'bootstrap': {
			deps   : ['jquery'],
			exports: 'bootstrap'
		},
		'jqueryui': {
			deps   : ['jquery'],
			exports: 'jquery'
		},
		'bsSwitch': {
			deps   : ['jquery'],
			exports: 'bsSwitch'
		},
		'select2-lang': {
			deps   : ['jquery', 'select2'],
			exports: 'select2'
		},
		'fullcalendar': {
			deps   : ['jquery']
		},
		'bsDatepicker-lang': {
			deps   : ['jquery', 'bsDatepicker'],
			exports: 'datepicker'
		},
		'bsTimepicker': {
			deps   : ['jquery']
		},
		'printElement': {
			deps   : ['jqueryui'],
			exports: 'printElement'
		},

		'app-interventions' : {
			deps   : ['app-interventions/config/require-config']
		},
		'app-reservations' : {
			deps   : ['app-reservations/config/require-config']
		}

	}

});

/******************************************
* Start The App
*/
require([
	'main', 'underscore', 'underscore.string', 'less'
], function(main, _, _s){

	'use strict';

	_.str = _s;
	_.mixin(_.str.exports());

	main.init('fr');

});<commit_msg>added 'patrimoines' to swif sub-modules in requireJS config<commit_after>/*!
 * SWIF-OpenSTC
 * Copyright 2013-2014 Siclic <contact@siclic.fr>
 * Licensed under AGPL-3.0 (https://www.gnu.org/licenses/agpl.txt)
 */

/******************************************
* Require JS Configuration
*/
requirejs.config({

	paths: {

		/* #############
		* Libs
		*/
		jquery                : 'libs/jQuery-2.1.0-min',
		printElement          : 'libs/jquery.printElement-2.0.0',
		underscore            : 'libs/underscore-1.6.0-min',
		backbone              : 'libs/backbone-1.1.1-min',

		jqueryui              : 'libs/jquery-ui-1.10.3.custom.min',
		datatables            : 'libs/jquery-dataTables-1.9.4-min',
		moment                : 'libs/moment-2.5.0-min',
		'moment-timezone'     : 'libs/moment-timezone-0.0.1-min',
		'moment-timezone-data': 'i18n/moment-timezone-data',

		'underscore.string'   : 'libs/underscore-string-2.3.2-min',
		nprogress             : 'libs/NProgress-0.1.2',
		pnotify               : 'libs/pnotify-1.2.0',
		bootstrap             : 'libs/bootstrap-3.1.1-min',
		bsDatepicker          : 'libs/bootstrap-datepicker-1.1.2',
		bsSwitch              : 'libs/bootstrap-switch-2.0.0',
		bsTimepicker          : 'libs/bootstrap-timepicker-0.2.3',
		fullcalendar          : 'libs/fullcalendar-1.6.4-min',
		select2               : 'libs/select2-3.4.5-min',

		less                  : 'libs/less-1.6.3-min',

		/* #############
		* i18n Libs
		*/
		'select2-lang'         : 'i18n/select2-lang',
		'bsDatepicker-lang'    : 'i18n/bootstrap-datepicker-lang',

		app                   : 'app',
		main                  : 'main',

		/* #############
		* Helpers
		*/
		appHelpers                : 'helpers/main',

		/* #############
		* Routers
		*/
		appRouter                 : 'routers/AppRouter',

		/* #############
		* Collections
		*/
		genericCollection          : 'collections/GenericCollection',
		usersCollection            : 'collections/UsersCollection',
		claimersCollection         : 'collections/ClaimersCollection',
		claimersContactsCollection : 'collections/ClaimersContactsCollection',
		claimersServicesCollection : 'collections/ClaimersServicesCollection',
		claimersTypesCollection    : 'collections/ClaimersTypesCollection',
		equipmentsCollection       : 'collections/EquipmentsCollection',
		equipmentsTypesCollection  : 'collections/EquipmentsTypesCollection',
		placesCollection           : 'collections/PlacesCollection',
		placeTypesCollection       : 'collections/PlaceTypesCollection',
		stcGroupsCollection        : 'collections/STCGroupsCollection',
		teamsCollection            : 'collections/TeamsCollection',
		officersCollection         : 'collections/OfficersCollection',
		metaDatasCollection        : 'collections/MetaDatasCollection',
		filtersCollection          : 'collections/FiltersCollection',

		/* #############
		* Models
		*/
		genericModel            : 'models/GenericModel',
		userModel               : 'models/UserModel',
		claimerModel            : 'models/ClaimerModel',
		claimerTypeModel        : 'models/ClaimerTypeModel',
		claimerContactModel     : 'models/ClaimerContactModel',
		claimerServiceModel     : 'models/ClaimerServiceModel',
		equipmentModel          : 'models/EquipmentModel',
		equipmentTypeModel      : 'models/EquipmentTypeModel',
		placeModel              : 'models/PlaceModel',
		teamModel               : 'models/TeamModel',
		officerModel            : 'models/OfficerModel',
		metaDataModel           : 'models/MetaDataModel',
		filterModel             : 'models/FilterModel',

		/* #############
		* Views
		*/
		headerView                  : 'views/others/HeaderView',
		footerView                  : 'views/others/FooterView',
		loginView                   : 'views/others/LoginView',
		paginationView              : 'views/others/PaginationView',
		notFoundView                : 'views/others/NotFoundView',
		aboutView                   : 'views/others/AboutView',
		teamMembersAndServices      : 'views/others/TeamMembersAndServices',
		advanceSearchView           : 'views/others/AdvanceSearchView',
		fieldContainerView          : 'views/others/FieldContainerView',
		recordFilterView            : 'views/others/RecordFilterView',

		// Lists //
		genericListView         : 'views/lists/GenericListView',
		placesListView          : 'views/lists/PlacesListView',
		claimersListView        : 'views/lists/ClaimersListView',
		claimerContactsListView : 'views/lists/ClaimerContactsListView',
		servicesListView        : 'views/lists/ServicesListView',
		officersListView        : 'views/lists/OfficersListView',
		teamsListView           : 'views/lists/TeamsListView',
		claimersTypesListView   : 'views/lists/ClaimersTypesListView',
		equipmentsListView      : 'views/lists/EquipmentsListView',
		usersListView           : 'views/lists/UsersListView',

		// Items //
		itemPlaceView           : 'views/items/ItemPlaceView',
		claimerView             : 'views/items/ClaimerView',
		itemTeamView            : 'views/items/ItemTeamView',
		itemServiceView         : 'views/items/ItemServiceView',
		itemOfficerView         : 'views/items/ItemOfficerView',
		claimerContactView      : 'views/items/ClaimerContactView',
		itemEquipmentView       : 'views/items/ItemEquipmentView',
		itemClaimerTypeView     : 'views/items/ItemClaimerTypeView',
		itemUserView            : 'views/items/ItemUserView',

		// Modals //
		genericModalView        : 'views/modals/GenericModalView',
		modalPlaceView          : 'views/modals/ModalPlaceView',
		modalClaimerEdit        : 'views/modals/ModalClaimerEdit',
		modalTeamView           : 'views/modals/ModalTeamView',
		modalDeleteView         : 'views/modals/ModalDeleteView',
		modalServiceView        : 'views/modals/ModalServiceView',
		modalOfficerView        : 'views/modals/ModalOfficerView',
		modalContactEdit        : 'views/modals/ModalContactEdit',
		modalEquipmentView      : 'views/modals/ModalEquipmentView',
		modalClaimerTypeView    : 'views/modals/ModalClaimerTypeView',
		modalResetPasswordView  : 'views/modals/ModalResetPasswordView',
		modalSaveFilterView     : 'views/modals/ModalSaveFilterView',

		// Form Components //
		advancedSelectBoxView   : 'views/form-components/AdvancedSelectBoxView',
		inputFieldView          : 'views/form-components/InputFieldView',
		dateFieldView           : 'views/form-components/DateFieldView',
		numberFieldView         : 'views/form-components/NumberFieldView'

	},

	packages: [
		{
			name    : 'app-interventions',
			location: '../app-interventions',
			main    : 'main'
		},
		{
			name    : 'app-reservations',
			location: '../app-reservations',
			main    : 'main'
		}
	],

	shim: {
		'nprogress': {
			deps   : ['jquery'],
			exports: 'NProgress'
		},
		'pnotify': {
			deps   : ['jquery'],
			exports: 'pnotify'
		},
		'bootstrap': {
			deps   : ['jquery'],
			exports: 'bootstrap'
		},
		'jqueryui': {
			deps   : ['jquery'],
			exports: 'jquery'
		},
		'bsSwitch': {
			deps   : ['jquery'],
			exports: 'bsSwitch'
		},
		'select2-lang': {
			deps   : ['jquery', 'select2'],
			exports: 'select2'
		},
		'fullcalendar': {
			deps   : ['jquery']
		},
		'bsDatepicker-lang': {
			deps   : ['jquery', 'bsDatepicker'],
			exports: 'datepicker'
		},
		'bsTimepicker': {
			deps   : ['jquery']
		},
		'printElement': {
			deps   : ['jqueryui'],
			exports: 'printElement'
		},

		'app-interventions' : {
			deps   : ['app-interventions/config/require-config']
		},
		'app-reservations' : {
			deps   : ['app-reservations/config/require-config']
		},
		'app-patrimoines' : {
			deps   : ['app-patrimoines/config/require-config']
		}

	}

});

/******************************************
* Start The App
*/
require([
	'main', 'underscore', 'underscore.string', 'less'
], function(main, _, _s){

	'use strict';

	_.str = _s;
	_.mixin(_.str.exports());

	main.init('fr');

});<|endoftext|>


<commit_before>window.requestAnimationFrame(function () {
  var version = "1.0.4";
  development = false;
  devlog = function(str){
    if(development)
      console.log(str);
  }
  new ApplicationManager(KeyboardInputManager, HTMLActuator, LocalStorageManager, TranslationManager, version);
});
<commit_msg>Incremented version<commit_after>window.requestAnimationFrame(function () {
  var version = "1.1.0";
  development = false;
  devlog = function(str){
    if(development)
      console.log(str);
  }
  new ApplicationManager(KeyboardInputManager, HTMLActuator, LocalStorageManager, TranslationManager, version);
});
<|endoftext|>


<commit_before>// $ à la place de jQuery dans WordPress
var $ = jQuery.noConflict();

//Slider
var $slider = $('#slider');

// Initialize carousel
$slider.carousel({
    //interval: 4000
});

function doAnimations(elems) {
    var animEndEv = 'webkitAnimationEnd animationend';

    elems.each(function () {
        var $this = $(this),
            $animationType = $this.data('animation');

        // Add animate.css classes to
        // the elements to be animated
        // Remove animate.css classes
        // once the animation event has ended
        $this.addClass($animationType).one(animEndEv, function () {
            $this.removeClass($animationType);
        });
    });
}

// Select the elements to be animated
// in the first slide on page load
var $firstAnimatingElems = $slider.find('.item:first').find('[data-animation ^= "animated"]');

// Apply the animation using our function
doAnimations($firstAnimatingElems);

// Attach our doAnimations() function to the
// carousel's slide.bs.carousel event
$slider.on('slide.bs.carousel', function (e) {
    // Select the elements to be animated inside the active slide
    var $animatingElems = $(e.relatedTarget)
        .find("[data-animation ^= 'animated']");
    doAnimations($animatingElems);
});

// Carousel Swipe touch
$(document).ready(function () {

    //Enable swiping...
    $(".carousel-inner").swipe({
        //Generic swipe handler for all directions
        swipeLeft: function (event, direction, distance, duration, fingerCount) {
            $(this).parent().carousel('prev');
        },
        swipeRight: function () {
            $(this).parent().carousel('next');
        },
        //Default is 75px, set to 0 for demo so any distance triggers swipe
        threshold: 0
    });
});

// Ajouter une class au ul des widgets
$(document).ready(function () {
    $('.widget > ul').addClass('list-unstyled');
});

//Fastclick
$(function () {
    FastClick.attach(document.body);
});

//Scroll to top
$(function () {

    $(document).on('scroll', function () {

        if ($(window).scrollTop() > 300) {
            $('.scroll-top-wrapper').removeClass('fadeOutDownBig');
            $('.scroll-top-wrapper').addClass('show animated fadeInUpBig');
        } else {
            $('.scroll-top-wrapper').removeClass('show');
            $('.scroll-top-wrapper').addClass('animated fadeOutDownBig');
        }
    });

    $('.scroll-top-wrapper').on('click', scrollToTop);
});

function scrollToTop() {
    verticalOffset = typeof (verticalOffset) !== 'undefined' ? verticalOffset : 0;
    element = $('body');
    offset = element.offset();
    offsetTop = offset.top;
    $('html, body').animate({
        scrollTop: offsetTop
    }, 200, 'linear');
}

// Ajouter une class aux éléments d'un menu (pour hover.css)
$(document).ready(function () {
    $('#fullscreen-panel li > a').addClass('hvr-grow');
});

// ADD SLIDEDOWN ANIMATION TO DROPDOWN //
$('.dropdown').on('show.bs.dropdown', function(e){
    $(this).find('.dropdown-menu').first().stop(true, true).fadeIn();
});

// ADD SLIDEUP ANIMATION TO DROPDOWN //
$('.dropdown').on('hide.bs.dropdown', function(e){
    $(this).find('.dropdown-menu').first().stop(true, true).fadeOut();
});

//animate.css
jQuery(document).ready(function() {

    jQuery('.st_bounceInLeft').addClass("bt_hidden").viewportChecker({
        classToAdd: 'bt_visible animated bounceInLeft',
        offset: 0,
        repeat: true,
        callbackFunction: function(elem, action){},
        scrollHorizontal: false
    });
//
//
//jQuery('.st_fadeIn').addClass("bt_hidden").viewportChecker({
//classToAdd: 'bt_visible animated fadeIn',
//offset: 0,
//repeat: true,
//callbackFunction: function(elem, action){},
//scrollHorizontal: false
//});
//
//jQuery('.st_zoomIn').addClass("bt_hidden").viewportChecker({
//classToAdd: 'bt_visible animated zoomIn',
//offset: 0,
//repeat: true,
//callbackFunction: function(elem, action){},
//scrollHorizontal: false
//});
//
//jQuery('.st_rotateIn').addClass("bt_hidden").viewportChecker({
//classToAdd: 'bt_visible animated rotateIn',
//offset: 0,
//repeat: true,
//callbackFunction: function(elem, action){},
//scrollHorizontal: false
//});
//
//
//
});
<commit_msg>Animate.css tests<commit_after>// $ à la place de jQuery dans WordPress
var $ = jQuery.noConflict();

//Slider
var $slider = $('#slider');

// Initialize carousel
$slider.carousel({
    //interval: 4000
});

function doAnimations(elems) {
    var animEndEv = 'webkitAnimationEnd animationend';

    elems.each(function () {
        var $this = $(this),
            $animationType = $this.data('animation');

        // Add animate.css classes to
        // the elements to be animated
        // Remove animate.css classes
        // once the animation event has ended
        $this.addClass($animationType).one(animEndEv, function () {
            $this.removeClass($animationType);
        });
    });
}

// Select the elements to be animated
// in the first slide on page load
var $firstAnimatingElems = $slider.find('.item:first').find('[data-animation ^= "animated"]');

// Apply the animation using our function
doAnimations($firstAnimatingElems);

// Attach our doAnimations() function to the
// carousel's slide.bs.carousel event
$slider.on('slide.bs.carousel', function (e) {
    // Select the elements to be animated inside the active slide
    var $animatingElems = $(e.relatedTarget)
        .find("[data-animation ^= 'animated']");
    doAnimations($animatingElems);
});

// Carousel Swipe touch
$(document).ready(function () {

    //Enable swiping...
    $(".carousel-inner").swipe({
        //Generic swipe handler for all directions
        swipeLeft: function (event, direction, distance, duration, fingerCount) {
            $(this).parent().carousel('prev');
        },
        swipeRight: function () {
            $(this).parent().carousel('next');
        },
        //Default is 75px, set to 0 for demo so any distance triggers swipe
        threshold: 0
    });
});

// Ajouter une class au ul des widgets
$(document).ready(function () {
    $('.widget > ul').addClass('list-unstyled');
});

//Fastclick
$(function () {
    FastClick.attach(document.body);
});

//Scroll to top
$(function () {

    $(document).on('scroll', function () {

        if ($(window).scrollTop() > 300) {
            $('.scroll-top-wrapper').removeClass('fadeOutDownBig');
            $('.scroll-top-wrapper').addClass('show animated fadeInUpBig');
        } else {
            $('.scroll-top-wrapper').removeClass('show');
            $('.scroll-top-wrapper').addClass('animated fadeOutDownBig');
        }
    });

    $('.scroll-top-wrapper').on('click', scrollToTop);
});

function scrollToTop() {
    verticalOffset = typeof (verticalOffset) !== 'undefined' ? verticalOffset : 0;
    element = $('body');
    offset = element.offset();
    offsetTop = offset.top;
    $('html, body').animate({
        scrollTop: offsetTop
    }, 200, 'linear');
}

// Ajouter une class aux éléments d'un menu (pour hover.css)
$(document).ready(function () {
    $('#fullscreen-panel li > a').addClass('hvr-grow');
});

// ADD SLIDEDOWN ANIMATION TO DROPDOWN //
$('.dropdown').on('show.bs.dropdown', function(e){
    $(this).find('.dropdown-menu').first().stop(true, true).fadeIn();
});

// ADD SLIDEUP ANIMATION TO DROPDOWN //
$('.dropdown').on('hide.bs.dropdown', function(e){
    $(this).find('.dropdown-menu').first().stop(true, true).fadeOut();
});

//animate.css
jQuery(document).ready(function() {
    jQuery('#products .product').viewportChecker({
        classToAdd: 'animated bounceIn',
        offset: 0,
        repeat: true,
        callbackFunction: function(elem, action){},
        scrollHorizontal: false
    });
});<|endoftext|>


<commit_before>"use strict";

var supportsLocalStorage = typeof(Storage) !== "undefined";
var settings = {
    showRowRaceNone: true,
    showRowRaceProtoss: true,
    showRowRaceTerran: true,
    showRowRaceZerg: true,
    showRowOffline: false,
    showColRace: false,
    showColViewers: true,
    showColPeak: false,
    showColDuration: true,
    showColLastSeen: false,
    autoRefresh: true,
    useFullScreenLinks: false,

    keys: (function() {
        return Object.keys(this).filter(s => typeof(this[s]) == "boolean")
    }),

    visibilityKeys: (function() {
        return this.keys().filter(s => s.substring(0, 4) === "show");
    }),

    loadSettings: function() {
        //console.log("loadSettings");
        var settingKeys = this.keys();
        for (var i = 0; i < settingKeys.length; i++) {
            var setting = settingKeys[i];
            if (supportsLocalStorage && localStorage.hasOwnProperty(setting)) {
                this[setting] = localStorage[setting] === "true";
            }
            var checkboxId = this.settingToCheckboxId(setting);
            var element = $("#" + checkboxId);
            //console.log(setting + ": " + localStorage[setting]);
            if (element.length && element.is(":checked") !== this[setting])
            {
                element.click();
                //console.log("Fired click event");
            }
        }
    },

    saveSettings: function() {
        if (!supportsLocalStorage) {
            //console.log("localStorage not supported");
            return;
        }
        //console.log("saveSettings");
        var settingKeys = this.keys();
        for (var i = 0; i < settingKeys.length; i++) {
            var setting = settingKeys[i];
            localStorage[setting] = Boolean(this[setting]);
            //console.log(setting + ": " + localStorage[setting]);
        }
    },

    settingToCheckboxId: function(s) {
        return "checkbox-" + s.replace(/([A-Z])/g, function (x, y){return "-" + y.toLowerCase()}).replace(/^-/, "");
    },

    settingToClassName: function(s) {
        switch (s) {
            case "showRowRaceNone":
                return "race-none";
            case "showRowRaceProtoss":
                return "race-protoss";
            case "showRowRaceTerran":
                return "race-terran";
            case "showRowRaceZerg":
                return "race-zerg";
            case "showRowOffline":
                return "stream-row-offline";
            case "showColRace":
                return "stream-col-race";
            case "showColViewers":
                return "stream-col-viewers";
            case "showColPeak":
                return "stream-col-peak";
            case "showColDuration":
                return "stream-col-duration";
            case "showColLastSeen":
                return "stream-col-last-seen";
            default:
                throw "Unknown value in switch statement: " + setting;
        }
    },

    callback: function(setting) {
        if (setting === "autoRefresh") {
            timer.updateTimerStatus();
            return;
        }

        if (setting === "useFullScreenLinks") {
            $("a.stream-link").each(function() {
                var streamLink = $(this).attr("href");
                var streamId = streamLink.split("/")[3];
                var updatedStreamLink = getStreamUrl(streamId, settings["useFullScreenLinks"]);
                $(this).attr("href", updatedStreamLink);
            });
            return;
        }

        var className = this.settingToClassName(setting);
        if (this[setting]) {
            if (setting === "showRowOffline") {
                var showRaceSettings = this.keys().filter(s => s.substring(0, 11) === "showRowRace");
                for (var i = 0; i < showRaceSettings.length; i++) {
                    var showRateSetting = showRaceSettings[i];
                    if (this[showRateSetting]) {
                        var query = "." + className + "." + this.settingToClassName(showRateSetting);
                        $(query).show();
                    }
                }
            } else if (setting.substring(0, 11) === "showRowRace" && !this["showRowOffline"]) {
                $("." + className + ".stream-row-online").show();
            } else {
                $("." + className).show();
            }
        } else {
            $("." + className).hide();
        }
    }
};

function getStreamUrl(streamId, useFullScreenLink) {
    var url = "//play.afreecatv.com/" + streamId;
    if (useFullScreenLink) {
        url += "/embed";
    }
    return url;
}

function getLastSeenStatus(isOnline, lastSeen) {
    if (isOnline) {
        return "online";
    } else if (lastSeen === null) {
        return "never seen";
    } else {
        return moment(lastSeen).fromNow();
    }
}

function replaceTable(streams, updateTime) {
    var idTableStreams = "streams";
    var newTbody = document.createElement("tbody");

    let idCounter = 0;
    for (var key in streams) {
        if (key.substring(0, 8) != "afreeca_") {
            continue;
        }
        if (streams.hasOwnProperty(key)) {
            var stream = streams[key];
            var game = stream["game"];
            if (game != "brood war") {
                continue;
            }

            var nickname = stream["nickname"];
            var race = stream["game_info"]["race"];
            var raceText = race ? race : "";
            var viewers = stream["viewers"];
            var maxViewers = stream["max_viewers"];
            var isOnline = stream["online_since"] !== null;
            var durationText = "offline";
            var durationValue = 0;
            if (stream["online_since"] !== null) {
                var duration = moment.duration(moment(updateTime).diff(moment(stream["online_since"])));
                durationText = duration.humanize();
                durationValue = duration.asMinutes();
            }
            var lastSeenText = getLastSeenStatus(isOnline, stream["last_seen"]);
            var lastSeenValue = stream["last_seen"] || 0;
            var url = getStreamUrl(stream["id"], settings["useFullScreenLinks"]);
            var broadcast = stream["image"];

            var cells = [
                [nickname,     { "class": [ "stream-col-nickname" ] } ],
                [raceText,     { "class": [ "stream-col-race", "text-capitalize" ] } ],
                [viewers,      { "class": [ "stream-col-viewers", "text-center" ] } ],
                [maxViewers,   { "class": [ "stream-col-peak", "text-center" ] } ],
                [durationText, { "class": [ "stream-col-duration", "text-center" ], "data-value": durationValue } ],
                [lastSeenText, { "class": [ "stream-col-last-seen", "text-center" ], "data-value": lastSeenValue } ],
            ];

            var newRow = newTbody.insertRow(-1);
            newRow.classList.add(isOnline ? "stream-row-online" : "stream-row-offline");
            newRow.classList.add("race-" + (race || "none"));
            for (var i = 0; i < cells.length; i++) {
                var text = cells[i][0];
                var data = cells[i][1];
                var newCell = newRow.insertCell(-1);
                if (data !== null) {
                    for (var key in data) {
                        if (data.hasOwnProperty(key)) {
                            if (key === "class") {
                                for (var j = 0; j < data[key].length; j++) {
                                    newCell.classList.add(data[key][j]);
                                }
                            } else if (key.slice(0, 5) === "data-") {
                                newCell.setAttribute(key, data[key]);
                            }
                        }
                    }
                }
                if (i == 0) {
                    let id = "preview_" + idCounter++;
                    var span = document.createElement("span");
                    var raceAnchor = document.createElement("a");
                    raceAnchor.classList.add("race-emoji");
                    raceAnchor.classList.add("race-emoji-popover");
                    raceAnchor.setAttribute("triggerId", id);
                    raceAnchor.innerHTML = race == "zerg"    ? "🦂"
                                         : race == "protoss" ? "🛸"
                                         : race == "terran"  ? "🌎"
                                                             : "📺";
                    var linkAnchor = document.createElement("a");
                    linkAnchor.id = id;
                    linkAnchor.href = url;
                    linkAnchor.rel = "noreferrer";
                    linkAnchor.classList.add("stream-link");
                    linkAnchor.appendChild(document.createTextNode(text));
                    if (broadcast) {
                        // Hover preview for desktop
                        linkAnchor.setAttribute("data-image", broadcast);
                        linkAnchor.setAttribute("data-placement", "right");
                        linkAnchor.setAttribute("data-content", "<img src='" + broadcast + "' width='470' height='270'>");
                        linkAnchor.setAttribute("data-html", "true");
                        linkAnchor.setAttribute("data-trigger", "hover");
                        linkAnchor.setAttribute("data-toggle", "popover");
                        linkAnchor.setAttribute("tabindex", "0");
                        linkAnchor.setAttribute("title", '');

                        // Preload images
                        new Image().src = broadcast;
                    }

                    span.appendChild(raceAnchor);
                    span.appendChild(document.createTextNode(" "));
                    span.appendChild(linkAnchor);
                    newCell.appendChild(span);
                } else {
                    newCell.appendChild(document.createTextNode(text));
                }
            }
        }
    }

    var table = document.getElementById(idTableStreams);
    var oldTbody = document.getElementById(idTableStreams).getElementsByTagName("tbody")[0];
    table.replaceChild(newTbody, oldTbody);
}

function setLastUpdate(timeStr) {
    $("#text-last-updated").text(moment(timeStr).format("lll"));
}

var previewDiv = $("#preview")
var previewImage = $("#preview-image");
var divShown = false;
var updater = {
    json: null,

    refreshStreams: function() {
        this.refreshStarted();
        var promise = $.getJSON('/streams.json');
        promise.done((function(data) {
            this.json = data;
            this.refreshComplete();
        }).bind(this));
    },

    refreshStarted: function() {
        $("#icon-refresh").addClass("fa-spin");
        $("#icon-refresh").addClass("active");
        timer.resetTimer();
    },

    refreshComplete: function() {
        let hidePopovers = function() {
            $('.popover').popover('hide');
        };

        hidePopovers();

        replaceTable(this.json["streams"], this.json["last_update"]);
        setLastUpdate(this.json["last_update"]);
        $.bootstrapSortable(true);
        var visibilitySettings = settings.visibilityKeys();
        for (var i = 0; i < visibilitySettings.length; i++) {
            settings.callback(visibilitySettings[i]);
        }
        $("#icon-refresh").removeClass("fa-spin");
        $("#icon-refresh").removeClass("active");

        $('[data-toggle="popover"]').popover();

        $('.race-emoji-popover').click(function(e) {
            hidePopovers();
            let triggerId = $(this).attr('triggerId');
            $('#' + triggerId).popover('toggle');
            e.stopPropagation();
        });

        $('html').click(hidePopovers);
    }
}

var timer = {
    timerDuration: 60,
    timerElapsed: 0,
    timerId: null,

    updateTimerStatus: function() {
        if (settings.autoRefresh && this.timerId == null) {
            this.startTimer();
        }
    },

    resetTimer: function() {
        this.timerElapsed = 0;
    },

    stopTimer: function() {
        if (this.timerId != null) {
            clearInterval(this.timerId);
            this.timerId = null;
        }
    },

    startTimer: function() {
        this.timerId = setInterval((function() {
            ++this.timerElapsed;
            if (settings.autoRefresh && this.timerElapsed >= this.timerDuration) {
                updater.refreshStreams();
            }
        }).bind(this), 1000);
    }
}

jQuery(document).ready(function($) {
    $("#btn-refresh").click(function() {
        updater.refreshStreams();
    });

    var visibilitySettings = settings.visibilityKeys();
    for (var i = 0; i < visibilitySettings.length; i++) {
        (function() {
            var setting = visibilitySettings[i];
            var checkboxId = settings.settingToCheckboxId(setting);
            $("#" + checkboxId).click(function() {
                settings[setting] = $("#" + checkboxId).is(":checked");
                settings.callback(setting);
            });
        })();
    }

    $("#checkbox-auto-refresh").click(function() {
        settings["autoRefresh"] = $("#checkbox-auto-refresh").is(":checked");
        settings.callback("autoRefresh");
    });

    $("#checkbox-use-full-screen-links").click(function() {
        settings["useFullScreenLinks"] = $("#checkbox-use-full-screen-links").is(":checked");
        settings.callback("useFullScreenLinks");
    });

    settings.loadSettings();

    watch(settings, settings.keys(), function(){
        settings.saveSettings();
    });

    updater.refreshStreams();
});
<commit_msg>change Last Seen text<commit_after>"use strict";

var supportsLocalStorage = typeof(Storage) !== "undefined";
var settings = {
    showRowRaceNone: true,
    showRowRaceProtoss: true,
    showRowRaceTerran: true,
    showRowRaceZerg: true,
    showRowOffline: false,
    showColRace: false,
    showColViewers: true,
    showColPeak: false,
    showColDuration: true,
    showColLastSeen: false,
    autoRefresh: true,
    useFullScreenLinks: false,

    keys: (function() {
        return Object.keys(this).filter(s => typeof(this[s]) == "boolean")
    }),

    visibilityKeys: (function() {
        return this.keys().filter(s => s.substring(0, 4) === "show");
    }),

    loadSettings: function() {
        //console.log("loadSettings");
        var settingKeys = this.keys();
        for (var i = 0; i < settingKeys.length; i++) {
            var setting = settingKeys[i];
            if (supportsLocalStorage && localStorage.hasOwnProperty(setting)) {
                this[setting] = localStorage[setting] === "true";
            }
            var checkboxId = this.settingToCheckboxId(setting);
            var element = $("#" + checkboxId);
            //console.log(setting + ": " + localStorage[setting]);
            if (element.length && element.is(":checked") !== this[setting])
            {
                element.click();
                //console.log("Fired click event");
            }
        }
    },

    saveSettings: function() {
        if (!supportsLocalStorage) {
            //console.log("localStorage not supported");
            return;
        }
        //console.log("saveSettings");
        var settingKeys = this.keys();
        for (var i = 0; i < settingKeys.length; i++) {
            var setting = settingKeys[i];
            localStorage[setting] = Boolean(this[setting]);
            //console.log(setting + ": " + localStorage[setting]);
        }
    },

    settingToCheckboxId: function(s) {
        return "checkbox-" + s.replace(/([A-Z])/g, function (x, y){return "-" + y.toLowerCase()}).replace(/^-/, "");
    },

    settingToClassName: function(s) {
        switch (s) {
            case "showRowRaceNone":
                return "race-none";
            case "showRowRaceProtoss":
                return "race-protoss";
            case "showRowRaceTerran":
                return "race-terran";
            case "showRowRaceZerg":
                return "race-zerg";
            case "showRowOffline":
                return "stream-row-offline";
            case "showColRace":
                return "stream-col-race";
            case "showColViewers":
                return "stream-col-viewers";
            case "showColPeak":
                return "stream-col-peak";
            case "showColDuration":
                return "stream-col-duration";
            case "showColLastSeen":
                return "stream-col-last-seen";
            default:
                throw "Unknown value in switch statement: " + setting;
        }
    },

    callback: function(setting) {
        if (setting === "autoRefresh") {
            timer.updateTimerStatus();
            return;
        }

        if (setting === "useFullScreenLinks") {
            $("a.stream-link").each(function() {
                var streamLink = $(this).attr("href");
                var streamId = streamLink.split("/")[3];
                var updatedStreamLink = getStreamUrl(streamId, settings["useFullScreenLinks"]);
                $(this).attr("href", updatedStreamLink);
            });
            return;
        }

        var className = this.settingToClassName(setting);
        if (this[setting]) {
            if (setting === "showRowOffline") {
                var showRaceSettings = this.keys().filter(s => s.substring(0, 11) === "showRowRace");
                for (var i = 0; i < showRaceSettings.length; i++) {
                    var showRateSetting = showRaceSettings[i];
                    if (this[showRateSetting]) {
                        var query = "." + className + "." + this.settingToClassName(showRateSetting);
                        $(query).show();
                    }
                }
            } else if (setting.substring(0, 11) === "showRowRace" && !this["showRowOffline"]) {
                $("." + className + ".stream-row-online").show();
            } else {
                $("." + className).show();
            }
        } else {
            $("." + className).hide();
        }
    }
};

function getStreamUrl(streamId, useFullScreenLink) {
    var url = "//play.afreecatv.com/" + streamId;
    if (useFullScreenLink) {
        url += "/embed";
    }
    return url;
}

function getLastSeenStatus(isOnline, lastSeen) {
    if (isOnline) {
        return "now";
    } else if (lastSeen === null) {
        return "never";
    } else {
        return moment(lastSeen).fromNow();
    }
}

function replaceTable(streams, updateTime) {
    var idTableStreams = "streams";
    var newTbody = document.createElement("tbody");

    let idCounter = 0;
    for (var key in streams) {
        if (key.substring(0, 8) != "afreeca_") {
            continue;
        }
        if (streams.hasOwnProperty(key)) {
            var stream = streams[key];
            var game = stream["game"];
            if (game != "brood war") {
                continue;
            }

            var nickname = stream["nickname"];
            var race = stream["game_info"]["race"];
            var raceText = race ? race : "";
            var viewers = stream["viewers"];
            var maxViewers = stream["max_viewers"];
            var isOnline = stream["online_since"] !== null;
            var durationText = "offline";
            var durationValue = 0;
            if (stream["online_since"] !== null) {
                var duration = moment.duration(moment(updateTime).diff(moment(stream["online_since"])));
                durationText = duration.humanize();
                durationValue = duration.asMinutes();
            }
            var lastSeenText = getLastSeenStatus(isOnline, stream["last_seen"]);
            var lastSeenValue = stream["last_seen"] || 0;
            var url = getStreamUrl(stream["id"], settings["useFullScreenLinks"]);
            var broadcast = stream["image"];

            var cells = [
                [nickname,     { "class": [ "stream-col-nickname" ] } ],
                [raceText,     { "class": [ "stream-col-race", "text-capitalize" ] } ],
                [viewers,      { "class": [ "stream-col-viewers", "text-center" ] } ],
                [maxViewers,   { "class": [ "stream-col-peak", "text-center" ] } ],
                [durationText, { "class": [ "stream-col-duration", "text-center" ], "data-value": durationValue } ],
                [lastSeenText, { "class": [ "stream-col-last-seen", "text-center" ], "data-value": lastSeenValue } ],
            ];

            var newRow = newTbody.insertRow(-1);
            newRow.classList.add(isOnline ? "stream-row-online" : "stream-row-offline");
            newRow.classList.add("race-" + (race || "none"));
            for (var i = 0; i < cells.length; i++) {
                var text = cells[i][0];
                var data = cells[i][1];
                var newCell = newRow.insertCell(-1);
                if (data !== null) {
                    for (var key in data) {
                        if (data.hasOwnProperty(key)) {
                            if (key === "class") {
                                for (var j = 0; j < data[key].length; j++) {
                                    newCell.classList.add(data[key][j]);
                                }
                            } else if (key.slice(0, 5) === "data-") {
                                newCell.setAttribute(key, data[key]);
                            }
                        }
                    }
                }
                if (i == 0) {
                    let id = "preview_" + idCounter++;
                    var span = document.createElement("span");
                    var raceAnchor = document.createElement("a");
                    raceAnchor.classList.add("race-emoji");
                    raceAnchor.classList.add("race-emoji-popover");
                    raceAnchor.setAttribute("triggerId", id);
                    raceAnchor.innerHTML = race == "zerg"    ? "🦂"
                                         : race == "protoss" ? "🛸"
                                         : race == "terran"  ? "🌎"
                                                             : "📺";
                    var linkAnchor = document.createElement("a");
                    linkAnchor.id = id;
                    linkAnchor.href = url;
                    linkAnchor.rel = "noreferrer";
                    linkAnchor.classList.add("stream-link");
                    linkAnchor.appendChild(document.createTextNode(text));
                    if (broadcast) {
                        // Hover preview for desktop
                        linkAnchor.setAttribute("data-image", broadcast);
                        linkAnchor.setAttribute("data-placement", "right");
                        linkAnchor.setAttribute("data-content", "<img src='" + broadcast + "' width='470' height='270'>");
                        linkAnchor.setAttribute("data-html", "true");
                        linkAnchor.setAttribute("data-trigger", "hover");
                        linkAnchor.setAttribute("data-toggle", "popover");
                        linkAnchor.setAttribute("tabindex", "0");
                        linkAnchor.setAttribute("title", '');

                        // Preload images
                        new Image().src = broadcast;
                    }

                    span.appendChild(raceAnchor);
                    span.appendChild(document.createTextNode(" "));
                    span.appendChild(linkAnchor);
                    newCell.appendChild(span);
                } else {
                    newCell.appendChild(document.createTextNode(text));
                }
            }
        }
    }

    var table = document.getElementById(idTableStreams);
    var oldTbody = document.getElementById(idTableStreams).getElementsByTagName("tbody")[0];
    table.replaceChild(newTbody, oldTbody);
}

function setLastUpdate(timeStr) {
    $("#text-last-updated").text(moment(timeStr).format("lll"));
}

var previewDiv = $("#preview")
var previewImage = $("#preview-image");
var divShown = false;
var updater = {
    json: null,

    refreshStreams: function() {
        this.refreshStarted();
        var promise = $.getJSON('/streams.json');
        promise.done((function(data) {
            this.json = data;
            this.refreshComplete();
        }).bind(this));
    },

    refreshStarted: function() {
        $("#icon-refresh").addClass("fa-spin");
        $("#icon-refresh").addClass("active");
        timer.resetTimer();
    },

    refreshComplete: function() {
        let hidePopovers = function() {
            $('.popover').popover('hide');
        };

        hidePopovers();

        replaceTable(this.json["streams"], this.json["last_update"]);
        setLastUpdate(this.json["last_update"]);
        $.bootstrapSortable(true);
        var visibilitySettings = settings.visibilityKeys();
        for (var i = 0; i < visibilitySettings.length; i++) {
            settings.callback(visibilitySettings[i]);
        }
        $("#icon-refresh").removeClass("fa-spin");
        $("#icon-refresh").removeClass("active");

        $('[data-toggle="popover"]').popover();

        $('.race-emoji-popover').click(function(e) {
            hidePopovers();
            let triggerId = $(this).attr('triggerId');
            $('#' + triggerId).popover('toggle');
            e.stopPropagation();
        });

        $('html').click(hidePopovers);
    }
}

var timer = {
    timerDuration: 60,
    timerElapsed: 0,
    timerId: null,

    updateTimerStatus: function() {
        if (settings.autoRefresh && this.timerId == null) {
            this.startTimer();
        }
    },

    resetTimer: function() {
        this.timerElapsed = 0;
    },

    stopTimer: function() {
        if (this.timerId != null) {
            clearInterval(this.timerId);
            this.timerId = null;
        }
    },

    startTimer: function() {
        this.timerId = setInterval((function() {
            ++this.timerElapsed;
            if (settings.autoRefresh && this.timerElapsed >= this.timerDuration) {
                updater.refreshStreams();
            }
        }).bind(this), 1000);
    }
}

jQuery(document).ready(function($) {
    $("#btn-refresh").click(function() {
        updater.refreshStreams();
    });

    var visibilitySettings = settings.visibilityKeys();
    for (var i = 0; i < visibilitySettings.length; i++) {
        (function() {
            var setting = visibilitySettings[i];
            var checkboxId = settings.settingToCheckboxId(setting);
            $("#" + checkboxId).click(function() {
                settings[setting] = $("#" + checkboxId).is(":checked");
                settings.callback(setting);
            });
        })();
    }

    $("#checkbox-auto-refresh").click(function() {
        settings["autoRefresh"] = $("#checkbox-auto-refresh").is(":checked");
        settings.callback("autoRefresh");
    });

    $("#checkbox-use-full-screen-links").click(function() {
        settings["useFullScreenLinks"] = $("#checkbox-use-full-screen-links").is(":checked");
        settings.callback("useFullScreenLinks");
    });

    settings.loadSettings();

    watch(settings, settings.keys(), function(){
        settings.saveSettings();
    });

    updater.refreshStreams();
});
<|endoftext|>


<commit_before>'use strict';

var Resource = require('resourcejs');
var async = require('async');
var mongoose = require('mongoose');
var vm = require('vm');
var _ = require('lodash');
var debug = require('debug')('formio:error');

/**
 * The ActionIndex export.
 *
 * @param router
 *
 * @returns {{actions: {}, register: Function, search: Function, execute: Function}}
 */
module.exports = function(router) {
  var Action = router.formio.Action;
  var hook = require('../util/hook')(router.formio);

  /**
   * Create the ActionIndex object.
   *
   * @type {{actions: {}, register: Function, search: Function, execute: Function}}
   */
  var ActionIndex = {

    /**
     * A list of all the actions.
     */
    actions: hook.alter('actions', {
      email: require('./EmailAction')(router),
      webhook: require('./WebhookAction')(router),
      sql: require('./SQLAction')(router),
      role: require('./RoleAction')(router),
      resetpass: require('./ResetPassword')(router),
      save: require('./SaveSubmission')(router),
      login: require('./LoginAction')(router)
    }),

    /**
     * The model to use for each Action.
     */
    model: mongoose.model('action', hook.alter('actionSchema', Action.schema)),

    /**
     * Load all actions for a provided form.
     *
     * @param req
     * @param next
     * @returns {*}
     */
    loadActions: function(req, res, next) {
      if (!req.actions) {
        req.actions = {};
      }

      var form = req.formId;
      if (!form) {
        return next();
      }

      // Use cache if it is available.
      if (req.actions && req.actions[form]) {
        return next(null, req.actions[form]);
      }

      // Find the actions associated with this form.
      this.model.find({
        form: form,
        deleted: {$eq: null}
      })
      .sort('-priority')
      .exec(function(err, result) {
        if (err) {
          return next(err);
        }

        // Iterate through all of the actions and load them.
        var actions = [];
        _.each(result, function(action) {
          if (!this.actions.hasOwnProperty(action.name)) {
            return;
          }

          // Create the action class.
          var ActionClass = this.actions[action.name];
          actions.push(new ActionClass(action, req, res));
        }.bind(this));

        req.actions[form] = actions;
        return next(null, actions);
      }.bind(this));
    },

    /**
     * Find an action within the available actions for this form.
     *
     * @param handler
     * @param method
     * @param req
     * @param next
     */
    search: function(handler, method, req, res, next) {
      if (!req.formId) {
        return next(null, []);
      }

      // Make sure we have actions attached to the request.
      if (req.actions) {
        var actions = [];
        _.each(req.actions[req.formId], function(action) {
          if (
            (!handler || action.handler.indexOf(handler) !== -1) &&
            (!method || action.method.indexOf(method) !== -1)
          ) {
            actions.push(action);
          }
        });
        return next(null, actions);
      }
      else {
        // Load the actions.
        this.loadActions(req, res, function(err) {
          if (err) {
            return next(err);
          }
          this.search(handler, method, req, res, next);
        }.bind(this));
      }
    },

    /**
     * Load an initialize all actions for this form.
     *
     * @param req
     * @param res
     * @param next
     */
    initialize: function(method, req, res, next) {
      this.search(null, method, req, res, function(err, actions) {
        if (err) {
          return next(err);
        }

        // Iterate through each action.
        async.forEachOf(actions, function(action, index, done) {
          if (actions[index].initialize) {
            actions[index].initialize(method, req, res, done);
          }
          else {
            done();
          }
        }.bind(this), next);
      }.bind(this));
    },

    /**
     * Execute an action provided a handler, form, and request params.
     *
     * @param handler
     * @param method
     * @param req
     * @param res
     * @param next
     */
    execute: function(handler, method, req, res, next) {
      // Find the available actions.
      this.search(handler, method, req, res, function(err, actions) {
        if (err) {
          return next(err);
        }

        // Iterate and execute each action.
        async.eachSeries(actions, function(action, cb) {
          var execute = true;

          // See if there is a custom condition.
          if (
            action.condition &&
            action.condition.custom
          ) {
            try {
              var script = new vm.Script(action.condition.custom);
              var sandbox = {
                data: req.body.data,
                execute: false
              };
              script.runInContext(vm.createContext(sandbox), {
                timeout: 500
              });
              execute = sandbox.execute;
            }
            catch (e) {
              debug(e);
              execute = false;
            }
          }

          // See if a condition is not established within the action.
          if (
            action.condition &&
            action.condition.field &&
            (action.condition.eq === 'equals') &&
            (req.body.data[action.condition.field] !== action.condition.value)
          ) {
            execute = false;
          }

          if (!execute) {
            return cb();
          }

          // Resolve the action.
          action.resolve(handler, method, req, res, cb);
        }, function(err) {
          if (err) {
            return next(err);
          }

          next();
        });
      });
    }
  };

  /**
   * Get the settings form for each action.
   *
   * @param action
   */
  var getSettingsForm = function(action, req) {
    var basePath = hook.alter('url', '/form', req);
    var dataSrc = basePath + '/' + req.params.formId + '/components';
    // The default settings form.
    var settingsForm = {
      components: [
        {type: 'hidden', input: true, key: 'priority'},
        {type: 'hidden', input: true, key: 'name'},
        {type: 'hidden', input: true, key: 'title'}
      ]
    };

    var mainSettings = {
      components: []
    };
    var conditionalSettings = {
      components: []
    };

    // If the defaults are read only.
    if (action.access && (action.access.handler === false)) {
      mainSettings.components.push({
        type: 'hidden',
        input: true,
        key: 'handler'
      });
    }
    else {
      mainSettings.components.push({
        type: 'select',
        input: true,
        key: 'handler',
        label: 'Handler',
        placeholder: 'Select which handler(s) you would like to trigger',
        dataSrc: 'json',
        data: {json: JSON.stringify([
          {
            name: 'before',
            title: 'Before'
          },
          {
            name: 'after',
            title: 'After'
          }
        ])},
        template: '{{ item.title }}',
        valueProperty: 'name',
        multiple: true
      });
    }

    if (action.access && (action.access.method === false)) {
      mainSettings.components.push({
        type: 'hidden',
        input: true,
        key: 'method'
      });
    }
    else {
      mainSettings.components.push({
        type: 'select',
        input: true,
        label: 'Methods',
        key: 'method',
        placeholder: 'Trigger action on method(s)',
        dataSrc: 'json',
        data: {json: JSON.stringify([
          {
            name: 'create',
            title: 'Create'
          },
          {
            name: 'update',
            title: 'Update'
          },
          {
            name: 'read',
            title: 'Read'
          },
          {
            name: 'delete',
            title: 'Delete'
          },
          {
            name: 'index',
            title: 'Index'
          }
        ])},
        template: '{{ item.title }}',
        valueProperty: 'name',
        multiple: true
      });
    }

    var customPlaceHolder = "// Example: Only execute if submitted roles has 'authenticated'.\n";
    customPlaceHolder +=    "execute = (data.roles.indexOf('authenticated') !== -1);";
    conditionalSettings.components.push({
      type: 'fieldset',
      input: false,
      tree: true,
      key: 'condition',
      components: [
        {
          type: 'columns',
          input: false,
          columns: [
            {
              components: [
                {
                  type: 'select',
                  input: true,
                  label: 'Trigger this action only if field',
                  key: 'condition[field]',
                  placeholder: 'Select the conditional field',
                  template: '{{ item.label || item.key }}',
                  dataSrc: 'url',
                  data: {url: dataSrc},
                  valueProperty: 'key',
                  multiple: false,
                  validate: {}
                },
                {
                  type : 'select',
                  input : true,
                  label : '',
                  key : 'condition[eq]',
                  placeholder : 'Select comparison',
                  template : '{{ item.label }}',
                  dataSrc : 'values',
                  data : {
                    values : [
                      {
                        value : 'equals',
                        label : 'Equals'
                      },
                      {
                        value : 'notEqual',
                        label : 'Does Not Equal'
                      }
                    ],
                    json : '',
                    url : '',
                    resource : ''
                  },
                  valueProperty : 'value',
                  multiple : false
                },
                {
                  input: true,
                  type: 'textfield',
                  inputType: 'text',
                  key: 'condition[value]',
                  placeholder: 'Enter value',
                  multiple: false
                }
              ]
            },
            {
              components: [
                {
                  type: 'well',
                  input: false,
                  components: [
                    {
                      type: 'htmlelement',
                      tag: 'h4',
                      input: false,
                      content: 'Or you can provide your own custom JavaScript condition logic here',
                      className: ''
                    },
                    {
                      label: '',
                      type: 'textarea',
                      input: true,
                      key: 'condition[custom]',
                      placeholder: customPlaceHolder
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    });

    // Create the settings form.
    var actionSettings = {
      type: 'fieldset',
      input: false,
      tree: true,
      key: 'settings',
      legend: 'Action Settings',
      components: []
    };

    // Add the settings form to the action settings.
    settingsForm.components.push(actionSettings);

    settingsForm.components.push({
      type: 'fieldset',
      input: false,
      tree: false,
      key: 'conditions',
      legend: 'Action Execution',
      components: mainSettings.components
    });

    settingsForm.components.push({
      type: 'fieldset',
      input: false,
      tree: false,
      key: 'conditions',
      legend: 'Action Conditions (optional)',
      components: conditionalSettings.components
    });

    settingsForm.components.push({
      type: 'htmlelement',
      tag: 'hr',
      input: false,
      content: '',
      className: ''
    });
    settingsForm.components.push({
      type: 'button',
      input: true,
      label: 'Save Action',
      key: 'submit',
      size: 'md',
      leftIcon: '',
      rightIcon: '',
      block: false,
      action: 'submit',
      disableOnInvalid: true,
      theme: 'primary'
    });

    // Return the settings form.
    return {
      actionSettings: actionSettings,
      settingsForm: settingsForm
    };
  };

  // Return a list of available actions.
  router.get('/form/:formId/actions', function(req, res, next) {
    var result = [];

    // Add an action to the results array.
    var addAction = function(action) {
      action.defaults = action.defaults || {};
      action.defaults = _.assign(action.defaults, {
        priority: action.priority || 0,
        name: action.name,
        title: action.title
      });

      hook.alter('actionInfo', action, req);
      result.push(action);
    };

    // Iterate through each of the available actions.
    async.eachSeries(_.values(ActionIndex.actions), function(action, callback) {
      action.info(req, res, function(err, info) {
        if (err) {
          return callback(err);
        }
        if (!info || (info.name === 'default')) {
          return callback();
        }

        addAction(info);
        callback();
      });
    }, function(err) {
      if (err) {
        return next(err);
      }

      res.json(result);
    });
  });

  // Return a list of available actions.
  router.get('/form/:formId/actions/:name', function(req, res, next) {
    if (ActionIndex.actions[req.params.name]) {
      var action = ActionIndex.actions[req.params.name];
      action.info(req, res, function(err, info) {
        if (err) {
          return next(err);
        }

        info.defaults = info.defaults || {};
        info.defaults = _.assign(info.defaults, {
          priority: info.priority || 0,
          name: info.name,
          title: info.title
        });

        var settings = getSettingsForm(action, req);
        action.settingsForm(req, res, function(err, settingsForm) {
          if (err) {
            return next(err);
          }

          // Add the ability to change the title, and add the other settings.
          settings.actionSettings.components = [{
            type: 'textfield',
            input: true,
            label: 'Title',
            key: 'title'
          }].concat(settingsForm);

          info.settingsForm = settings.settingsForm;
          info.settingsForm.action = hook.alter('url', '/form/' + req.params.formId + '/action', req);
          hook.alter('actionInfo', info, req);
          res.json(info);
        });
      });
    }
    else {
      return next('Action not found');
    }
  });

  // Before all middleware for actions.
  var actionPayload = function(req, res, next) {
    if (req.body) {
      // Translate the request body if data is provided.
      if (req.body.hasOwnProperty('data')) {
        req.body = req.body.data;
      }

      // Set the form on the request body.
      req.body.form = req.params.formId;

      // Make sure to store handler to lowercase.
      if (req.body.handler) {
        _.each(req.body.handler, function(handler, index) {
          req.body.handler[index] = handler.toLowerCase();
        });
      }

      // Make sure the method is uppercase.
      if (req.body.method) {
        _.each(req.body.method, function(method, index) {
          req.body.method[index] = method.toLowerCase();
        });
      }
    }

    req.modelQuery = req.modelQuery || this.model;
    req.countQuery = req.countQuery || this.model;
    req.modelQuery = req.modelQuery.find({form: req.params.formId}).sort('-priority');
    req.countQuery = req.countQuery.find({form: req.params.formId});
    next();
  };

  // After Index middleware for actions.
  var indexPayload = function(req, res, next) {
    res.resource.status = 200;
    _.each(res.resource.item, function(item) {
      if (ActionIndex.actions.hasOwnProperty(item.name)) {
        item = _.assign(item, ActionIndex.actions[item.name].info);
      }
    });

    next();
  };

  // Build the middleware stack.
  var handlers = {};
  var methods = ['Post', 'Get', 'Put', 'Index', 'Delete'];
  methods.forEach(function(method) {
    handlers['before' + method] = [
      router.formio.middleware.filterMongooseExists({field: 'deleted', isNull: true}),
      actionPayload
    ];
    handlers['after' + method] = [
      router.formio.middleware.filterResourcejsResponse(['deleted', '__v', 'externalTokens'])
    ];
  });

  // Add specific middleware to individual endpoints.
  handlers['beforeDelete'] = handlers['beforeDelete'].concat([router.formio.middleware.deleteActionHandler]);
  handlers['afterIndex'] = handlers['afterIndex'].concat([indexPayload]);

  /**
   * Create the REST properties using ResourceJS, as a nested resource of forms.
   *
   * Adds the endpoints:
   * [GET]    /form/:formId/action
   * [GET]    /form/:formId/action/:actionId
   * [PUT]    /form/:formId/action/:actionId
   * [POST]   /form/:formId/action/:actionId
   * [DELETE] /form/:formId/action/:actionId
   *
   * @TODO: Add `action` validation on POST/PUT with the keys inside `available`.
   */
  Resource(router, '/form/:formId', 'action', ActionIndex.model).rest(handlers);

  // Return the action index.
  return ActionIndex;
};
<commit_msg>Updating how action settings forms are rendered, to fix form rendering issues. Fixing duplicate field keys.<commit_after>'use strict';

var Resource = require('resourcejs');
var async = require('async');
var mongoose = require('mongoose');
var vm = require('vm');
var _ = require('lodash');
var debug = require('debug')('formio:error');

/**
 * The ActionIndex export.
 *
 * @param router
 *
 * @returns {{actions: {}, register: Function, search: Function, execute: Function}}
 */
module.exports = function(router) {
  var Action = router.formio.Action;
  var hook = require('../util/hook')(router.formio);

  /**
   * Create the ActionIndex object.
   *
   * @type {{actions: {}, register: Function, search: Function, execute: Function}}
   */
  var ActionIndex = {

    /**
     * A list of all the actions.
     */
    actions: hook.alter('actions', {
      email: require('./EmailAction')(router),
      webhook: require('./WebhookAction')(router),
      sql: require('./SQLAction')(router),
      role: require('./RoleAction')(router),
      resetpass: require('./ResetPassword')(router),
      save: require('./SaveSubmission')(router),
      login: require('./LoginAction')(router)
    }),

    /**
     * The model to use for each Action.
     */
    model: mongoose.model('action', hook.alter('actionSchema', Action.schema)),

    /**
     * Load all actions for a provided form.
     *
     * @param req
     * @param next
     * @returns {*}
     */
    loadActions: function(req, res, next) {
      if (!req.actions) {
        req.actions = {};
      }

      var form = req.formId;
      if (!form) {
        return next();
      }

      // Use cache if it is available.
      if (req.actions && req.actions[form]) {
        return next(null, req.actions[form]);
      }

      // Find the actions associated with this form.
      this.model.find({
        form: form,
        deleted: {$eq: null}
      })
      .sort('-priority')
      .exec(function(err, result) {
        if (err) {
          return next(err);
        }

        // Iterate through all of the actions and load them.
        var actions = [];
        _.each(result, function(action) {
          if (!this.actions.hasOwnProperty(action.name)) {
            return;
          }

          // Create the action class.
          var ActionClass = this.actions[action.name];
          actions.push(new ActionClass(action, req, res));
        }.bind(this));

        req.actions[form] = actions;
        return next(null, actions);
      }.bind(this));
    },

    /**
     * Find an action within the available actions for this form.
     *
     * @param handler
     * @param method
     * @param req
     * @param next
     */
    search: function(handler, method, req, res, next) {
      if (!req.formId) {
        return next(null, []);
      }

      // Make sure we have actions attached to the request.
      if (req.actions) {
        var actions = [];
        _.each(req.actions[req.formId], function(action) {
          if (
            (!handler || action.handler.indexOf(handler) !== -1) &&
            (!method || action.method.indexOf(method) !== -1)
          ) {
            actions.push(action);
          }
        });
        return next(null, actions);
      }
      else {
        // Load the actions.
        this.loadActions(req, res, function(err) {
          if (err) {
            return next(err);
          }
          this.search(handler, method, req, res, next);
        }.bind(this));
      }
    },

    /**
     * Load an initialize all actions for this form.
     *
     * @param req
     * @param res
     * @param next
     */
    initialize: function(method, req, res, next) {
      this.search(null, method, req, res, function(err, actions) {
        if (err) {
          return next(err);
        }

        // Iterate through each action.
        async.forEachOf(actions, function(action, index, done) {
          if (actions[index].initialize) {
            actions[index].initialize(method, req, res, done);
          }
          else {
            done();
          }
        }.bind(this), next);
      }.bind(this));
    },

    /**
     * Execute an action provided a handler, form, and request params.
     *
     * @param handler
     * @param method
     * @param req
     * @param res
     * @param next
     */
    execute: function(handler, method, req, res, next) {
      // Find the available actions.
      this.search(handler, method, req, res, function(err, actions) {
        if (err) {
          return next(err);
        }

        // Iterate and execute each action.
        async.eachSeries(actions, function(action, cb) {
          var execute = true;

          // See if there is a custom condition.
          if (
            action.condition &&
            action.condition.custom
          ) {
            try {
              var script = new vm.Script(action.condition.custom);
              var sandbox = {
                data: req.body.data,
                execute: false
              };
              script.runInContext(vm.createContext(sandbox), {
                timeout: 500
              });
              execute = sandbox.execute;
            }
            catch (e) {
              debug(e);
              execute = false;
            }
          }

          // See if a condition is not established within the action.
          if (
            action.condition &&
            action.condition.field &&
            (action.condition.eq === 'equals') &&
            (req.body.data[action.condition.field] !== action.condition.value)
          ) {
            execute = false;
          }

          if (!execute) {
            return cb();
          }

          // Resolve the action.
          action.resolve(handler, method, req, res, cb);
        }, function(err) {
          if (err) {
            return next(err);
          }

          next();
        });
      });
    }
  };

  /**
   * Get the settings form for each action.
   *
   * @param action
   */
  var getSettingsForm = function(action, req) {
    var basePath = hook.alter('url', '/form', req);
    var dataSrc = basePath + '/' + req.params.formId + '/components';
    var mainSettings = {
      components: []
    };
    var conditionalSettings = {
      components: []
    };

    // If the defaults are read only.
    if (action.access && (action.access.handler === false)) {
      mainSettings.components.push({
        type: 'hidden',
        input: true,
        key: 'handler'
      });
    }
    else {
      mainSettings.components.push({
        type: 'select',
        input: true,
        key: 'handler',
        label: 'Handler',
        placeholder: 'Select which handler(s) you would like to trigger',
        dataSrc: 'json',
        data: {json: JSON.stringify([
          {
            name: 'before',
            title: 'Before'
          },
          {
            name: 'after',
            title: 'After'
          }
        ])},
        template: '{{ item.title }}',
        valueProperty: 'name',
        multiple: true
      });
    }

    if (action.access && (action.access.method === false)) {
      mainSettings.components.push({
        type: 'hidden',
        input: true,
        key: 'method'
      });
    }
    else {
      mainSettings.components.push({
        type: 'select',
        input: true,
        label: 'Methods',
        key: 'method',
        placeholder: 'Trigger action on method(s)',
        dataSrc: 'json',
        data: {json: JSON.stringify([
          {
            name: 'create',
            title: 'Create'
          },
          {
            name: 'update',
            title: 'Update'
          },
          {
            name: 'read',
            title: 'Read'
          },
          {
            name: 'delete',
            title: 'Delete'
          },
          {
            name: 'index',
            title: 'Index'
          }
        ])},
        template: '{{ item.title }}',
        valueProperty: 'name',
        multiple: true
      });
    }

    var customPlaceHolder = "// Example: Only execute if submitted roles has 'authenticated'.\n";
    customPlaceHolder +=    "execute = (data.roles.indexOf('authenticated') !== -1);";
    conditionalSettings.components.push({
      type: 'fieldset',
      input: false,
      tree: true,
      components: [
        {
          type: 'columns',
          input: false,
          columns: [
            {
              components: [
                {
                  type: 'select',
                  input: true,
                  label: 'Trigger this action only if field',
                  key: 'field',
                  placeholder: 'Select the conditional field',
                  template: '{{ item.label || item.key }}',
                  dataSrc: 'url',
                  data: {url: dataSrc},
                  valueProperty: 'key',
                  multiple: false,
                  validate: {}
                },
                {
                  type : 'select',
                  input : true,
                  label : '',
                  key : 'eq',
                  placeholder : 'Select comparison',
                  template : '{{ item.label }}',
                  dataSrc : 'values',
                  data : {
                    values : [
                      {
                        value : 'equals',
                        label : 'Equals'
                      },
                      {
                        value : 'notEqual',
                        label : 'Does Not Equal'
                      }
                    ],
                    json : '',
                    url : '',
                    resource : ''
                  },
                  valueProperty : 'value',
                  multiple : false
                },
                {
                  input: true,
                  type: 'textfield',
                  inputType: 'text',
                  key: 'value',
                  placeholder: 'Enter value',
                  multiple: false
                }
              ]
            },
            {
              components: [
                {
                  type: 'well',
                  input: false,
                  components: [
                    {
                      type: 'htmlelement',
                      tag: 'h4',
                      input: false,
                      content: 'Or you can provide your own custom JavaScript condition logic here',
                      className: ''
                    },
                    {
                      label: '',
                      type: 'textarea',
                      input: true,
                      key: 'custom',
                      placeholder: customPlaceHolder
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    });

    // Create the settings form.
    var actionSettings = {
      type: 'fieldset',
      input: false,
      tree: true,
      legend: 'Action Settings',
      components: []
    };

    // The default settings form.
    var settingsForm = {
      components: [
        {type: 'hidden', input: true, key: 'priority'},
        {type: 'hidden', input: true, key: 'name'},
        {type: 'hidden', input: true, key: 'title'},
        actionSettings,
        {
          type: 'fieldset',
          input: false,
          tree: false,
          key: 'conditions',
          legend: 'Action Execution',
          components: mainSettings.components
        },
        {
          type: 'fieldset',
          input: false,
          tree: false,
          key: 'conditional',
          legend: 'Action Conditions (optional)',
          components: conditionalSettings.components
        },
        {
          type: 'htmlelement',
          tag: 'hr',
          input: false,
          content: '',
          className: ''
        },
        {
          type: 'button',
          input: true,
          label: 'Save Action',
          key: 'submit',
          size: 'md',
          leftIcon: '',
          rightIcon: '',
          block: false,
          action: 'submit',
          disableOnInvalid: true,
          theme: 'primary'
        }
      ]
    };

    // Return the settings form.
    return {
      actionSettings: actionSettings,
      settingsForm: settingsForm
    };
  };

  // Return a list of available actions.
  router.get('/form/:formId/actions', function(req, res, next) {
    var result = [];

    // Add an action to the results array.
    var addAction = function(action) {
      action.defaults = action.defaults || {};
      action.defaults = _.assign(action.defaults, {
        priority: action.priority || 0,
        name: action.name,
        title: action.title
      });

      hook.alter('actionInfo', action, req);
      result.push(action);
    };

    // Iterate through each of the available actions.
    async.eachSeries(_.values(ActionIndex.actions), function(action, callback) {
      action.info(req, res, function(err, info) {
        if (err) {
          return callback(err);
        }
        if (!info || (info.name === 'default')) {
          return callback();
        }

        addAction(info);
        callback();
      });
    }, function(err) {
      if (err) {
        return next(err);
      }

      res.json(result);
    });
  });

  // Return a list of available actions.
  router.get('/form/:formId/actions/:name', function(req, res, next) {
    if (ActionIndex.actions[req.params.name]) {
      var action = ActionIndex.actions[req.params.name];
      action.info(req, res, function(err, info) {
        if (err) {
          return next(err);
        }

        info.defaults = info.defaults || {};
        info.defaults = _.assign(info.defaults, {
          priority: info.priority || 0,
          name: info.name,
          title: info.title
        });

        var settings = getSettingsForm(action, req);
        action.settingsForm(req, res, function(err, settingsForm) {
          if (err) {
            return next(err);
          }

          // Add the ability to change the title, and add the other settings.
          settings.actionSettings.components = [{
            input: false,
            type: 'container',
            key: 'settings',
            components: [{
              type: 'textfield',
              input: true,
              label: 'Title',
              key: 'title'
            }].concat(settingsForm)
          }];

          info.settingsForm = settings.settingsForm;
          info.settingsForm.action = hook.alter('url', '/form/' + req.params.formId + '/action', req);
          hook.alter('actionInfo', info, req);
          res.json(info);
        });
      });
    }
    else {
      return next('Action not found');
    }
  });

  // Before all middleware for actions.
  var actionPayload = function(req, res, next) {
    if (req.body) {
      // Translate the request body if data is provided.
      if (req.body.hasOwnProperty('data')) {
        req.body = req.body.data;
      }

      // Set the form on the request body.
      req.body.form = req.params.formId;

      // Make sure to store handler to lowercase.
      if (req.body.handler) {
        _.each(req.body.handler, function(handler, index) {
          req.body.handler[index] = handler.toLowerCase();
        });
      }

      // Make sure the method is uppercase.
      if (req.body.method) {
        _.each(req.body.method, function(method, index) {
          req.body.method[index] = method.toLowerCase();
        });
      }
    }

    req.modelQuery = req.modelQuery || this.model;
    req.countQuery = req.countQuery || this.model;
    req.modelQuery = req.modelQuery.find({form: req.params.formId}).sort('-priority');
    req.countQuery = req.countQuery.find({form: req.params.formId});
    next();
  };

  // After Index middleware for actions.
  var indexPayload = function(req, res, next) {
    res.resource.status = 200;
    _.each(res.resource.item, function(item) {
      if (ActionIndex.actions.hasOwnProperty(item.name)) {
        item = _.assign(item, ActionIndex.actions[item.name].info);
      }
    });

    next();
  };

  // Build the middleware stack.
  var handlers = {};
  var methods = ['Post', 'Get', 'Put', 'Index', 'Delete'];
  methods.forEach(function(method) {
    handlers['before' + method] = [
      router.formio.middleware.filterMongooseExists({field: 'deleted', isNull: true}),
      actionPayload
    ];
    handlers['after' + method] = [
      router.formio.middleware.filterResourcejsResponse(['deleted', '__v', 'externalTokens'])
    ];
  });

  // Add specific middleware to individual endpoints.
  handlers['beforeDelete'] = handlers['beforeDelete'].concat([router.formio.middleware.deleteActionHandler]);
  handlers['afterIndex'] = handlers['afterIndex'].concat([indexPayload]);

  /**
   * Create the REST properties using ResourceJS, as a nested resource of forms.
   *
   * Adds the endpoints:
   * [GET]    /form/:formId/action
   * [GET]    /form/:formId/action/:actionId
   * [PUT]    /form/:formId/action/:actionId
   * [POST]   /form/:formId/action/:actionId
   * [DELETE] /form/:formId/action/:actionId
   *
   * @TODO: Add `action` validation on POST/PUT with the keys inside `available`.
   */
  Resource(router, '/form/:formId', 'action', ActionIndex.model).rest(handlers);

  // Return the action index.
  return ActionIndex;
};
<|endoftext|>