<commit_before>/* jslint node: true, esnext: true */

"use strict";

const endpoint = require('kronos-endpoint'),
	llm = require('loglevel-mixin'),
	stm = require('statetransition-mixin'),
	merge = require('merge-deep');

const actions = stm.prepareActions({
	start: {
		stopped: {
			target: "running",
			during: "starting",
			timeout: 5000
		}
	},
	stop: {
		running: {
			target: "stopped",
			during: "stopping",
			timeout: 5000
		},
		starting: {
			target: "stopped",
			during: "stopping",
			timeout: 5000
		}
	},
	remove: {
		stopped: {
			target: "removed"
		}
	}
});

// Steps plain attributes without special handling
// may be extended by some properties like writable,...
const ATTRIBUTES = ['description'];

const BaseStep = {
	"name": "kronos-step",
	"description": "This step is the base class for step implementations",

	/**
	 * Creates the properties for a given object
	 * @param {Object} manager The kronos service manager
	 * @param {String} (mandatory) name The Name of the step
	 * @param {Object} (optional) stepDefinition The step definition
	 *
	 */
	prepareProperties(manager, name, stepConfiguration) {
		let endpoints = {};

		// TODO ther is no better way ?
		const type = stepConfiguration.type ? stepConfiguration.type : this.name;

		const props = {
			name: {
				value: name
			},
			type: {
				value: type
			},
			endpoints: {
				value: endpoints
			},
			manager: {
				value: manager
			},
			/**
			 * @return {Boolean} if step is in a running state
			 */
			isRunning: {
				get: function () {
					return this.state === 'running' || this.state === 'starting';
				}
			}
		};

		ATTRIBUTES.forEach(a => {
			if (stepConfiguration[a] !== undefined) {
				props[a] = {
					value: stepConfiguration[a]
				};
			}
		});

		return props;
	},

	/**
	 * Called to initialize step
	 * Please note 'this' is not jet present
	 */
	initialize(manager, name, stepConfiguration, props) {},

	/**
	 * Called when step instance properties are present.
	 *
	 */
	finalize(manager, stepConfiguration) {},

	/**
	 * This method could be overwritten by a derived object to setup default endpoints.
	 * This method may be overwritten by derived classes.
	 * This will be called by the initialize method
	 * @param {Object} stepConfiguration The default step configuration
	 * @api protected
	 */
	createPredefinedEndpoints(stepConfiguration) {
		if (this.endpoints.log) return;

		const logEndpoint = new endpoint.SendEndpointDefault("log", this);
		if (this.manager.services && this.manager.services.logger) {
			logEndpoint.connected = this.manager.services.logger.endpoints.log;
		}
		this.addEndpoint(logEndpoint);
	},

	/**
	 * This function mixes the endpoint definitions of a step.
	 * So that extensions comming from the flow will be mixed into
	 * the definition from the step (The definition from the prototype).
	 * Only the 'endpoints' part will be extended
	 *
	 * @param def The step definition from the flow or step itslef.
	 * @return definition The new extended step definition
	 */
	inheritEndpoints(def) {
		const prototype = Object.getPrototypeOf(this);
		if (prototype && prototype.endpoints) {
			if (def && def.endpoints) {
				// before we can merge endpoints of type string needs to be converted
				for (const endpointName in def.endpoints) {
					let endpointDefinition = def.endpoints[endpointName];
					if (typeof endpointDefinition === 'string') {
						def.endpoints[endpointName] = {
							"target": endpointDefinition
						};
					}
				}

				// need to mix the definition
				const newEndpointDef = merge({}, prototype.endpoints, def.endpoints);
				def.endpoints = newEndpointDef;
			} else {
				if (!def) {
					def = {};
				}
				// just take the prototype definition
				def.endpoints = prototype.endpoints;
			}
		}
		return def;
	},

	/**
	 * Creates the endpoint objects defined as a combination from
	 * implementation and definition
	 * @param {Object} def The step configuration
	 * @api protected
	 */
	createEndpoints(def) {
		if (def && def.endpoints) {
			Object.keys(def.endpoints).forEach(name =>
				this.createEndpoint(name, def.endpoints[name]));
		}
	},

	createEndpoint(name, def) {
		let ep;
		if (def.in) ep = new endpoint.ReceiveEndpoint(name, this);
		if (def.out) ep = new endpoint.SendEndpoint(name, this);

		this.addEndpoint(ep);

		if (def.interceptors) {
			ep.interceptors = def.interceptors.map(icDef => this.manager.createInterceptorInstanceFromConfig(icDef, ep));
		}
	},

	/**
	 * @param {Endpoint} ep
	 */
	addEndpoint(ep) {
		this.endpoints[ep.name] = ep;
	},

	/**
	 * removes a endpoint
	 * @param {string} name
	 */
	removeEndpoint(name) {
		delete this.endpoints[name];
	},

	/**
	 * Sends a 'stepStateChanged' event to the manager.
	 * arguments are:
	 *  step,oldState,newState
	 * @param {String} oldState
	 * @param {String} newState
	 */
	stateChanged(oldState, newState) {
		this.trace(level => ({
			"message": "state changed",
			"oldState": oldState,
			"newState": newState
		}));
		this.manager.emit('stepStateChanged', this, oldState, newState);
	},

	/**
	 * Returns the string representation of this step
	 * @return {String} human readable name
	 */
	toString() {
		return this.name;
	},

	/**
	 * Deliver json representation
	 * @param {Objects} options
	 *  with the following flags:
	 *    includeRuntimeInfo - include runtime informtion like state
	 *    includeName - name of the step
	 *    includeDefaults - include all properties also the inherited and not overwittern ones
	 * @return json representation
	 */
	toJSONWithOptions(options) {
		const json = {
			type: this.type,
			endpoints: {}
		};

		if (options.includeName) {
			json.name = this.name;
		}

		if (options.includeRuntimeInfo) {
			json.state = this.state;
		}

		for (const endpointName in this.endpoints) {
			const ep = this.endpoints[endpointName];
			if (ep.isDefault) {
				if (options.includeDefaults) {
					json.endpoints[endpointName] = ep.toJSON();
				}
			} else {
				json.endpoints[endpointName] = ep.toJSON();
			}
		}

		ATTRIBUTES.forEach(a => {
			json[a] = this[a];
		});

		return json;
	},

	toJSON() {
		return this.toJSONWithOptions({
			includeRuntimeInfo: false,
			includeDefaults: false,
			includeName: false
		});
	},

	/**
	 * @return {Step} newly created step
	 */
	createInstance(stepDefinition, manager) {
		if (!manager) {
			throw new Error(`No Manager given in 'createInstance' for step '${stepDefinition.name}'`);
		}

		const props = this.prepareProperties(manager, stepDefinition.name, stepDefinition);
		this.initialize(manager, stepDefinition.name, stepDefinition, props);
		const newInstance = Object.create(this, props);

		stm.defineStateTransitionProperties(newInstance, actions, "stopped");

		// get the default loglevel for this step
		let logLevel = llm.defaultLogLevels[stepDefinition.logLevel] || llm.defaultLogLevels.info;

		// create the properties to store the loglevel
		llm.defineLogLevelProperties(newInstance, llm.defaultLogLevels, logLevel);

		// mix the endpoints from the prototype with the new definition
		stepDefinition = newInstance.inheritEndpoints(stepDefinition);

		newInstance.createEndpoints(stepDefinition);
		newInstance.createPredefinedEndpoints(stepDefinition);
		newInstance.finalize(stepDefinition);

		return newInstance;
	},

	log(level, arg) {
		const logevent = llm.makeLogEvent(level, arg, {
			"step-type": this.type,
			"step-name": this.name
		});

		if (typeof arg === 'object') {
			if (arg instanceof Error) {
				// The 'arg' is an Error Object
				const stack = arg.stack;

				if (stack.getLineNumber) {
					logevent.line = stack.getLineNumber();
				}
				if (stack.getFileName) {
					logevent._file_name = stack.getFileName();
				}
				logevent._error_name = arg.name;
				logevent.short_message = arg.message;
			} else {
				// The 'arg' is a normal object. Copy all the properties

				if (arg.short_message !== undefined) {
					logevent.short_message = arg.short_message;
					delete(arg.short_message);
				}
				if (arg.full_message !== undefined) {
					logevent.full_message = arg.full_message;
					delete(arg.full_message);
				}
			}
		}
		if (this.endpoints.log && this.endpoints.log.isConnected) {
			this.endpoints.log.receive(logevent);
		} else {
			console.log(logevent);
		}
	}
};

// define the logger methods
llm.defineLoggerMethods(BaseStep, llm.defaultLogLevels);

stm.defineActionMethods(BaseStep, actions, true);

module.exports.BaseStep = BaseStep;
<commit_msg>refactor: use const where possible<commit_after>/* jslint node: true, esnext: true */

"use strict";

const endpoint = require('kronos-endpoint'),
	llm = require('loglevel-mixin'),
	stm = require('statetransition-mixin'),
	merge = require('merge-deep');

const actions = stm.prepareActions({
	start: {
		stopped: {
			target: "running",
			during: "starting",
			timeout: 5000
		}
	},
	stop: {
		running: {
			target: "stopped",
			during: "stopping",
			timeout: 5000
		},
		starting: {
			target: "stopped",
			during: "stopping",
			timeout: 5000
		}
	},
	remove: {
		stopped: {
			target: "removed"
		}
	}
});

// Steps plain attributes without special handling
// may be extended by some properties like writable,...
const ATTRIBUTES = ['description'];

const BaseStep = {
	"name": "kronos-step",
	"description": "This step is the base class for step implementations",

	/**
	 * Creates the properties for a given object
	 * @param {Object} manager The kronos service manager
	 * @param {String} (mandatory) name The Name of the step
	 * @param {Object} (optional) stepDefinition The step definition
	 *
	 */
	prepareProperties(manager, name, stepConfiguration) {
		let endpoints = {};

		// TODO ther is no better way ?
		const type = stepConfiguration.type ? stepConfiguration.type : this.name;

		const props = {
			name: {
				value: name
			},
			type: {
				value: type
			},
			endpoints: {
				value: endpoints
			},
			manager: {
				value: manager
			},
			/**
			 * @return {Boolean} if step is in a running state
			 */
			isRunning: {
				get: function () {
					return this.state === 'running' || this.state === 'starting';
				}
			}
		};

		ATTRIBUTES.forEach(a => {
			if (stepConfiguration[a] !== undefined) {
				props[a] = {
					value: stepConfiguration[a]
				};
			}
		});

		return props;
	},

	/**
	 * Called to initialize step
	 * Please note 'this' is not jet present
	 */
	initialize(manager, name, stepConfiguration, props) {},

	/**
	 * Called when step instance properties are present.
	 *
	 */
	finalize(manager, stepConfiguration) {},

	/**
	 * This method could be overwritten by a derived object to setup default endpoints.
	 * This method may be overwritten by derived classes.
	 * This will be called by the initialize method
	 * @param {Object} stepConfiguration The default step configuration
	 * @api protected
	 */
	createPredefinedEndpoints(stepConfiguration) {
		if (this.endpoints.log) return;

		const logEndpoint = new endpoint.SendEndpointDefault("log", this);
		if (this.manager.services && this.manager.services.logger) {
			logEndpoint.connected = this.manager.services.logger.endpoints.log;
		}
		this.addEndpoint(logEndpoint);
	},

	/**
	 * This function mixes the endpoint definitions of a step.
	 * So that extensions comming from the flow will be mixed into
	 * the definition from the step (The definition from the prototype).
	 * Only the 'endpoints' part will be extended
	 *
	 * @param def The step definition from the flow or step itslef.
	 * @return definition The new extended step definition
	 */
	inheritEndpoints(def) {
		const prototype = Object.getPrototypeOf(this);
		if (prototype && prototype.endpoints) {
			if (def && def.endpoints) {
				// before we can merge endpoints of type string needs to be converted
				for (const endpointName in def.endpoints) {
					const endpointDefinition = def.endpoints[endpointName];
					if (typeof endpointDefinition === 'string') {
						def.endpoints[endpointName] = {
							"target": endpointDefinition
						};
					}
				}

				// need to mix the definition
				def.endpoints = merge({}, prototype.endpoints, def.endpoints);
			} else {
				if (!def) {
					def = {};
				}
				// just take the prototype definition
				def.endpoints = prototype.endpoints;
			}
		}
		return def;
	},

	/**
	 * Creates the endpoint objects defined as a combination from
	 * implementation and definition
	 * @param {Object} def The step configuration
	 * @api protected
	 */
	createEndpoints(def) {
		if (def && def.endpoints) {
			Object.keys(def.endpoints).forEach(name =>
				this.createEndpoint(name, def.endpoints[name]));
		}
	},

	createEndpoint(name, def) {
		let ep;
		if (def.in) ep = new endpoint.ReceiveEndpoint(name, this);
		if (def.out) ep = new endpoint.SendEndpoint(name, this);

		this.addEndpoint(ep);

		if (def.interceptors) {
			ep.interceptors = def.interceptors.map(icDef => this.manager.createInterceptorInstanceFromConfig(icDef, ep));
		}
	},

	/**
	 * @param {Endpoint} ep
	 */
	addEndpoint(ep) {
		this.endpoints[ep.name] = ep;
	},

	/**
	 * removes a endpoint
	 * @param {string} name
	 */
	removeEndpoint(name) {
		delete this.endpoints[name];
	},

	/**
	 * Sends a 'stepStateChanged' event to the manager.
	 * arguments are:
	 *  step,oldState,newState
	 * @param {String} oldState
	 * @param {String} newState
	 */
	stateChanged(oldState, newState) {
		this.trace(level => ({
			"message": "state changed",
			"oldState": oldState,
			"newState": newState
		}));
		this.manager.emit('stepStateChanged', this, oldState, newState);
	},

	/**
	 * Returns the string representation of this step
	 * @return {String} human readable name
	 */
	toString() {
		return this.name;
	},

	/**
	 * Deliver json representation
	 * @param {Objects} options
	 *  with the following flags:
	 *    includeRuntimeInfo - include runtime informtion like state
	 *    includeName - name of the step
	 *    includeDefaults - include all properties also the inherited and not overwittern ones
	 * @return json representation
	 */
	toJSONWithOptions(options) {
		const json = {
			type: this.type,
			endpoints: {}
		};

		if (options.includeName) {
			json.name = this.name;
		}

		if (options.includeRuntimeInfo) {
			json.state = this.state;
		}

		for (const endpointName in this.endpoints) {
			const ep = this.endpoints[endpointName];
			if (ep.isDefault) {
				if (options.includeDefaults) {
					json.endpoints[endpointName] = ep.toJSON();
				}
			} else {
				json.endpoints[endpointName] = ep.toJSON();
			}
		}

		ATTRIBUTES.forEach(a => {
			json[a] = this[a];
		});

		return json;
	},

	toJSON() {
		return this.toJSONWithOptions({
			includeRuntimeInfo: false,
			includeDefaults: false,
			includeName: false
		});
	},

	/**
	 * @return {Step} newly created step
	 */
	createInstance(stepDefinition, manager) {
		if (!manager) {
			throw new Error(`No Manager given in 'createInstance' for step '${stepDefinition.name}'`);
		}

		const props = this.prepareProperties(manager, stepDefinition.name, stepDefinition);
		this.initialize(manager, stepDefinition.name, stepDefinition, props);
		const newInstance = Object.create(this, props);

		stm.defineStateTransitionProperties(newInstance, actions, "stopped");

		// get the default loglevel for this step
		let logLevel = llm.defaultLogLevels[stepDefinition.logLevel] || llm.defaultLogLevels.info;

		// create the properties to store the loglevel
		llm.defineLogLevelProperties(newInstance, llm.defaultLogLevels, logLevel);

		// mix the endpoints from the prototype with the new definition
		stepDefinition = newInstance.inheritEndpoints(stepDefinition);

		newInstance.createEndpoints(stepDefinition);
		newInstance.createPredefinedEndpoints(stepDefinition);
		newInstance.finalize(stepDefinition);

		return newInstance;
	},

	log(level, arg) {
		const logevent = llm.makeLogEvent(level, arg, {
			"step-type": this.type,
			"step-name": this.name
		});

		if (typeof arg === 'object') {
			if (arg instanceof Error) {
				// The 'arg' is an Error Object
				const stack = arg.stack;

				if (stack.getLineNumber) {
					logevent.line = stack.getLineNumber();
				}
				if (stack.getFileName) {
					logevent._file_name = stack.getFileName();
				}
				logevent._error_name = arg.name;
				logevent.short_message = arg.message;
			} else {
				// The 'arg' is a normal object. Copy all the properties

				if (arg.short_message !== undefined) {
					logevent.short_message = arg.short_message;
					delete(arg.short_message);
				}
				if (arg.full_message !== undefined) {
					logevent.full_message = arg.full_message;
					delete(arg.full_message);
				}
			}
		}
		if (this.endpoints.log && this.endpoints.log.isConnected) {
			this.endpoints.log.receive(logevent);
		} else {
			console.log(logevent);
		}
	}
};

// define the logger methods
llm.defineLoggerMethods(BaseStep, llm.defaultLogLevels);

stm.defineActionMethods(BaseStep, actions, true);

module.exports.BaseStep = BaseStep;
<|endoftext|>


<commit_before>/* jslint node: true, esnext: true */

"use strict";

const states = require('./states');
const endpoint = require('./endpoint');
const loggingSupport = require('./loggingSupport');
const myScopeReporter = require('scope-reporter');
const scopeDefinitions = require('../lib/scopeDefinitions');

/**
 * Creates the properties for a given object
 * @param {Object} (mandatory) obj The object which where the properties should be added
 * @param {Object} manager The kronos service manager
 * @param {String} (mandatory) name The Name of the step
 * @param {Object} (optional) stepDefinition The step definition
 *
 */
function propertyConfigurer(obj, manager, name, stepDefinition) {
	let state = "stopped";

	const props = {
		name: {
			"value": name
		},
		type: {
			value: stepDefinition.type
		},
		manager: {
			value: manager
		},
		state: {
			get: function () {
				return state;
			},
			set: function (newState) {
				if (state != newState) {
					const oldState = state;
					state = newState;
					this._stateChanged(oldState, newState);
				}
			}
		},
		/**
		 * @return {Boolean} if step is in a running state
		 */
		isRunning: {
			get: function () {
				return state === 'running' || state === 'starting';
			}
		},
	};

	if (stepDefinition.description) {
		props.description = {
			value: stepDefinition.description
		};
	}

	Object.defineProperties(obj, props);
}

class Step {

	/**
	 * Creates a step
	 * @param {Object} manager The kronos service manager
	 * @param {Object} scopeReporter This reporter is used to report parsing errors
	 * @param {String} name The name for this step
	 * @param {Object} stepDefinition The json step definition
	 */
	constructor(manager, scopeReporter, name, stepDefinition) {
		if (!scopeReporter) {
			scopeReporter = myScopeReporter.createReporter(scopeDefinitions, function (reporter) {
				// do nothing
			});
		}

		scopeReporter.enterScope('step', name);

		if (!manager) {
			throw new Error("No 'kronos' service manager given");
		}
		if (!scopeReporter) {
			throw new Error("No 'scopeReporter' given");
		}
		if (!name) {
			throw new Error("No 'name' given");
		}
		if (!stepDefinition) {
			throw new Error("No 'stepDefinition' given");
		}

		// Stores all endpoints of this step by its name
		this.endpoints = {};

		/**
		 * Adding a start function
		 * Brings a step into the running state.
		 * @return {Promise} that fullfills with the step in the running state
		 */
		this.start = states.transitionFunction(states.transitions.start);

		/**
		 * Adding a stop function
		 * Brings the step into the stopped state
		 * @return {Promise} that fullfills with the step in the stopped state
		 */
		this.stop = states.transitionFunction(states.transitions.stop);

		propertyConfigurer(this, manager, name, stepDefinition);

		// Now initialize this step
		this._initialize(scopeReporter, stepDefinition);

		this._stateChanged(undefined, this.state);

		scopeReporter.leaveScope('step');
	}

	/**
	 * Initializes a step.
	 * This method may be overwritten by derived classes.
	 * @param {Object} scopeReporter This reporter is used to report parsing errors
	 * @param {Object} stepDefinition The json step definition
	 * @api protected
	 */
	_initialize(scopeReporter, stepDefinition) {
		this._beforeInitialize(scopeReporter, stepDefinition);
		this._setupEndpointsFromConfig(scopeReporter, stepDefinition);
		this._setupPredefinedEndpoints(scopeReporter, stepDefinition);
		this._setupEndpoints(scopeReporter, stepDefinition);
		this._afterInitialize(scopeReporter, stepDefinition);
	}

	/**
	 * Could be overwritten by a derived object. This method is the first method called from
	 * the initialize function.
	 * @param {Object} scopeReporter This reporter is used to report parsing errors
	 * @param {Object} stepDefinition The json step definition
	 * @api protected
	 */
	_beforeInitialize(scopeReporter, stepDefinition) {}

	/**
	 * Could be overwritten by a derived object. This method is the last method called from
	 * the initialize function.
	 * @param {Object} scopeReporter This reporter is used to report parsing errors
	 * @param {Object} stepDefinition The json step definition
	 * @api protected
	 */
	_afterInitialize(scopeReporter, stepDefinition) {}

	/**
	 * This method could be overwritten by a derived object to setup default endpoints.
	 * The derived method should call super first.
	 * This method may be overwritten by derived classes.
	 * This will be called by the initialize method
	 * @param {Object} scopeReporter This reporter is used to report parsing errors
	 * @param {Object} stepDefinition The json step definition
	 * @api protected
	 */
	_setupPredefinedEndpoints(scopeReporter, stepDefinition) {
		const logEndpoint = endpoint.createEndpoint("log", {
			"description": "logging endpoint. All logging goes through this endpoint",
			"out": true,
			"active": true,
			"default": true
		});
		this.putEndpoint(logEndpoint);

		const loggerFunction = function (args) {
			if (logEndpoint.isConnected()) {
				logEndpoint.send(args);
			} else {
				console.log(args);
			}
		};

		loggingSupport.assignLoggerFunctions(this, loggerFunction);
	}

	/**
	 * Derived objects should implement this method to setup the endpoints
	 * This will be called by the initialize method
	 * @param {Object} scopeReporter This reporter is used to report parsing errors
	 * @param {Object} stepDefinition The json step definition
	 * @api protected
	 */
	_setupEndpoints(scopeReporter, stepDefinition) {}

	/**
	 * This method creates the endpoint objects dfefined in the stepDefinition
	 * @param {Object} scopeReporter This reporter is used to report parsing errors
	 * @param {Object} stepDefinition The json step definition
	 * @api protected
	 */
	_setupEndpointsFromConfig(scopeReporter, stepDefinition) {
		if (stepDefinition.endpoints) {
			for (const endpoinName in stepDefinition.endpoints) {
				const ep = endpoint.createEndpoint(endpoinName, stepDefinition.endpoints[endpoinName]);
				this.putEndpoint(ep);
			}
		}
	}

	/**
	 * Sends a 'stepStateChanged' event to the manager.
	 * arguments are:
	 *  step,oldState,newState
	 * @param {String} oldState
	 * @param {String} newState
	 */
	_stateChanged(oldState, newState) {
		this.manager.emit('stepStateChanged', this, oldState, newState);
	}

	/**
	 * Called when state transition is not allowed
	 * @param {String} action
	 * @return {Promise} rejecting with an Error
	 */
	_rejectWrongState(action) {
		return Promise.reject(new Error(`Can't ${action} ${this.name} in ${this.state} state`));
	}

	/**
	 * To be overwritten
	 * @return {Promise} always fullfills with the receiving step
	 */
	_start() {
		return Promise.resolve(this);
	}

	/**
	 * To be overwritten
	 * @return {Promise} always fullfills with the receiving step
	 */
	_stop() {
		return Promise.resolve(this);
	}

	/**
	 * Stores the given endpoint under its name. If there was an exsting endpoint before
	 * with the same name it will be overwritten.
	 * @param endpoint The endpoint to set
	 */
	putEndpoint(endpoint) {
		this.endpoints[endpoint.name] = endpoint;
		endpoint.step = this;
	}

	/**
	 * Returns the string representation of this step
	 */
	toString() {
		return this.name;
	}

	toJSON() {
		const json = {
			type: this.type,
			endpoints: {},
			state: this.state
		};

		for (const endpointName in this.endpoints) {
			if (!this.endpoints[endpointName].default) {
				json.endpoints[endpointName] = this.endpoints[endpointName].toJSON();
			}
		}
		if (this.description) {
			json.description = this.description;
		}

		return json;
	}

}

Step.configuration = {
	"name": "kronos-step",
	"type": "kronos-step",
	"description": "This step is the base class for step implementations",
};

module.exports = Step;
<commit_msg>working changes<commit_after>/* jslint node: true, esnext: true */

"use strict";

const states = require('./states');
const endpoint = require('./endpoint');
const loggingSupport = require('./loggingSupport');
const myScopeReporter = require('scope-reporter');
const scopeDefinitions = require('../lib/scopeDefinitions');

/**
 * Creates the properties for a given object
 * @param {Object} (mandatory) obj The object which where the properties should be added
 * @param {Object} manager The kronos service manager
 * @param {String} (mandatory) name The Name of the step
 * @param {Object} (optional) stepDefinition The step definition
 *
 */
function propertyConfigurer(obj, manager, name, stepDefinition) {
	let state = "stopped";

	const props = {
		name: {
			"value": name
		},
		type: {
			value: stepDefinition.type
		},
		manager: {
			value: manager
		},
		state: {
			get: function () {
				return state;
			},
			set: function (newState) {
				if (state != newState) {
					const oldState = state;
					state = newState;
					this._stateChanged(oldState, newState);
				}
			}
		},
		/**
		 * @return {Boolean} if step is in a running state
		 */
		isRunning: {
			get: function () {
				return state === 'running' || state === 'starting';
			}
		},
	};

	if (stepDefinition.description) {
		props.description = {
			value: stepDefinition.description
		};
	}

	Object.defineProperties(obj, props);
}

class Step {

	/**
	 * Creates a step
	 * @param {Object} manager The kronos service manager
	 * @param {Object} scopeReporter This reporter is used to report parsing errors
	 * @param {String} name The name for this step
	 * @param {Object} stepDefinition The json step definition
	 */
	constructor(manager, scopeReporter, name, stepDefinition) {
		if (!scopeReporter) {
			scopeReporter = myScopeReporter.createReporter(scopeDefinitions, function (reporter) {
				// do nothing
			});
		}

		scopeReporter.enterScope('step', name);

		if (!manager) {
			throw new Error("No 'kronos' service manager given");
		}
		if (!scopeReporter) {
			throw new Error("No 'scopeReporter' given");
		}
		if (!name) {
			throw new Error("No 'name' given");
		}
		if (!stepDefinition) {
			throw new Error("No 'stepDefinition' given");
		}

		// Stores all endpoints of this step by its name
		this.endpoints = {};

		/**
		 * Adding a start function
		 * Brings a step into the running state.
		 * @return {Promise} that fullfills with the step in the running state
		 */
		this.start = states.transitionFunction(states.transitions.start);

		/**
		 * Adding a stop function
		 * Brings the step into the stopped state
		 * @return {Promise} that fullfills with the step in the stopped state
		 */
		this.stop = states.transitionFunction(states.transitions.stop);

		propertyConfigurer(this, manager, name, stepDefinition);

		// Now initialize this step
		this._initialize(scopeReporter, Step.configuration, stepDefinition);

		this._stateChanged(undefined, this.state);

		scopeReporter.leaveScope('step');
	}

	/**
	 * Initializes a step.
	 * This method may be overwritten by derived classes.
	 * @param {Object} scopeReporter This reporter is used to report parsing errors
	 * @param {Object} stepConfiguration The default step configuration
	 * @param {Object} stepDefinition The json step definition
	 * @api protected
	 */
	_initialize(scopeReporter, stepConfiguration, stepDefinition) {
		this._beforeInitialize(scopeReporter, stepConfiguration, stepDefinition);
		let endpoints = this._setupEndpointsFromConfig(scopeReporter, stepConfiguration, stepDefinition);
		endpoints = this._setupPredefinedEndpoints(endpoints, scopeReporter, stepConfiguration, stepDefinition);
		endpoints = this._setupEndpoints(endpoints, scopeReporter, stepConfiguration, stepDefinition);
		this._afterInitialize(endpoints, scopeReporter, stepConfiguration, stepDefinition);
	}

	/**
	 * Could be overwritten by a derived object. This method is the first method called from
	 * the initialize function.
	 * @param {Object} scopeReporter This reporter is used to report parsing errors
	 * @param {Object} stepConfiguration The default step configuration
	 * @param {Object} stepDefinition The json step definition
	 * @api protected
	 */
	_beforeInitialize(scopeReporter, stepConfiguration, stepDefinition) {}

	/**
	 * Could be overwritten by a derived object. This method is the last method called from
	 * the initialize function.
	 * @param {Object} scopeReporter This reporter is used to report parsing errors
	 * @param {Object} stepConfiguration The default step configuration
	 * @param {Object} stepDefinition The json step definition
	 * @api protected
	 */
	_afterInitialize(scopeReporter, stepConfiguration, stepDefinition) {}

	/**
	 * This method could be overwritten by a derived object to setup default endpoints.
	 * The derived method should call super first.
	 * This method may be overwritten by derived classes.
	 * This will be called by the initialize method
	 * @param {Object} scopeReporter This reporter is used to report parsing errors
	 * @param {Object} stepConfiguration The default step configuration
	 * @param {Object} stepDefinition The json step definition
	 * @return {Object} endpoints A hash containing the endpoints stored by its name
	 * @api protected
	 */
	_setupPredefinedEndpoints(scopeReporter, stepConfiguration, stepDefinition) {
		const logEndpoint = endpoint.createEndpoint("log", {
			"description": "logging endpoint. All logging goes through this endpoint",
			"out": true,
			"active": true,
			"default": true
		});
		this.putEndpoint(logEndpoint);

		const loggerFunction = function (args) {
			if (logEndpoint.isConnected()) {
				logEndpoint.send(args);
			} else {
				console.log(args);
			}
		};

		loggingSupport.assignLoggerFunctions(this, loggerFunction);
	}

	/**
	 * Derived objects should implement this method to setup the endpoints
	 * This will be called by the initialize method
	 * @param {Object} scopeReporter This reporter is used to report parsing errors
	 * @param {Object} stepConfiguration The default step configuration
	 * @param {Object} stepDefinition The json step definition
	 * @return {Object} endpoints A hash containing the endpoints stored by its name
	 * @api protected
	 */
	_setupEndpoints(scopeReporter, stepConfiguration, stepDefinition) {}

	/**
	 * This method creates the endpoint objects dfefined in the stepDefinition
	 * @param {Object} scopeReporter This reporter is used to report parsing errors
	 * @param {Object} stepConfiguration The default step configuration
	 * @param {Object} stepDefinition The json step definition
	 * @return {Object} endpoints A hash containing the endpoints stored by its name
	 * @api protected
	 */
	_setupEndpointsFromConfig(scopeReporter, stepConfiguration, stepDefinition) {
		if (stepDefinition.endpoints) {
			for (const endpoinName in stepDefinition.endpoints) {
				const ep = endpoint.createEndpoint(endpoinName, stepDefinition.endpoints[endpoinName]);
				this.putEndpoint(ep);
			}
		}
	}

	/**
	 * Sends a 'stepStateChanged' event to the manager.
	 * arguments are:
	 *  step,oldState,newState
	 * @param {String} oldState
	 * @param {String} newState
	 */
	_stateChanged(oldState, newState) {
		this.manager.emit('stepStateChanged', this, oldState, newState);
	}

	/**
	 * Called when state transition is not allowed
	 * @param {String} action
	 * @return {Promise} rejecting with an Error
	 */
	_rejectWrongState(action) {
		return Promise.reject(new Error(`Can't ${action} ${this.name} in ${this.state} state`));
	}

	/**
	 * To be overwritten
	 * @return {Promise} always fullfills with the receiving step
	 */
	_start() {
		return Promise.resolve(this);
	}

	/**
	 * To be overwritten
	 * @return {Promise} always fullfills with the receiving step
	 */
	_stop() {
		return Promise.resolve(this);
	}

	/**
	 * Stores the given endpoint under its name. If there was an exsting endpoint before
	 * with the same name it will be overwritten.
	 * @param endpoint The endpoint to set
	 */
	putEndpoint(endpoint) {
		this.endpoints[endpoint.name] = endpoint;
		endpoint.step = this;
	}

	/**
	 * Returns the string representation of this step
	 */
	toString() {
		return this.name;
	}

	toJSON() {
		const json = {
			type: this.type,
			endpoints: {},
			state: this.state
		};

		for (const endpointName in this.endpoints) {
			if (!this.endpoints[endpointName].default) {
				json.endpoints[endpointName] = this.endpoints[endpointName].toJSON();
			}
		}
		if (this.description) {
			json.description = this.description;
		}

		return json;
	}

}

Step.configuration = {
	"name": "kronos-step",
	"type": "kronos-step",
	"description": "This step is the base class for step implementations",
};

module.exports = Step;
<|endoftext|>


<commit_before>Template.product.events({
    'click .insert.item.button': function (event, template) {
        event.preventDefault();

        var productId = this._id,
            qty = template.find('.qty.input').value,
            cartItems = Items.find({}, {sort: {created: -1}}),
            cartItemId = '',
            cartItemQty = '',
            qtyCount = 0,
            itemExists = false;

        if (qty === '') {
            qty = '1';
        }

        // TODO: validate if item exists

        cartItems.forEach(function (cartItem) {
            if (cartItem.product === productId) {
                cartItemId = cartItem._id;
                cartItemQty = cartItem.qty;
                qtyCount += +cartItemQty;
                itemExists = true;
                return false;
            } else {
                cartItemId = cartItem._id;
                cartItemQty = cartItem.qty;
                qtyCount += +cartItemQty;
                itemExists = false;
            }
        });

        if (itemExists) {
            var qtyNew = template.find('.qty.input').value;

            if (qtyNew === '') {
                qtyNew = '1';
            }

            var qtySum = Number(cartItemQty) + Number(qtyNew);

            Meteor.call('updateItem', cartItemId, qtySum.toString(), function (error) {
                if (error) {
                    console.error('Update item method failed: ' + error.reason);
                } else {
                    // TODO need to get qty of all items
                    Tracker.autorun(function() {
                        Session.set('itemsCount', qtyCount);
                    });
                }
            });
        } else {
            Meteor.call('insertItem', qty, productId, function (error) {
                if (error) {
                    console.error('Insert item method failed: ' + error.reason);
                } else {
                    // TODO need to get qty of all items
                    Tracker.autorun(function() {
                        Session.set('itemsCount', qtyCount);
                    });
                }
            });
        }
    }
});<commit_msg>more updates to add to cart event<commit_after>Template.product.events({
    'click .insert.item.button': function (event, template) {
        event.preventDefault();

        var productId = this._id,
            qty = template.find('.qty.input').value,
            cartItems = Items.find({}, {sort: {created: -1}}),
            cartItemId = '',
            cartItemQty = '',
            qtyCount = 0,
            itemExists = false;

        if (qty === '') {
            qty = '1';
        }

        cartItems.forEach(function (cartItem) {
            if (cartItem.product === productId) {
                cartItemId = cartItem._id;
                cartItemQty = cartItem.qty;
                qtyCount += +cartItemQty;
                itemExists = true;
                return false;
            } else {
                cartItemId = cartItem._id;
                cartItemQty = cartItem.qty;
                qtyCount += +cartItemQty;
                itemExists = false;
            }
        });

        Tracker.autorun(function () {
            if (itemExists) {
                var qtyNew = template.find('.qty.input').value;

                if (qtyNew === '') {
                    qtyNew = '1';
                }

                var qtySum = Number(cartItemQty) + Number(qtyNew);

                Meteor.call('updateItem', cartItemId, qtySum.toString(), function (error) {
                    if (error) {
                        console.error('Update item method failed: ' + error.reason);
                    } else {
                        Session.set('itemsCount', qtyCount);
                    }
                });
            } else {
                Meteor.call('insertItem', qty, productId, function (error) {
                    if (error) {
                        console.error('Insert item method failed: ' + error.reason);
                    } else {
                        Session.set('itemsCount', qtyCount);
                    }
                });
            }
        });
    }
});<|endoftext|>


<commit_before>export const signIn = (email, password) => {
  return {
    type: 'SIGN_IN',
    email,
    password
  };
};

export const signUp = (firstName, lastName, email, password, phone) => {
  return {
    type: 'SIGN_UP',
    firstName,
    lastName,
    email,
    password,
    phone
  };
};

export const postMessage = (senderId, groupId, body, priority, isComment, token) => {
  return {
    type: 'POST_MESSAGE',
    body,
    priority,
    isComment,
    senderId,
    groupId,
    token
  };
};

export const addUser = (email, groupId, adderId, token) => {
  return {
    type: 'ADD_MEMBER',
    email,
    groupId,
    adderId,
    token
  };
};

export const deleteGroup = (ownerId, groupId, token) => {
  return {
    type: 'DELETE_A_GROUP',
    ownerId,
    groupId,
    token
  };
};

export const createGroup = (creatorId, title, description, initialMembers, token) => {
  return {
    type: 'CREATE_GROUP',
    creatorId,
    title,
    description,
    initialMembers,
    token
  };
};

export const getMessages = (groupId, token) => {
  return {
    type: 'GET_MESSAGES',
    groupId,
    token
  };
};

export const getGroupMembers = (groupId, token) => {
  return {
    type: 'GET_GROUP_MEMBERS',
    groupId,
    token
  };
};

export const getPostItMembers = (token) => {
  return {
    type: 'GET_POST_IT_MEMBERS',
    token
  };
};

export const getAllGroups = (offset, limit, token) => {
  return {
    type: 'GET_ALL_GROUPS',
    offset,
    limit,
    token
  };
};

export const getGroupsForUser = (userId, offset, limit, token) => {
  return {
    type: 'GET_ALL_GROUPS_FOR_A_USER',
    userId,
    offset,
    limit,
    token
  };
};

export const getAllGroupsForUser = (userId, token) => {
  return {
    type: 'GET_ALL_GROUPS_FOR_A_USER_AT_ONCE',
    userId,
    token
  };
};

export const deleteMember = (ownerId, idToDelete, groupId, token) => {
  return {
    type: 'DELETE_GROUP_MEMBER',
    ownerId,
    idToDelete,
    groupId,
    token
  };
};

export const loadMessages = (groupId) => {
  return {
    type: 'LOAD_MESSAGES',
    groupId
  };
};

export const resetErrorLog = () => {
  return {
    type: 'RESET_ERROR_LOG',
  };
};

export const resetRedirect = () => {
  return {
    type: 'RESET_REDIRECT_STATE'
  };
};

export const verifyToken = (token) => {
  return {
    type: 'VERIFY_TOKEN',
    token
  };
};
<commit_msg>add actions for leaving group<commit_after>export const signIn = (email, password) => {
  return {
    type: 'SIGN_IN',
    email,
    password
  };
};

export const signUp = (firstName, lastName, email, password, phone) => {
  return {
    type: 'SIGN_UP',
    firstName,
    lastName,
    email,
    password,
    phone
  };
};

export const postMessage = (senderId, groupId, body, priority, isComment, token) => {
  return {
    type: 'POST_MESSAGE',
    body,
    priority,
    isComment,
    senderId,
    groupId,
    token
  };
};

export const addUser = (email, groupId, adderId, token) => {
  return {
    type: 'ADD_MEMBER',
    email,
    groupId,
    adderId,
    token
  };
};

export const deleteGroup = (ownerId, groupId, token) => {
  return {
    type: 'DELETE_A_GROUP',
    ownerId,
    groupId,
    token
  };
};

export const createGroup = (creatorId, title, description, initialMembers, token) => {
  return {
    type: 'CREATE_GROUP',
    creatorId,
    title,
    description,
    initialMembers,
    token
  };
};

export const getMessages = (groupId, token) => {
  return {
    type: 'GET_MESSAGES',
    groupId,
    token
  };
};

export const getGroupMembers = (groupId, token) => {
  return {
    type: 'GET_GROUP_MEMBERS',
    groupId,
    token
  };
};

export const getPostItMembers = (token) => {
  return {
    type: 'GET_POST_IT_MEMBERS',
    token
  };
};

export const getAllGroups = (offset, limit, token) => {
  return {
    type: 'GET_ALL_GROUPS',
    offset,
    limit,
    token
  };
};

export const getGroupsForUser = (userId, offset, limit, token) => {
  return {
    type: 'GET_ALL_GROUPS_FOR_A_USER',
    userId,
    offset,
    limit,
    token
  };
};

export const getAllGroupsForUser = (userId, token) => {
  return {
    type: 'GET_ALL_GROUPS_FOR_A_USER_AT_ONCE',
    userId,
    token
  };
};

export const deleteMember = (ownerId, idToDelete, groupId, token) => {
  return {
    type: 'DELETE_GROUP_MEMBER',
    ownerId,
    idToDelete,
    groupId,
    token
  };
};

export const loadMessages = (groupId) => {
  return {
    type: 'LOAD_MESSAGES',
    groupId
  };
};

export const resetErrorLog = () => {
  return {
    type: 'RESET_ERROR_LOG',
  };
};

export const resetRedirect = () => {
  return {
    type: 'RESET_REDIRECT_STATE'
  };
};

export const resetLoadingState = () => {
  return {
    type: 'RESET_LOADING_STATE'
  };
};

export const signOut = () => {
  return {
    type: 'SIGN_OUT',
  };
};

export const verifyToken = (token) => {
  return {
    type: 'VERIFY_TOKEN',
    token
  };
};

export const leaveGroup = (token, groupId) => {
  return {
    type: 'LEAVE_GROUP',
    token,
    groupId
  };
};

<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//THIS IS A DATA CUBE, WITH ONLY INTEGER INDICES
//THIS IS NOT A STANDARD MATHEMATICAL MATRIX
Matrix=function(arg){
	if (arg.data){
		this.dim=[];
		var d=arg.data;
		while (d instanceof Array) {
			this.dim.append(d.length);
			d = d[0];
		}//while
		this.num=this.dim.length;
		if (this.num==0 && typeof(arg.data)=="object"){
			Log.error("Expecting an array");
		}//endif
		this.data=arg.data;
	}else if (arg instanceof Array){
		//EXPECTING COORDINATES ARRAY
		this.num=arg.length;
		this.dim=arg;
	}else{
		Log.error("not supported")
	}//endif
};//function

//PROVIDE func(v, i, c, cube) WHERE
// v - IS A VALUE IN THE CUBE
// i - IS THE INDEX INTO THE edge
// c - AN ARRAY OF COORDINATES v IS FOUND AT
// cube - THE WHOLE CUBE
// NOTE: c[edge]==i
function forall1(edge, func){
	var data = this.data;
	var num = this.num;
	var c = Uint32Array(this.num);

	function iter(v, d){
		if (d == num) {
			func(v, c[edge], c, data);
		} else {
			for (var j = 0; j < v.length; j++) {
				c[d] = j;
				iter(v[j], d + 1);
			}//for
		}//endif
	}//function
	iter(data, 0);
}

//PROVIDE func(v, c, cube) WHERE
// v - IS A VALUE IN THE CUBE
// c - AN ARRAY OF COORDINATES v IS FOUND AT
// cube - THE WHOLE CUBE
Matrix.prototype.forall = function(func, other){
	if (other!==undefined){
		Log.error("No longer supported, use the simpler version");
	}//endif

	var data = this.data;
	var num = this.num;
	var c = Uint32Array(this.num);

	function iter(v, d){
		if (d == num) {
			func(v, c, data);
		} else {
			for (var j = 0; j < v.length; j++) {
				c[d] = j;
				iter(v[j], d + 1);
			}//for
		}//endif
	}//function
	iter(data, 0);
}

//PROVIDE func(v, i, c, cube) WHERE
// v - IS A VALUE IN THE CUBE
// c - AN ARRAY OF COORDINATES v IS FOUND AT
// cube - THE WHOLE CUBE
// func MUST RETURN A NEW VALUE
Matrix.prototype.map = function (func) {
	var data=this.data;
	var num = this.num;
	var c = Uint32Array(this.num);

	function iter(v, d) {
		if (d == num) {
			return func(v, c, data);
		} else {
			var output=[];
			for (var j = 0; j < v.length; j++) {
				c[d]=j;
				output.append(iter(v[j], d+1));
			}//for
			return output;
		}//endif
	}//function
	return iter(data, 0);
};

//PROVIDE func(v, i, c, cube) WHERE
// v - IS A SUB-CUBE
// i - IS THE INDEX INTO THE edge
// c - AN ARRAY OF PARTIAL COORDINATES v IS FOUND AT
// cube - THE WHOLE CUBE
// func MUST RETURN A SUBCUBE, OR undefined
Matrix.prototype.filter = function (edge, func) {
	var data=this.data;
	var c = new Uint32Array(edge);

	function iter(v, d) {
		var output=[];
		if (d == edge) {
			for (var k = 0; k < v.length; k++) {
				var u = func(v[k], k, c, data);
				if (u!==undefined) output.append(u);
			}//for
		} else {
			for (var j = 0; j < v.length; j++) {
				c[d]=j;
				output.append(iter(v[j], d+1));
			}//for
		}//endif
		return output;
	}//function
	return iter(data, 0);
};

Matrix.prototype.get=function(coord){
	if (coord.length!=this.num){
		Log.error("expecting coordinates of "+this.num+" dimensions")
	}//endif
	var d=this.data;
	for(var i=0;i<coord.length;i++){
		d=d[coord[i]];
	}//for
	return d;
};

Matrix.prototype.set=function(coord, value){
	if (coord.length!=this.num){
		Log.error("expecting coordinates of "+this.num+" dimensions")
	}//endif
	var d=this.data;
	for(var i=0;i<coord.length-1;i++){
		d=d[coord[i]];
	}//for
	d[coord[this.num-1]]=value;
	return this;
};
<commit_msg>fix new arrays<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//THIS IS A DATA CUBE, WITH ONLY INTEGER INDICES
//THIS IS NOT A STANDARD MATHEMATICAL MATRIX
Matrix=function(arg){
	if (arg.data){
		this.dim=[];
		var d=arg.data;
		while (d instanceof Array) {
			this.dim.append(d.length);
			d = d[0];
		}//while
		this.num=this.dim.length;
		if (this.num==0 && typeof(arg.data)=="object"){
			Log.error("Expecting an array");
		}//endif
		this.data=arg.data;
	}else if (arg instanceof Array){
		//EXPECTING COORDINATES ARRAY
		this.num=arg.length;
		this.dim=arg;
	}else{
		Log.error("not supported")
	}//endif
};//function

//PROVIDE func(v, i, c, cube) WHERE
// v - IS A VALUE IN THE CUBE
// i - IS THE INDEX INTO THE edge
// c - AN ARRAY OF COORDINATES v IS FOUND AT
// cube - THE WHOLE CUBE
// NOTE: c[edge]==i
function forall1(edge, func){
	var data = this.data;
	var num = this.num;
	var c = new Uint32Array(this.num);

	function iter(v, d){
		if (d == num) {
			func(v, c[edge], c, data);
		} else {
			for (var j = 0; j < v.length; j++) {
				c[d] = j;
				iter(v[j], d + 1);
			}//for
		}//endif
	}//function
	iter(data, 0);
}

//PROVIDE func(v, c, cube) WHERE
// v - IS A VALUE IN THE CUBE
// c - AN ARRAY OF COORDINATES v IS FOUND AT
// cube - THE WHOLE CUBE
Matrix.prototype.forall = function(func, other){
	if (other!==undefined){
		Log.error("No longer supported, use the simpler version");
	}//endif

	var data = this.data;
	var num = this.num;
	var c = new Uint32Array(this.num);

	function iter(v, d){
		if (d == num) {
			func(v, c, data);
		} else {
			for (var j = 0; j < v.length; j++) {
				c[d] = j;
				iter(v[j], d + 1);
			}//for
		}//endif
	}//function
	iter(data, 0);
}

//PROVIDE func(v, i, c, cube) WHERE
// v - IS A VALUE IN THE CUBE
// c - AN ARRAY OF COORDINATES v IS FOUND AT
// cube - THE WHOLE CUBE
// func MUST RETURN A NEW VALUE
Matrix.prototype.map = function (func) {
	var data=this.data;
	var num = this.num;
	var c = new Uint32Array(this.num);

	function iter(v, d) {
		if (d == num) {
			return func(v, c, data);
		} else {
			var output=[];
			for (var j = 0; j < v.length; j++) {
				c[d]=j;
				output.append(iter(v[j], d+1));
			}//for
			return output;
		}//endif
	}//function
	return iter(data, 0);
};

//PROVIDE func(v, i, c, cube) WHERE
// v - IS A SUB-CUBE
// i - IS THE INDEX INTO THE edge
// c - AN ARRAY OF PARTIAL COORDINATES v IS FOUND AT
// cube - THE WHOLE CUBE
// func MUST RETURN A SUBCUBE, OR undefined
Matrix.prototype.filter = function (edge, func) {
	var data=this.data;
	var c = new Uint32Array(edge);

	function iter(v, d) {
		var output=[];
		if (d == edge) {
			for (var k = 0; k < v.length; k++) {
				var u = func(v[k], k, c, data);
				if (u!==undefined) output.append(u);
			}//for
		} else {
			for (var j = 0; j < v.length; j++) {
				c[d]=j;
				output.append(iter(v[j], d+1));
			}//for
		}//endif
		return output;
	}//function
	return iter(data, 0);
};

Matrix.prototype.get=function(coord){
	if (coord.length!=this.num){
		Log.error("expecting coordinates of "+this.num+" dimensions")
	}//endif
	var d=this.data;
	for(var i=0;i<coord.length;i++){
		d=d[coord[i]];
	}//for
	return d;
};

Matrix.prototype.set=function(coord, value){
	if (coord.length!=this.num){
		Log.error("expecting coordinates of "+this.num+" dimensions")
	}//endif
	var d=this.data;
	for(var i=0;i<coord.length-1;i++){
		d=d[coord[i]];
	}//for
	d[coord[this.num-1]]=value;
	return this;
};
<|endoftext|>


<commit_before>'use babel';

import {Point, Range} from 'atom';

export default class EventHandler {
  constructor(socket, editor, doc, subscriptions) {
    this.socket = socket;
    this.editor = editor;
    this.doc = doc;
    this.subscriptions = subscriptions;
    this.push = true;
    this.buffer = editor.getBuffer();
  }

  listen() {
    let disposables = [];

    // local events
    disposables.push(this.editor.onDidSave( (event) => {
      if (!this.push) {
        return;
      }
      let saveData = {
        a: 'meta',
        type: 'save',
        path: atom.project.relativizePath(event.path)[1]
      };
      setTimeout( () => {
        this.sendSocketMsg( () => {
          this.socket.send(JSON.stringify(saveData));
        });
      }, 0);
    }));

    disposables.push(this.editor.onDidChangeSelectionRange( (event) => {

      let data = {
        a: 'meta'
      };

      // cursor moved event
      if (event.newBufferRange.start.isEqual(event.newBufferRange.end)) {
        data.type = 'cursorMoved';
      } else { // selection event
        data.type = 'select';
      }
      data.newRange = event.newBufferRange;

      // let selectionData = {
      //   a: 'meta',
      //   type: 'select',
      //   selectRange: event.newBufferRange
      // };
      setTimeout( () => {
        this.sendSocketMsg( () => {
          this.socket.send(JSON.stringify(data));
        });
      }, 0);
    }));

    disposables.push(this.buffer.onDidChange((event) => {
      if (!this.push) {
        return;
      }
      // remove text event
      if (event['oldText'].length !== 0) {
        let startPoint = event['oldRange'].start;
        let position = this.buffer.characterIndexForPosition(startPoint);
        let op = [position, {d: event['oldText'].length}];
        this.doc.submitOp(op);
      }
      // insert text event
      if (event['newText'].length !== 0) {
        let startPoint = event['newRange'].start;
        let position = this.buffer.characterIndexForPosition(startPoint);
        let op = [position, event['newText']];
        this.doc.submitOp(op);
      }
    }));
    this.subscriptions.add(...disposables);

    // remote events
    this.listenToServerChanges();
  }

  listenToServerChanges() {
    this.socket.on('message', (msg) => {
      let data = JSON.parse(msg);
      console.log(data);
      if (data.a !== 'meta') {
        return;
      }
      switch (data.type) {
        case 'cursorMoved':
          this.onCursorMoved(data.newRange);
          break;
        case 'select':
          // this.onSelect(data.newRange);
          break;
        case 'save':
          this.onSave(data.path);
          break;
        case 'close':
          this.onClose();
          break;
        case 'socketClose':
          this.onSocketClose(data.clientId);
          break;
        default:
      }
    });

    this.doc.on('op', (op, source) => {
      if (source) {
        console.log('Got your own changes, I am not doing anything...');
        return;
      }
      console.log('changing...');
      console.log(op);

      var pos = 0;
      for (var i = 0; i < op.length; i++) {
        var component = op[i];
        switch (typeof component) {
          case 'number':
            pos += component;
            break;
          case 'string':
            this.onInsert(pos, component);
            pos += component.length;
            break;
          case 'object':
            this.onRemove(pos, component.d);
            break;
          default:
            break;
        }
      }
    });
  }

  onCursorMoved(rangeObj) {
    // type = overlay
    if (typeof this.overlayMarker !== 'undefined') {
      this.overlayMarker.destroy();
    }

    this.overlayMarker = this.editor.markBufferRange(Range.fromObject(rangeObj), {
      'invalidate': 'never'
    });
    let element = document.createElement('div');
    element.textContent = 'Overlay decorations!';
    let style = {
      transform: 'translate(0, -100%)',
      backgroundColor: 'rgba(31, 161, 93, 1)',
      padding: '4px',
      fontFamily: 'comic sans, comic sans ms, cursive, verdana, arial, sans-serif'
    }

    Object.assign(element.style, style);
    this.editor.decorateMarker(this.overlayMarker, {
      type: 'overlay',
      position: 'head',
      item: element
    });
  }

  onSelect(rangeObj) {
    if (typeof this.marker !== 'undefined') {
      this.marker.destroy();
    }
    if (Point.fromObject(rangeObj.start).isEqual(Point.fromObject(rangeObj.end))) {
      return;
    }
    this.marker = this.editor.markBufferRange(Range.fromObject(rangeObj), {
      invalidate: 'never'
    });
    this.editor.decorateMarker(this.marker, {
      type: 'highlight',
      class: 'coeditor-selection'
    });
  }

  onSave(relativePath) {
    // atom.workspace.getActivePane().saveItem(atom.workspace.getActivePaneItem());
    this.push = false;
    console.log(atom.project.getPaths());
    if (typeof this.editor.getPath() !== 'undefined') {
      this.editor.save();
    }
    this.push = true;
    // if (typeof this.editor.getPath() !== 'undefined') {
    //   this.editor.save();
    // } else {
    //   let absolutePath = path.join(atom.project.getPaths()[0], relativePath);
    //   this.editor.saveAs(absolutePath);
    // }
  }

  onClose() {
    atom.workspace.getActivePaneItem().destroy();
  }

  onSocketClose(clientId) {
    atom.notifications.addInfo(clientId + ' left.');
  }

  onInsert(position, text) {
    this.doBufferChange( () => {
      let from = this.buffer.positionForCharacterIndex(position);
      this.buffer.insert(from, text);
    })
  }

  onRemove(position, length) {
    this.doBufferChange( () => {
      let from = this.buffer.positionForCharacterIndex(position);
      let to = this.buffer.positionForCharacterIndex(position + length);
      this.buffer.delete([from.toArray(), to.toArray()]);
    })
  }

  doBufferChange(callback) {
    this.push = false;
    callback();
    this.push = true;
  }

  sendSocketMsg(callback) {
    if (this.socket) {
      callback();
    }
  }
}
<commit_msg>change highlight sync to cursor indicator<commit_after>'use babel';

import {Point, Range} from 'atom';

export default class EventHandler {
  constructor(socket, editor, doc, subscriptions) {
    this.socket = socket;
    this.editor = editor;
    this.doc = doc;
    this.subscriptions = subscriptions;
    this.push = true;
    this.buffer = editor.getBuffer();
  }

  listen() {
    let disposables = [];

    // local events
    disposables.push(this.editor.onDidSave( (event) => {
      if (!this.push) {
        return;
      }
      let saveData = {
        a: 'meta',
        type: 'save',
        path: atom.project.relativizePath(event.path)[1]
      };
      setTimeout( () => {
        this.sendSocketMsg( () => {
          this.socket.send(JSON.stringify(saveData));
        });
      }, 0);
    }));

    disposables.push(this.editor.onDidChangeSelectionRange( (event) => {

      let data = {
        a: 'meta',
        type: 'cursorMoved',
        newRange: event.newBufferRange
      };
      setTimeout( () => {
        this.sendSocketMsg( () => {
          this.socket.send(JSON.stringify(data));
        });
      }, 0);
    }));

    disposables.push(this.buffer.onDidChange((event) => {
      if (!this.push) {
        return;
      }
      // remove text event
      if (event['oldText'].length !== 0) {
        let startPoint = event['oldRange'].start;
        let position = this.buffer.characterIndexForPosition(startPoint);
        let op = [position, {d: event['oldText'].length}];
        this.doc.submitOp(op);
      }
      // insert text event
      if (event['newText'].length !== 0) {
        let startPoint = event['newRange'].start;
        let position = this.buffer.characterIndexForPosition(startPoint);
        let op = [position, event['newText']];
        this.doc.submitOp(op);
      }
    }));
    this.subscriptions.add(...disposables);

    // remote events
    this.listenToServerChanges();
  }

  listenToServerChanges() {
    this.socket.on('message', (msg) => {
      let data = JSON.parse(msg);
      console.log(data);
      if (data.a !== 'meta') {
        return;
      }
      switch (data.type) {
        case 'cursorMoved':
          this.onCursorMoved(data.newRange);
          break;
        case 'select':
          // this.onSelect(data.newRange);
          break;
        case 'save':
          this.onSave(data.path);
          break;
        case 'close':
          this.onClose();
          break;
        case 'socketClose':
          this.onSocketClose(data.clientId);
          break;
        default:
      }
    });

    this.doc.on('op', (op, source) => {
      if (source) {
        console.log('Got your own changes, I am not doing anything...');
        return;
      }
      console.log('changing...');
      console.log(op);

      var pos = 0;
      for (var i = 0; i < op.length; i++) {
        var component = op[i];
        switch (typeof component) {
          case 'number':
            pos += component;
            break;
          case 'string':
            this.onInsert(pos, component);
            pos += component.length;
            break;
          case 'object':
            this.onRemove(pos, component.d);
            break;
          default:
            break;
        }
      }
    });
  }

  onCursorMoved(rangeObj) {
    // type = overlay
    if (typeof this.overlayMarker !== 'undefined') {
      this.overlayMarker.destroy();
    }

    this.overlayMarker = this.editor.markBufferRange(Range.fromObject(rangeObj), {
      'invalidate': 'never'
    });
    let element = document.createElement('div');
    element.textContent = 'Overlay decorations!';
    let style = {
      transform: 'translate(0, -100%)',
      backgroundColor: 'rgba(31, 161, 93, 1)',
      padding: '4px',
      fontFamily: 'comic sans, comic sans ms, cursive, verdana, arial, sans-serif'
    }

    Object.assign(element.style, style);
    this.editor.decorateMarker(this.overlayMarker, {
      type: 'overlay',
      position: 'head',
      item: element
    });
  }

  onSelect(rangeObj) {
    if (typeof this.marker !== 'undefined') {
      this.marker.destroy();
    }
    if (Point.fromObject(rangeObj.start).isEqual(Point.fromObject(rangeObj.end))) {
      return;
    }
    this.marker = this.editor.markBufferRange(Range.fromObject(rangeObj), {
      invalidate: 'never'
    });
    this.editor.decorateMarker(this.marker, {
      type: 'highlight',
      class: 'coeditor-selection'
    });
  }

  onSave(relativePath) {
    // atom.workspace.getActivePane().saveItem(atom.workspace.getActivePaneItem());
    this.push = false;
    console.log(atom.project.getPaths());
    if (typeof this.editor.getPath() !== 'undefined') {
      this.editor.save();
    }
    this.push = true;
    // if (typeof this.editor.getPath() !== 'undefined') {
    //   this.editor.save();
    // } else {
    //   let absolutePath = path.join(atom.project.getPaths()[0], relativePath);
    //   this.editor.saveAs(absolutePath);
    // }
  }

  onClose() {
    atom.workspace.getActivePaneItem().destroy();
  }

  onSocketClose(clientId) {
    atom.notifications.addInfo(clientId + ' left.');
  }

  onInsert(position, text) {
    this.doBufferChange( () => {
      let from = this.buffer.positionForCharacterIndex(position);
      this.buffer.insert(from, text);
    })
  }

  onRemove(position, length) {
    this.doBufferChange( () => {
      let from = this.buffer.positionForCharacterIndex(position);
      let to = this.buffer.positionForCharacterIndex(position + length);
      this.buffer.delete([from.toArray(), to.toArray()]);
    })
  }

  doBufferChange(callback) {
    this.push = false;
    callback();
    this.push = true;
  }

  sendSocketMsg(callback) {
    if (this.socket) {
      callback();
    }
  }
}
<|endoftext|>


<commit_before>// Create our grid module namespace
var grid = angular.module('game.grid', []);

// Returns a tile object
grid.factory('TileModel', function() {
  var Tile = function(pos, id, val) {
    this.x = pos.x;
    this.y = pos.y;
    this.img = this.getImage(id);
    this.val = val || 2;
    this.orientation = 0;
    this.meeples = null;
  }
  Tile.prototype.getImage = function(id) {
    // Grab an image from our asset folder to set as img attribute
    return 'assets/img/Tiles/' + id.toUpperCase() + '.png'
  };
  return Tile;
});

grid.service('GridService', function(TileModel) {

  this.createEmptyGameBoard = function() {
    var grid = [];
    var size = 72;
    for (var x = 0; x < size; x++) {
      grid[x] = [];
      for (var y = 0; y < size; y++) {
        grid[x][y] = null;
      }
    }
    return grid;
  };

});

grid.controller('gridCtrl', function($scope, TileModel, GridService){
  $scope.orientation = 0;
  $scope.src = null;

  $scope.range = function(n) {
    return new Array(n);
  };
  $scope.rotate = function() {
    $scope.orientation = ($scope.orientation + 1) % 4;
  };
  $scope.init = function() {
    // Create board
    $scope.grid = GridService.createEmptyGameBoard();
    placeInitialTile();

    socket.on('nextTurn', function(gamestate) {
      if (!gamestate.lastTile) {
        $scope.currentTile = gamestate.nextTile;
      } else {
        $scope.currentTile = gamestate.nextTile;
        updateGrid(gamestate.lastTile.x, gamestate.lastTile.y, gamestate.lastTile);
        setCell(gamestate.lastTile);
        // var tile = new TileModel({x:0, y:0}, $scope.currentTile.id);
      }
      $scope.src = getImage($scope.currentTile.id);
    });
  };

  $scope.clickCell = function(x, y) {
    if (!cellAlreadyExists(x, y)) {
      // Get out current tile generated from nextTurn
      var draw = $scope.currentTile;

      // Create our tile model
      var tile = new TileModel({x:x, y:y}, draw);
      tile.orientation = $scope.orientation;
      // $scope.src = tile.img;

      // We push a new tile onto the grid at xy
      updateGrid(x, y, tile);
      // Set the background image of grid cell
      setCell(tile);
      // emit endturn
      socket.emit('endTurn', tile);
    }
  };

  var getImage = function(id) {
    // Grab an image from our asset folder to set as img attribute
    return 'assets/img/Tiles/' + id.toUpperCase() + '.png'
  };

  // These functions should be moved into a factory/service
  var updateGrid = function(x, y, tile) {
    $scope.grid[y][x] = tile;
    console.log($scope.grid[y]);
  };

  var cellAlreadyExists = function(x, y) {
    return $scope.grid[y][x] !== null;
  };

  var setCell = function(tile) {
    var id = '#' + 'x-' + tile.x + '-y-' + tile.y;
    angular.element(document).ready(function() {
      var domElement = angular.element(document.querySelector(id));
      domElement.css('background-size', 'contain');
      domElement.css('background-image', 'url(' + tile.img + ')');
      domElement.css('transform', 'rotate(' + tile.orientation*90 + 'deg)');
    });
  };

  var placeInitialTile = function() {
    // var x = $scope.grid.length/2, y = $scope.grid.length/2;
    var x = 0, y = 0;
    var DTile = new TileModel({x: x, y: y}, 'd');
    updateGrid(x, y, DTile);
    setCell(DTile);
  };

  // Initialize game board
  $scope.init();
});
<commit_msg>got the right tile showing in playerview<commit_after>// Create our grid module namespace
var grid = angular.module('game.grid', []);

// Returns a tile object
grid.factory('TileModel', function() {
  var Tile = function(pos, id, val) {
    this.x = pos.x;
    this.y = pos.y;
    this.img = this.getImage(id);
    this.val = val || 2;
    this.orientation = 0;
    this.meeples = null;
  }
  Tile.prototype.getImage = function(id) {
    // Grab an image from our asset folder to set as img attribute
    return 'assets/img/Tiles/' + id.toUpperCase() + '.png'
  };
  return Tile;
});

grid.service('GridService', function(TileModel) {

  this.createEmptyGameBoard = function() {
    var grid = [];
    var size = 72;
    for (var x = 0; x < size; x++) {
      grid[x] = [];
      for (var y = 0; y < size; y++) {
        grid[x][y] = null;
      }
    }
    return grid;
  };

});

grid.controller('gridCtrl', function($scope, TileModel, GridService){
  $scope.orientation = 0;
  $scope.src = null;

  $scope.range = function(n) {
    return new Array(n);
  };
  $scope.rotate = function() {
    $scope.orientation = ($scope.orientation + 1) % 4;
  };
  $scope.init = function() {
    // Create board
    $scope.grid = GridService.createEmptyGameBoard();
    // angular.element(document).ready(function() {});
    placeInitialTile();

    socket.on('nextTurn', function(gamestate) {
      if (!gamestate.lastTile) {
        $scope.currentTile = gamestate.nextTile;
      } else {
        $scope.currentTile = gamestate.nextTile;
        updateGrid(gamestate.lastTile.x, gamestate.lastTile.y, gamestate.lastTile);
        setCell(gamestate.lastTile);
        // var tile = new TileModel({x:0, y:0}, $scope.currentTile.id);
      }
      $scope.src = getImage($scope.currentTile.id);
      $scope.$apply();
    });
  };

  $scope.clickCell = function(x, y) {
    if (!cellAlreadyExists(x, y)) {
      // Get out current tile generated from nextTurn
      var draw = $scope.currentTile;

      // Create our tile model
      var tile = new TileModel({x:x, y:y}, draw);
      tile.orientation = $scope.orientation;
      // $scope.src = tile.img;

      // We push a new tile onto the grid at xy
      updateGrid(x, y, tile);
      // Set the background image of grid cell
      setCell(tile);
      // emit endturn
      socket.emit('endTurn', tile);
    }
  };

  var getImage = function(id) {
    // Grab an image from our asset folder to set as img attribute
    return 'assets/img/Tiles/' + id.toUpperCase() + '.png';
  };

  // These functions should be moved into a factory/service
  var updateGrid = function(x, y, tile) {
    $scope.grid[y][x] = tile;
    console.log($scope.grid[y]);
  };

  var cellAlreadyExists = function(x, y) {
    return $scope.grid[y][x] !== null;
  };

  var setCell = function(tile) {
    var id = '#' + 'x-' + tile.x + '-y-' + tile.y;
    angular.element(document).ready(function() {
      var domElement = angular.element(document.querySelector(id));
      domElement.css('background-size', 'contain');
      domElement.css('background-image', 'url(' + tile.img + ')');
      domElement.css('transform', 'rotate(' + tile.orientation*90 + 'deg)');
    });
  };

  var placeInitialTile = function() {
    var x = $scope.grid.length/2, y = $scope.grid.length/2;
    // var x = 0, y = 0;
    var DTile = new TileModel({x: x, y: y}, 'd');
    updateGrid(x, y, DTile);
    setCell(DTile);
  };

  // Initialize game board
  $scope.init();
});
<|endoftext|>


<commit_before>import { CallBuilder } from "./call_builder";
import { BadRequestError } from "./errors";

const allowedResolutions = [60000, 300000, 900000, 3600000, 86400000, 604800000];

/**
 * Trade Aggregations facilitate efficient gathering of historical trade data.
 * Do not create this object directly, use {@link Server#tradeAggregation}.
 *
 * @class TradeAggregationCallBuilder
 * @extends CallBuilder
 * @constructor
 * @param {string} serverUrl serverUrl Horizon server URL.
 * @param {Asset} base base asset
 * @param {Asset} counter counter asset
 * @param {long} start_time lower time boundary represented as millis since epoch
 * @param {long} end_time upper time boundary represented as millis since epoch
 * @param {long} resolution segment duration as millis since epoch. *Supported values are 1 minute (60000), 5 minutes (300000), 15 minutes (900000), 1 hour (3600000), 1 day (86400000) and 1 week (604800000).
 * @param {long} offset segments can be offset using this parameter. Expressed in milliseconds. *Can only be used if the resolution is greater than 1 hour. Value must be in whole hours, less than the provided resolution, and less than 24 hours.
 */
export class TradeAggregationCallBuilder extends CallBuilder {
  constructor (serverUrl, base, counter, start_time, end_time, resolution, offset){
    super(serverUrl);

    this.url.segment('trade_aggregations');
    if (!base.isNative()) {
        this.url.setQuery("base_asset_type", base.getAssetType());
        this.url.setQuery("base_asset_code", base.getCode());
        this.url.setQuery("base_asset_issuer", base.getIssuer());
    } else {
        this.url.setQuery("base_asset_type", 'native');
    }
    if (!counter.isNative()) {
        this.url.setQuery("counter_asset_type", counter.getAssetType());
        this.url.setQuery("counter_asset_code", counter.getCode());
        this.url.setQuery("counter_asset_issuer", counter.getIssuer());
    } else {
        this.url.setQuery("counter_asset_type", 'native');
    }
    if ((typeof start_time === 'undefined') || (typeof end_time === 'undefined')) {
        throw new BadRequestError("Invalid time bounds", [start_time, end_time]);
    }else{
        this.url.setQuery("start_time", start_time);
        this.url.setQuery("end_time", end_time);
    }
    if (!this.isValidResolution(resolution)) {
        throw new BadRequestError("Invalid resolution", resolution);
    }else{
        this.url.setQuery("resolution", resolution);
    }
    if (!this.isValidOffset(offset, resolution)) {
        throw new BadRequestError("Invalid offset", offset);
    } else {
        this.url.setQuery("offset", offset);
    }

  }

  /**
   * @private
   * @param {long} resolution
   */
  isValidResolution(resolution){
    let found = false;

    for (let i = 0; i < allowedResolutions.length; i++) {
        if (allowedResolutions[i] == resolution) {
            found = true;
            break;
        }
    }
    return found;
  }

   /**
   * @private
   * @param {long} offset
   * @param {long} resolution
   */
  isValidOffset(offset, resolution){
    const hour = 3600000;
    return !(offset > resolution || offset > 24 * hour || (offset % hour !== 0));
  }
}
<commit_msg>bugfix: offset must less than 24 hours (#223)<commit_after>import { CallBuilder } from "./call_builder";
import { BadRequestError } from "./errors";

const allowedResolutions = [60000, 300000, 900000, 3600000, 86400000, 604800000];

/**
 * Trade Aggregations facilitate efficient gathering of historical trade data.
 * Do not create this object directly, use {@link Server#tradeAggregation}.
 *
 * @class TradeAggregationCallBuilder
 * @extends CallBuilder
 * @constructor
 * @param {string} serverUrl serverUrl Horizon server URL.
 * @param {Asset} base base asset
 * @param {Asset} counter counter asset
 * @param {long} start_time lower time boundary represented as millis since epoch
 * @param {long} end_time upper time boundary represented as millis since epoch
 * @param {long} resolution segment duration as millis since epoch. *Supported values are 1 minute (60000), 5 minutes (300000), 15 minutes (900000), 1 hour (3600000), 1 day (86400000) and 1 week (604800000).
 * @param {long} offset segments can be offset using this parameter. Expressed in milliseconds. *Can only be used if the resolution is greater than 1 hour. Value must be in whole hours, less than the provided resolution, and less than 24 hours.
 */
export class TradeAggregationCallBuilder extends CallBuilder {
  constructor (serverUrl, base, counter, start_time, end_time, resolution, offset){
    super(serverUrl);

    this.url.segment('trade_aggregations');
    if (!base.isNative()) {
        this.url.setQuery("base_asset_type", base.getAssetType());
        this.url.setQuery("base_asset_code", base.getCode());
        this.url.setQuery("base_asset_issuer", base.getIssuer());
    } else {
        this.url.setQuery("base_asset_type", 'native');
    }
    if (!counter.isNative()) {
        this.url.setQuery("counter_asset_type", counter.getAssetType());
        this.url.setQuery("counter_asset_code", counter.getCode());
        this.url.setQuery("counter_asset_issuer", counter.getIssuer());
    } else {
        this.url.setQuery("counter_asset_type", 'native');
    }
    if ((typeof start_time === 'undefined') || (typeof end_time === 'undefined')) {
        throw new BadRequestError("Invalid time bounds", [start_time, end_time]);
    }else{
        this.url.setQuery("start_time", start_time);
        this.url.setQuery("end_time", end_time);
    }
    if (!this.isValidResolution(resolution)) {
        throw new BadRequestError("Invalid resolution", resolution);
    }else{
        this.url.setQuery("resolution", resolution);
    }
    if (!this.isValidOffset(offset, resolution)) {
        throw new BadRequestError("Invalid offset", offset);
    } else {
        this.url.setQuery("offset", offset);
    }

  }

  /**
   * @private
   * @param {long} resolution
   */
  isValidResolution(resolution){
    let found = false;

    for (let i = 0; i < allowedResolutions.length; i++) {
        if (allowedResolutions[i] == resolution) {
            found = true;
            break;
        }
    }
    return found;
  }

   /**
   * @private
   * @param {long} offset
   * @param {long} resolution
   */
  isValidOffset(offset, resolution){
    const hour = 3600000;
    return !(offset > resolution || offset >= 24 * hour || (offset % hour !== 0));
  }
}
<|endoftext|>


<commit_before>'use strict';

/*global $, nodeca, document*/

var $notice = $([]), $message = $([]);

function init() {
  if (!$notice.length) {
    $notice   = $(nodeca.views.widgets.notice());
    $message  = $notice.find('.message');

    $notice.appendTo(document.body);
    $notice.find('.close').click(module.exports.hide);

    module.exports.hide();
  }
}

module.exports.show = function (message) {
  init();
  $message.html(message);
  $notice.show();
};

module.exports.hide = function () {
  $notice.hide();
  $message.html('');
};
<commit_msg>remove strict document call<commit_after>'use strict';

/*global $, nodeca*/

var $notice = $([]), $message = $([]);

function init() {
  if (!$notice.length) {
    $notice   = $(nodeca.views.widgets.notice());
    $message  = $notice.find('.message');

    $notice.appendTo($('body'));
    $notice.find('.close').click(module.exports.hide);

    module.exports.hide();
  }
}

module.exports.show = function (message) {
  init();
  $message.html(message);
  $notice.show();
};

module.exports.hide = function () {
  $notice.hide();
  $message.html('');
};
<|endoftext|>


<commit_before>/* Copyright (c) 2010 - 2012 Kaj Magnus Lindberg. All rights reserved. */

//========================================
   (function(){
//========================================
//----------------------------------------
   jQuery.noConflict()(function($){
//----------------------------------------

"use strict";

var d = { i: debiki.internal, u: debiki.v0.util };

// Debiki convention: Dialog elem tabindexes should vary from 101 to 109.
// HTML generation code assumes this, too. See Debiki for Developers, #7bZG31.
d.i.DEBIKI_TABINDEX_DIALOG_MAX = 109;

d.i.rootPostId = $('.dw-depth-0');
d.i.rootPostId = d.i.rootPostId.length ?
    d.i.rootPostId.attr('id').substr(5) : undefined; // drops initial `dw-t-'

// If there's no SVG support, use PNG arrow images instead.
d.i.SVG = Modernizr.inlinesvg && document.URL.indexOf('svg=false') === -1 ?
    d.i.makeSvgDrawer($) : d.i.makeFakeDrawer($);

d.i.Me = d.i.makeCurUser();

// Inits a post and its parent thread.
// Makes posts resizable, activates mouseenter/leave functionality,
// draws arrows to child threads, etc.
// Initing a thread is done in 4 steps. This function calls all those 4 steps.
// (The initialization is split into steps, so everything need not be done
// at once on page load.)
// Call on posts.
d.i.$initPostsThread = function() {
  $initPostsThreadStep1.apply(this);
  $initPostsThreadStep2.apply(this);
  $initPostsThreadStep3.apply(this);
  $initPostsThreadStep4.apply(this);
};

function $initPostsThreadStep1() {
  // Binding click events to action links takes rather long (100 ms on my
  // 6 core 2.8 GHz AMD for 200 posts) and could be split out to a separate
  // step, done as late as possible.
  d.i.createAndBindActionLinksForPost(this);

  // Open/close threads if the fold link is clicked.
  var $thread = $(this).closest('.dw-t');
  $thread.children('.dw-z').click(d.i.$threadToggleFolded);
};

function $initPostsThreadStep2() {
  d.i.shohwActionLinksOnHoverPost(this);
  d.i.placeInlineThreadsForPost(this);
  d.i.makeHeaderPrettyForPost(this);
};

function $initPostsThreadStep3() {
  // $initPostSvg takes rather long (190 ms on my 6 core 2.8 GHz AMD, for
  // 100 posts), and  need not be done until just before SVG is drawn.
  d.i.SVG.$initPostSvg.apply(this);
};

function $initPostsThreadStep4() {
  d.i.makeThreadResizableForPost(this);
};

// Inits a post, not its parent thread.
d.i.$initPost = function() {
  // ? Should I call createAndBindActionLinksForPost and
  // shohwActionLinksOnHoverPost from here, sometimes ?
  d.i.placeInlineThreadsForPost(this);
  d.i.makeHeaderPrettyForPost(this);
  d.i.SVG.$initPostSvg.apply(this);
};

function registerEventHandlersFireLoginOut() {

  // Hide all action forms, since they will be slided in.
  $('#dw-hidden-templates .dw-fs').hide();

  // Fire the dwEvLoggedInOut event, so all buttons etc will update
  // their text with the correct user name.
  // {{{ Details:
  // Firing the dwEvLoggedInOut event causes the user name to be updated
  // to the name of the logged in user, everywhere. This needs to be done
  // in JavaScript, cannot be done only server side — because when the user
  // logs in/out using JavaScript, and uses the browser's *back* button to
  // return to an earlier page, that page might not be fetched again
  // from the server, but this javascript code updates the page to take
  // into account that the user name (and related cookies) has changed
  // (since the user logged in/out).
  // Do this when everything has been inited, so all dwEvLoggedInOut event
  // listeners have been registered. }}}

  // COULD move this to debiki-login.js
  $('.dw-loginsubmit-on-click').click(d.i.$loginThenSubmit);

  if (d.i.Me.isLoggedIn()) d.i.Me.fireLogin();
  else d.i.Me.fireLogout();

  // If the user switches browser tab, s/he might logout and login
  // in another tab. That'd invalidate all xsrf tokens on this page,
  // and user specific permissions and ratings info (for this tab).
  // Therefore, when the user switches back to this tab, check
  // if a new session has been started.
  $(window).on('focus', d.i.Me.fireLoginIfNewSession);
  //{{{ What will work w/ IE?
  // See http://stackoverflow.com/a/5556858/694469
  // But: "This script breaks down in IE(8) when you have a textarea on the
  // page.  When you click on the textarea, the document and window both
  // lose focus"
  //// IE EVENTS
  //$(document).bind('focusin', function(){
  //    alert('document focusin');
  //});
  //if (/*@cc_on!@*/false) { // check for Internet Explorer
  //  document.onfocusin = onFocus;
  //  document.onfocusout = onBlur;
  //} else {
  //  window.onfocus = onFocus;
  //  window.onblur = onBlur;
  //}
  //
  // http://stackoverflow.com/a/6184276/694469
  //window.addEventListener('focus', function() {
  //  document.title = 'focused';
  //});
  //window.addEventListener('blur', function() {
  //    document.title = 'not focused';
  //});
  //}}}
};

// Render the page step by step, to reduce page loading time. (When the first
// step is done, the user should conceive the page as mostly loaded.)

function renderPageEtc() {

  // JSON vulnerability protection.
  // ((Details: Strip a certain reply prefix. This prevents the JSON
  // from being parsed as Javascript from a <script> tag. This'd otherwise
  // allow third party websites to turn your JSON resource URL into JSONP
  // request under some conditions, see:
  //   http://docs.angularjs.org/api/ng.$http, the "JSON Vulnerability
  // Protection" section.))
  $.ajaxSetup({
    dataFilter: function (response, type) {
      // Don't know why, but `type` is alwyas undefined, so won't work:
      // if (type !== 'json') return response;
      // Sometimes, in FF (at least) and when the server replies 200 OK
      // with no body it seems, `response` is the `document` itself,
      // oddly enough, not a string.
      if (typeof response === 'string')
        response = response.replace(/^\)\]}',\n/, '');
      return response;
    }
  });

  var $posts = $('.debiki .dw-p:not(.dw-p-ttl)');

  (d.u.workAroundAndroidZoomBug || function() {})($);

  // IE 6, 7 and 8 specific elems (e.g. upgrade-to-newer-browser info)
  // (Could do this on the server instead, that'd work also with Javascript
  // disabled. But people who know what javascript is and disable it,
  // probably don't use IE 6 and 7? So this'll be fine for now.)
  var $body =  $('body');
  if ($.browser.msie) {
    if ($.browser.version < '8') $body.addClass('dw-ua-lte-ie7');
    if ($.browser.version < '9') $body.addClass('dw-ua-lte-ie8');
  }

  d.i.Me.refreshProps();
  d.i.showCurLocationInSiteNav();

  if (!Modernizr.touch) {
    d.i.initKeybdShortcuts($);
    d.i.initUtterscrollAndTips();
  }

  // When you zoom in or out, the width of the root thread might change
  // a few pixels — then its parent should be resized so the root
  // thread fits inside with no float drop.
  d.u.zoomListeners.push(d.i.resizeRootThread);

  var steps = [];

  steps.push(function() {
    $posts.each($initPostsThreadStep1);
    $('html').removeClass('dw-render-actions-pending');
  });

  steps.push(function() {
    $posts.each($initPostsThreadStep2)
  });

  steps.push(function() {
    $posts.each($initPostsThreadStep3);
    registerEventHandlersFireLoginOut();
  });

  // COULD fire login earlier; it's confusing that the 'Login' link
  // is visible for rather long, when you load a *huge* page.
  steps.push(function() {
    $posts.each($initPostsThreadStep4)
  });

  // Don't draw SVG until all html tags has been placed, or the SVG
  // arrows might be offset incorrectly.
  // Actually, drawing SVG takes long, so wait for a while,
  // don't do it on page load.
  steps.push(d.i.SVG.initRootDrawArrows);

  steps.push(d.i.scrollToUrlAnchorPost);

  // Resize the article, now when the page has been rendered, and all inline
  // threads have been placed and can be taken into account.
  steps.push(function() {
    d.i.resizeRootThread();
    $('html').removeClass('dw-render-layout-pending');
    debiki.scriptLoad.resolve();
  });

  function runNextStep() {
    steps[0]();
    steps.shift();
    if (steps.length > 0)
      setTimeout(runNextStep, 70);
  }

  setTimeout(runNextStep, 60);
};

// Dont render page, if there is no root post, or some error happens,
// which kills other Javascript that runs on page load.
if (!d.i.rootPostId)
  return;

renderPageEtc();

//----------------------------------------
   }); // end jQuery onload
//----------------------------------------
//========================================
   }()); // end Debiki module
//========================================

// vim: fdm=marker et ts=2 sw=2 tw=80 fo=tcqwn list
<commit_msg>Use PNG arrows on mobile phones; rendering SVG arrows takes long.<commit_after>/* Copyright (c) 2010 - 2012 Kaj Magnus Lindberg. All rights reserved. */

//========================================
   (function(){
//========================================
//----------------------------------------
   jQuery.noConflict()(function($){
//----------------------------------------

"use strict";

var d = { i: debiki.internal, u: debiki.v0.util };

// Debiki convention: Dialog elem tabindexes should vary from 101 to 109.
// HTML generation code assumes this, too. See Debiki for Developers, #7bZG31.
d.i.DEBIKI_TABINDEX_DIALOG_MAX = 109;

d.i.rootPostId = $('.dw-depth-0');
d.i.rootPostId = d.i.rootPostId.length ?
    d.i.rootPostId.attr('id').substr(5) : undefined; // drops initial `dw-t-'

// If there's no SVG support, use PNG arrow images instead.
// Also use PNG arrows on mobiles; rendering SVG arrows takes rather long.
d.i.SVG = !Modernizr.touch && Modernizr.inlinesvg &&
      document.URL.indexOf('svg=false') === -1 ?
    d.i.makeSvgDrawer($) : d.i.makeFakeDrawer($);

d.i.Me = d.i.makeCurUser();

// Inits a post and its parent thread.
// Makes posts resizable, activates mouseenter/leave functionality,
// draws arrows to child threads, etc.
// Initing a thread is done in 4 steps. This function calls all those 4 steps.
// (The initialization is split into steps, so everything need not be done
// at once on page load.)
// Call on posts.
d.i.$initPostsThread = function() {
  $initPostsThreadStep1.apply(this);
  $initPostsThreadStep2.apply(this);
  $initPostsThreadStep3.apply(this);
  $initPostsThreadStep4.apply(this);
};

function $initPostsThreadStep1() {
  // Binding click events to action links takes rather long (100 ms on my
  // 6 core 2.8 GHz AMD for 200 posts) and could be split out to a separate
  // step, done as late as possible.
  d.i.createAndBindActionLinksForPost(this);

  // Open/close threads if the fold link is clicked.
  var $thread = $(this).closest('.dw-t');
  $thread.children('.dw-z').click(d.i.$threadToggleFolded);
};

function $initPostsThreadStep2() {
  d.i.shohwActionLinksOnHoverPost(this);
  d.i.placeInlineThreadsForPost(this);
  d.i.makeHeaderPrettyForPost(this);
};

function $initPostsThreadStep3() {
  // $initPostSvg takes rather long (190 ms on my 6 core 2.8 GHz AMD, for
  // 100 posts), and  need not be done until just before SVG is drawn.
  d.i.SVG.$initPostSvg.apply(this);
};

function $initPostsThreadStep4() {
  d.i.makeThreadResizableForPost(this);
};

// Inits a post, not its parent thread.
d.i.$initPost = function() {
  // ? Should I call createAndBindActionLinksForPost and
  // shohwActionLinksOnHoverPost from here, sometimes ?
  d.i.placeInlineThreadsForPost(this);
  d.i.makeHeaderPrettyForPost(this);
  d.i.SVG.$initPostSvg.apply(this);
};

function registerEventHandlersFireLoginOut() {

  // Hide all action forms, since they will be slided in.
  $('#dw-hidden-templates .dw-fs').hide();

  // Fire the dwEvLoggedInOut event, so all buttons etc will update
  // their text with the correct user name.
  // {{{ Details:
  // Firing the dwEvLoggedInOut event causes the user name to be updated
  // to the name of the logged in user, everywhere. This needs to be done
  // in JavaScript, cannot be done only server side — because when the user
  // logs in/out using JavaScript, and uses the browser's *back* button to
  // return to an earlier page, that page might not be fetched again
  // from the server, but this javascript code updates the page to take
  // into account that the user name (and related cookies) has changed
  // (since the user logged in/out).
  // Do this when everything has been inited, so all dwEvLoggedInOut event
  // listeners have been registered. }}}

  // COULD move this to debiki-login.js
  $('.dw-loginsubmit-on-click').click(d.i.$loginThenSubmit);

  if (d.i.Me.isLoggedIn()) d.i.Me.fireLogin();
  else d.i.Me.fireLogout();

  // If the user switches browser tab, s/he might logout and login
  // in another tab. That'd invalidate all xsrf tokens on this page,
  // and user specific permissions and ratings info (for this tab).
  // Therefore, when the user switches back to this tab, check
  // if a new session has been started.
  $(window).on('focus', d.i.Me.fireLoginIfNewSession);
  //{{{ What will work w/ IE?
  // See http://stackoverflow.com/a/5556858/694469
  // But: "This script breaks down in IE(8) when you have a textarea on the
  // page.  When you click on the textarea, the document and window both
  // lose focus"
  //// IE EVENTS
  //$(document).bind('focusin', function(){
  //    alert('document focusin');
  //});
  //if (/*@cc_on!@*/false) { // check for Internet Explorer
  //  document.onfocusin = onFocus;
  //  document.onfocusout = onBlur;
  //} else {
  //  window.onfocus = onFocus;
  //  window.onblur = onBlur;
  //}
  //
  // http://stackoverflow.com/a/6184276/694469
  //window.addEventListener('focus', function() {
  //  document.title = 'focused';
  //});
  //window.addEventListener('blur', function() {
  //    document.title = 'not focused';
  //});
  //}}}
};

// Render the page step by step, to reduce page loading time. (When the first
// step is done, the user should conceive the page as mostly loaded.)

function renderPageEtc() {

  // JSON vulnerability protection.
  // ((Details: Strip a certain reply prefix. This prevents the JSON
  // from being parsed as Javascript from a <script> tag. This'd otherwise
  // allow third party websites to turn your JSON resource URL into JSONP
  // request under some conditions, see:
  //   http://docs.angularjs.org/api/ng.$http, the "JSON Vulnerability
  // Protection" section.))
  $.ajaxSetup({
    dataFilter: function (response, type) {
      // Don't know why, but `type` is alwyas undefined, so won't work:
      // if (type !== 'json') return response;
      // Sometimes, in FF (at least) and when the server replies 200 OK
      // with no body it seems, `response` is the `document` itself,
      // oddly enough, not a string.
      if (typeof response === 'string')
        response = response.replace(/^\)\]}',\n/, '');
      return response;
    }
  });

  var $posts = $('.debiki .dw-p:not(.dw-p-ttl)');

  (d.u.workAroundAndroidZoomBug || function() {})($);

  // IE 6, 7 and 8 specific elems (e.g. upgrade-to-newer-browser info)
  // (Could do this on the server instead, that'd work also with Javascript
  // disabled. But people who know what javascript is and disable it,
  // probably don't use IE 6 and 7? So this'll be fine for now.)
  var $body =  $('body');
  if ($.browser.msie) {
    if ($.browser.version < '8') $body.addClass('dw-ua-lte-ie7');
    if ($.browser.version < '9') $body.addClass('dw-ua-lte-ie8');
  }

  d.i.Me.refreshProps();
  d.i.showCurLocationInSiteNav();

  if (!Modernizr.touch) {
    d.i.initKeybdShortcuts($);
    d.i.initUtterscrollAndTips();
  }

  // When you zoom in or out, the width of the root thread might change
  // a few pixels — then its parent should be resized so the root
  // thread fits inside with no float drop.
  d.u.zoomListeners.push(d.i.resizeRootThread);

  var steps = [];

  steps.push(function() {
    $posts.each($initPostsThreadStep1);
    $('html').removeClass('dw-render-actions-pending');
  });

  steps.push(function() {
    $posts.each($initPostsThreadStep2)
  });

  steps.push(function() {
    $posts.each($initPostsThreadStep3);
    registerEventHandlersFireLoginOut();
  });

  // COULD fire login earlier; it's confusing that the 'Login' link
  // is visible for rather long, when you load a *huge* page.
  steps.push(function() {
    $posts.each($initPostsThreadStep4)
  });

  // Don't draw SVG until all html tags has been placed, or the SVG
  // arrows might be offset incorrectly.
  // Actually, drawing SVG takes long, so wait for a while,
  // don't do it on page load.
  steps.push(d.i.SVG.initRootDrawArrows);

  steps.push(d.i.scrollToUrlAnchorPost);

  // Resize the article, now when the page has been rendered, and all inline
  // threads have been placed and can be taken into account.
  steps.push(function() {
    d.i.resizeRootThread();
    $('html').removeClass('dw-render-layout-pending');
    debiki.scriptLoad.resolve();
  });

  function runNextStep() {
    steps[0]();
    steps.shift();
    if (steps.length > 0)
      setTimeout(runNextStep, 70);
  }

  setTimeout(runNextStep, 60);
};

// Dont render page, if there is no root post, or some error happens,
// which kills other Javascript that runs on page load.
if (!d.i.rootPostId)
  return;

renderPageEtc();

//----------------------------------------
   }); // end jQuery onload
//----------------------------------------
//========================================
   }()); // end Debiki module
//========================================

// vim: fdm=marker et ts=2 sw=2 tw=80 fo=tcqwn list
<|endoftext|>