<commit_before>'use strict';

const
	$C = require('collection.js'),
	Sugar = require('sugar');

const
	fs = require('fs-extra-promise'),
	path = require('upath');

const
	glob = require('glob-promise'),
	vinyl = require('vinyl-fs'),
	hasha = require('hasha'),
	isPathInside = require('is-path-inside');

const
	resolve = require('./resolve'),
	Declaration = require('./declaration');

const
	{dependencies} = require('./config'),
	{blockTypeList} = /** @type {{blockTypeList: Array<string>}} */ require('./validators'),
	{createSerializableMap, createSerializableSet, jsonReviver} = require('./helpers');

const
	filesCache = Object.create(null),
	blockCache = Object.create(null);

/**
 * Validates a file by the specified path and return meta information
 *
 * @param {string} path
 * @param {!Object} cache - cache object
 * @returns {{mtime: !Object, fromCache: boolean}}
 */
function validateCache(path, cache) {
	const
		{mtime} = fs.statSync(path);

	return {
		mtime,
		fromCache: Boolean(cache[path] && Sugar.Date.is(mtime, cache[path].mtime))
	};
}

/**
 * Returns a file content by the specified path
 *
 * @param {string} path
 * @returns {string}
 */
function getFile(path) {
	const
		cache = validateCache(path, filesCache);

	if (!cache.fromCache) {
		filesCache[path] = {
			mtime: cache.mtime,
			content: fs.readFileSync(path, 'utf-8')
		};
	}

	return filesCache[path].content;
}

/**
 * Throws a new blockNotFound error by the specified name
 * @param {string} name
 */
function blockNotFound(name) {
	throw new Error(`Block "${name}" is not defined`);
}

class Block {
	/**
	 * Returns a block manifest file by the specified parameter
	 *
	 * @param {string} folder - block folder name
	 * @param {string} file - manifest file
	 * @returns {!Promise<Block>}
	 */
	static async get(folder, file = 'index.js') {
		const
			blockPath = await resolve.block(path.join(folder, file)),
			cache = validateCache(blockPath, blockCache);

		if (cache.fromCache) {
			return new this(blockPath, blockCache[blockPath].decl);
		}

		async function getDecl(url) {
			return new Declaration(getFile(path.join(await url, file)));
		}

		const
			decl = await getDecl(resolve.block(folder));

		if (decl.mixin) {
			let
				skip = 1,
				parent;

			while ((parent = await resolve.block(decl.name, skip))) {
				const
					parentDecl = await getDecl(parent.path);

				if (!decl.parent && parentDecl.parent) {
					decl.parent = parentDecl.parent;
				}

				decl.dependencies = [...new Set(decl.dependencies.concat(parentDecl.dependencies))];
				decl.libs = [...new Set(decl.libs.concat(parentDecl.libs))];

				if (parentDecl.mixin) {
					skip = parent.from;

				} else {
					break;
				}
			}
		}

		blockCache[blockPath] = {
			mtime: cache.mtime,
			decl
		};

		return new this(blockPath, decl);
	}

	/**
	 * Returns block manifests file by the specified names
	 *
	 * @param {Array<string>=} [names]
	 * @returns {!Promise<!Map<string, !Block>>}
	 */
	static async getAll(names) {
		const
			projectCWD = await resolve.cwd,
			projectHash = hasha(path.join(resolve.sourceDir, '/**/*'));

		const
			cachedBlockMap = createSerializableMap(),
			sources = [projectCWD, ...dependencies];

		for (let i = sources.length; i--;) {
			try {
				const
					dep = Sugar.Object.isString(sources[i]) ? sources[i] : sources[i],
					file = require.resolve(path.join(dep, 'graph-lock.json'));

				if (!fs.existsSync(file)) {
					break;
				}

				const normalizedJSONReviver = (key, val) => {
					val = jsonReviver(key, val);

					if (Sugar.Object.isString(val) && path.extname(val) !== '') {
						return path.resolve(path.dirname(file), val);
					}

					if (Sugar.Object.isObject(val) && val.declaration != null) {
						Object.setPrototypeOf(val, Block.prototype);
					}

					return val;
				};

				const
					fromCache = fs.readJSONSync(file, {reviver: normalizedJSONReviver});

				if (i === 0 && fromCache.hash !== projectHash) {
					break;
				}

				$C.extend(false, cachedBlockMap, fromCache.data);
				sources[i] = null;

			} catch {
				// We can ignore the deepest layer, i. e. @v4fire/core
				if (i === sources.length - 1) {
					sources[i] = null;
					continue;
				}

				break;
			}
		}

		if (sources.every((el) => el === null)) {
			return cachedBlockMap;
		}

		if (!names) {
			const
				b = `@(${blockTypeList.join('|')})-*`,
				components = `/**/${b}/index.js`,
				virtualComponents = `/**/${b}.index.js`;

			let
				dests = [await resolve.block(), ...resolve.dependencies];

			for (let i = 0; i < sources.length; i++) {
				if (sources[i] === null) {
					dests[i] = null;
				}
			}

			dests = dests.filter((el) => el !== null);

			names = await $C(dests)
				.async
				.to(new Map())
				.reduce(async (map, root) => {
					const src = [
						path.join(root, components),
						path.join(root, virtualComponents)
					];

					if (!map.has(src)) {
						map.set(
							src,

							await $C(vinyl.src(src, {read: false}))
								.async
								.to([])
								.map((file) => {
									file.base = root;
									return [path.dirname(file.relative), file.basename];
								})
						);
					}

					return map;
				});
		}

		const
			set = Object.create(null);

		const blocks = await $C(names)
			.parallel()
			.to([])
			.reduce(async (res, el) => {
				const
					i = res.push([]) - 1;

				await $C(el || []).async.forEach(async ([folder, name], j) => {
					if (set[folder] && set[folder] === name) {
						return;
					}

					set[folder] = name;

					const
						block = await this.get(folder, name);

					if (block) {
						res[i][j] = block;
					}
				});

				return res;
			});

		const blockMap = await $C(Sugar.Array.flatten(blocks)).to(new Map()).reduce((map, decl) => {
			if (!map.has(decl.name)) {
				return map.set(decl.name, decl);
			}

			return map;
		});

		const
			mixedBlockMap = $C.extend({traits: true}, new Map(), blockMap, cachedBlockMap);

		const normalizedBlockMap = await $C(mixedBlockMap).async.to(createSerializableMap()).reduce(async (map, decl, key) => {
			if (!blockMap.has(key)) {
				map.set(key, decl);

			} else {
				map.set(key, {
					...decl,

					name: decl.name,

					parent: decl.parent,
					resolvedParent: await decl.getParent({cache: mixedBlockMap}),

					dependencies: decl.dependencies,
					resolvedDependencies: await decl.getDependencies({cache: mixedBlockMap}),
					resolvedOwnDependencies: await decl.getDependencies({cache: mixedBlockMap}),

					libs: decl.libs,
					resolvedLibs: await decl.getLibs({cache: mixedBlockMap}),
					resolvedOwnLibs: await decl.getLibs({onlyOwn: true, cache: mixedBlockMap}),
					runtimeDependencies: await decl.getRuntimeDependencies({cache: mixedBlockMap}),

					type: decl.type,
					mixin: decl.mixin,

					index: await decl.index,
					logic: await decl.logic,
					styles: await decl.styles,
					tpl: await decl.tpl,
					etpl: await decl.etpl,

					__proto__: Block.prototype
				});
			}

			return map;
		});

		const blockMapToSave = $C(blockMap).to(createSerializableMap()).reduce((map, decl, key) => {
			if (blockMap.has(key)) {
				map.set(key, normalizedBlockMap.get(key));
			}

			return map;
		});

		const cacheData = {
			hash: projectHash,
			data: blockMapToSave
		};

		const jsonReplacer = (key, val) => {
			if (Sugar.Object.isString(val) && path.extname(val) !== '' && path.isAbsolute(val)) {
				return path.relative(projectCWD, val);
			}

			if (Sugar.Object.isObject(val) && val.declaration != null) {
				return normalizedBlockMap.get(key);
			}

			return val;
		};

		fs.writeFileSync(path.join(projectCWD, 'graph-lock.json'), JSON.stringify(cacheData, jsonReplacer, 2));

		return normalizedBlockMap;
	}

	/**
	 * Returns a path to the logic file
	 * @returns {!Promise<?string>}
	 */
	get logic() {
		return resolve.block(`${this.name}/${this.name}.logic`);
	}

	/**
	 * Returns a path to the template file
	 * @returns {!Promise<?string>}
	 */
	get tpl() {
		return resolve.block(`${this.name}/${this.name}.ss`);
	}

	/**
	 * Returns a path to the ess template file
	 * @returns {!Promise<?string>}
	 */
	get etpl() {
		return resolve.block(`${this.name}/${this.name}.ess`);
	}

	/**
	 * Returns paths to the style files
	 * @returns {!Promise<!Array<string>>}
	 */
	get styles() {
		return (async () => {
			const
				root = await resolve.block(),
				style = await resolve.block(`${this.name}/${this.name}.styl`),
				styles = [].concat(style || []);

			if (this.mixin && !isPathInside(style, root)) {
				styles.push(...await glob(path.join(root, `/**/${this.name}/${this.name}_*.styl`)));
			}

			return styles;
		})();
	}

	/**
	 * Block name
	 * @returns {string}
	 */
	get name() {
		return this.declaration.name;
	}

	/**
	 * Block type
	 * @returns {string}
	 */
	get type() {
		return this.declaration.type;
	}

	/**
	 * Block parent
	 * @returns {?string}
	 */
	get parent() {
		return this.declaration.parent;
	}

	/**
	 * Block mixin status
	 * @returns {boolean}
	 */
	get mixin() {
		return this.declaration.mixin;
	}

	/**
	 * Block dependencies
	 * @returns {!Array<string>}
	 */
	get dependencies() {
		return this.declaration.dependencies;
	}

	/**
	 * Block libraries
	 * @returns {!Array<string>}
	 */
	get libs() {
		return this.declaration.libs;
	}

	/**
	 * @param index - block index src
	 * @param declaration - block declaration
	 */
	constructor(index, declaration) {
		this.index = index;
		this.declaration = declaration;
	}

	/**
	 * Returns the block parent manifest
	 *
	 * @param {Map<string, !Block>=} [cache] - optional cache object with predefined blocks
	 * @returns {!Promise<Block>}
	 */
	async getParent({cache} = {}) {
		if ('resolvedParent' in this) {
			return this.resolvedParent;
		}

		if (!this.parent) {
			return null;
		}

		const
			block = cache ? cache.get(this.parent) : await this.constructor.get(this.parent);

		if (!block) {
			blockNotFound(this.parent);
		}

		return block;
	}

	/**
	 * Returns manifests of all block dependencies
	 *
	 * @param {boolean=} [onlyOwn] - if true parent dependencies also included
	 * @param {Map<string, !Block>=} [cache] - optional cache object with predefined blocks
	 * @returns {!Promise<!Map<string, !Block>>}
	 */
	async getDependencies({onlyOwn, cache} = {}) {
		if (onlyOwn) {
			if ('resolvedOwnDependencies' in this) {
				return this.resolvedOwnDependencies;
			}

		} else if ('resolvedDependencies' in this) {
			return this.resolvedDependencies;
		}

		let
			names = this.dependencies.slice();

		if (!onlyOwn) {
			let
				parent = await this.getParent({cache});

			while (parent) {
				names = [...parent.dependencies, ...names];
				parent = await parent.getParent({cache});
			}
		}

		if (cache) {
			return $C(names).to(createSerializableMap()).reduce((map, name) => {
				const
					block = cache.get(name);

				if (!block) {
					blockNotFound(name);
				}

				return map.set(name, block);
			});
		}

		return this.constructor.getAll([...new Set(names)]);
	}

	/**
	 * Returns manifests of all block libraries
	 *
	 * @param {boolean=} [onlyOwn] - if true parent libraries also included
	 * @param {Map<string, !Block>=} [cache] - optional cache object with predefined blocks
	 * @returns {!Promise<!Set<string>>}
	 */
	async getLibs({onlyOwn, cache} = {}) {
		if (onlyOwn) {
			if ('resolvedOwnLibs' in this) {
				return this.resolvedOwnLibs;
			}

		} else if ('resolvedLibs' in this) {
			return this.resolvedLibs;
		}

		let
			libs = this.libs.slice();

		if (!onlyOwn) {
			let
				parent = await this.getParent({cache});

			while (parent) {
				libs = [...parent.libs, ...libs];
				parent = await parent.getParent({cache});
			}
		}

		return createSerializableSet(libs);
	}

	/**
	 * Returns an object with block runtime dependencies
	 *
	 * @param {Map<string, !Block>=} [cache] - optional cache object with predefined blocks
	 * @returns {!Promise<{runtime: !Map<string, !Block>, parents: !Map<string, !Block>, libs: !Set<string>}>}
	 */
	async getRuntimeDependencies({cache} = {}) {
		if ('runtimeDependencies' in this) {
			return this.runtimeDependencies;
		}

		let
			runtime = createSerializableMap(),
			parents = createSerializableMap();

		const get = async (name, isParent) => {
			const
				block = name === this.name ? this : cache ? cache.get(name) : await this.constructor.get(name);

			if (!block) {
				blockNotFound(name);
			}

			runtime = createSerializableMap([[name, block], ...runtime.entries()]);
			if (!isParent && parents.has(name)) {
				parents.delete(name);
			}

			await $C(await block.getDependencies({onlyOwn: true, cache})).async.forEach(async (block) => {
				runtime = createSerializableMap([[block.name, block], ...runtime.entries()]);
				await get(block.name, false, runtime, parents);
			});

			const
				parentName = block.parent;

			if (parentName) {
				const
					parent = await block.getParent({cache});

				if (!runtime.has(parentName)) {
					parents = createSerializableMap([[parentName, parent], ...parents.entries()]);
					runtime = createSerializableMap([[parentName, parent], ...runtime.entries()]);
				}

				await get(parentName, true, runtime, parents);
			}
		};

		const [libs] = await Promise.all([
			this.getLibs({cache}),
			get(this.name)
		]);

		return {runtime, parents, libs};
	}
}

module.exports = Block;
<commit_msg>Removed multiple caching<commit_after>'use strict';

const
	$C = require('collection.js'),
	Sugar = require('sugar');

const
	fs = require('fs-extra-promise'),
	path = require('upath');

const
	glob = require('glob-promise'),
	vinyl = require('vinyl-fs'),
	hasha = require('hasha'),
	isPathInside = require('is-path-inside');

const
	resolve = require('./resolve'),
	Declaration = require('./declaration');

const
	{blockTypeList} = /** @type {{blockTypeList: Array<string>}} */ require('./validators'),
	{createSerializableMap, createSerializableSet, jsonReviver} = require('./helpers');

const
	filesCache = Object.create(null),
	blockCache = Object.create(null);

/**
 * Validates a file by the specified path and return meta information
 *
 * @param {string} path
 * @param {!Object} cache - cache object
 * @returns {{mtime: !Object, fromCache: boolean}}
 */
function validateCache(path, cache) {
	const
		{mtime} = fs.statSync(path);

	return {
		mtime,
		fromCache: Boolean(cache[path] && Sugar.Date.is(mtime, cache[path].mtime))
	};
}

/**
 * Returns a file content by the specified path
 *
 * @param {string} path
 * @returns {string}
 */
function getFile(path) {
	const
		cache = validateCache(path, filesCache);

	if (!cache.fromCache) {
		filesCache[path] = {
			mtime: cache.mtime,
			content: fs.readFileSync(path, 'utf-8')
		};
	}

	return filesCache[path].content;
}

/**
 * Throws a new blockNotFound error by the specified name
 * @param {string} name
 */
function blockNotFound(name) {
	throw new Error(`Block "${name}" is not defined`);
}

class Block {
	/**
	 * Returns a block manifest file by the specified parameter
	 *
	 * @param {string} folder - block folder name
	 * @param {string} file - manifest file
	 * @returns {!Promise<Block>}
	 */
	static async get(folder, file = 'index.js') {
		const
			blockPath = await resolve.block(path.join(folder, file)),
			cache = validateCache(blockPath, blockCache);

		if (cache.fromCache) {
			return new this(blockPath, blockCache[blockPath].decl);
		}

		async function getDecl(url) {
			return new Declaration(getFile(path.join(await url, file)));
		}

		const
			decl = await getDecl(resolve.block(folder));

		if (decl.mixin) {
			let
				skip = 1,
				parent;

			while ((parent = await resolve.block(decl.name, skip))) {
				const
					parentDecl = await getDecl(parent.path);

				if (!decl.parent && parentDecl.parent) {
					decl.parent = parentDecl.parent;
				}

				decl.dependencies = [...new Set(decl.dependencies.concat(parentDecl.dependencies))];
				decl.libs = [...new Set(decl.libs.concat(parentDecl.libs))];

				if (parentDecl.mixin) {
					skip = parent.from;

				} else {
					break;
				}
			}
		}

		blockCache[blockPath] = {
			mtime: cache.mtime,
			decl
		};

		return new this(blockPath, decl);
	}

	/**
	 * Returns block manifests file by the specified names
	 *
	 * @param {Array<string>=} [names]
	 * @returns {!Promise<!Map<string, !Block>>}
	 */
	static async getAll(names) {
		const
			projectCWD = await resolve.cwd,
			projectHash = hasha(path.join(resolve.sourceDir, '/**/*')),
			graphLockFile = path.join(projectCWD, 'graph-lock.json');

		try {
			if (fs.existsSync(graphLockFile)) {
				const normalizedJSONReviver = (key, val) => {
					val = jsonReviver(key, val);

					if (Sugar.Object.isString(val) && path.extname(val) !== '') {
						return path.resolve(path.dirname(graphLockFile), val);
					}

					if (Sugar.Object.isObject(val) && val.declaration != null) {
						Object.setPrototypeOf(val, Block.prototype);
					}

					return val;
				};

				const
					fromCache = fs.readJSONSync(graphLockFile, {reviver: normalizedJSONReviver});

				if (fromCache.hash === projectHash) {
					return fromCache.data;
				}
			}

		} catch {}

		if (!names) {
			const
				b = `@(${blockTypeList.join('|')})-*`,
				components = `/**/${b}/index.js`,
				virtualComponents = `/**/${b}.index.js`;

			names = await $C([await resolve.block(), ...resolve.dependencies])
				.async
				.to(new Map())
				.reduce(async (map, root) => {
					const src = [
						path.join(root, components),
						path.join(root, virtualComponents)
					];

					if (!map.has(src)) {
						map.set(
							src,

							await $C(vinyl.src(src, {read: false}))
								.async
								.to([])
								.map((file) => {
									file.base = root;
									return [path.dirname(file.relative), file.basename];
								})
						);
					}

					return map;
				});
		}

		const
			set = Object.create(null);

		const blocks = await $C(names)
			.parallel()
			.to([])
			.reduce(async (res, el) => {
				const
					i = res.push([]) - 1;

				await $C([].concat(el || [])).async.forEach(async ([folder, name], j) => {
					if (set[folder] && set[folder] === name) {
						return;
					}

					set[folder] = name;

					const
						block = await this.get(folder, name);

					if (block) {
						res[i][j] = block;
					}
				});

				return res;
			});

		const blockMap = await $C(Sugar.Array.flatten(blocks)).to(new Map()).reduce((map, decl) => {
			if (!map.has(decl.name)) {
				return map.set(decl.name, decl);
			}

			return map;
		});

		const normalizedBlockMap = await $C(blockMap).async.to(createSerializableMap()).reduce(async (map, decl, key) => {
			map.set(key, {
				...decl,

				name: decl.name,
				parent: decl.parent,
				dependencies: decl.dependencies,

				libs: decl.libs,
				resolvedLibs: await decl.getLibs({cache: blockMap}),
				resolvedOwnLibs: await decl.getLibs({onlyOwn: true, cache: blockMap}),

				type: decl.type,
				mixin: decl.mixin,

				index: await decl.index,
				logic: await decl.logic,
				styles: await decl.styles,
				tpl: await decl.tpl,
				etpl: await decl.etpl,

				__proto__: Block.prototype
			});

			return map;
		});

		const cacheData = {
			hash: projectHash,
			data: normalizedBlockMap
		};

		const jsonReplacer = (key, val) => {
			if (Sugar.Object.isString(val) && path.extname(val) !== '' && path.isAbsolute(val)) {
				return path.relative(projectCWD, val);
			}

			if (Sugar.Object.isObject(val) && val.declaration != null) {
				return normalizedBlockMap.get(key);
			}

			return val;
		};

		fs.writeFileSync(graphLockFile, JSON.stringify(cacheData, jsonReplacer, 2));

		return normalizedBlockMap;
	}

	/**
	 * Returns a path to the logic file
	 * @returns {!Promise<?string>}
	 */
	get logic() {
		return resolve.block(`${this.name}/${this.name}.logic`);
	}

	/**
	 * Returns a path to the template file
	 * @returns {!Promise<?string>}
	 */
	get tpl() {
		return resolve.block(`${this.name}/${this.name}.ss`);
	}

	/**
	 * Returns a path to the ess template file
	 * @returns {!Promise<?string>}
	 */
	get etpl() {
		return resolve.block(`${this.name}/${this.name}.ess`);
	}

	/**
	 * Returns paths to the style files
	 * @returns {!Promise<!Array<string>>}
	 */
	get styles() {
		return (async () => {
			const
				root = await resolve.block(),
				style = await resolve.block(`${this.name}/${this.name}.styl`),
				styles = [].concat(style || []);

			if (this.mixin && !isPathInside(style, root)) {
				styles.push(...await glob(path.join(root, `/**/${this.name}/${this.name}_*.styl`)));
			}

			return styles;
		})();
	}

	/**
	 * Block name
	 * @returns {string}
	 */
	get name() {
		return this.declaration.name;
	}

	/**
	 * Block type
	 * @returns {string}
	 */
	get type() {
		return this.declaration.type;
	}

	/**
	 * Block parent
	 * @returns {?string}
	 */
	get parent() {
		return this.declaration.parent;
	}

	/**
	 * Block mixin status
	 * @returns {boolean}
	 */
	get mixin() {
		return this.declaration.mixin;
	}

	/**
	 * Block dependencies
	 * @returns {!Array<string>}
	 */
	get dependencies() {
		return this.declaration.dependencies;
	}

	/**
	 * Block libraries
	 * @returns {!Array<string>}
	 */
	get libs() {
		return this.declaration.libs;
	}

	/**
	 * @param index - block index src
	 * @param declaration - block declaration
	 */
	constructor(index, declaration) {
		this.index = index;
		this.declaration = declaration;
	}

	/**
	 * Returns the block parent manifest
	 *
	 * @param {Map<string, !Block>=} [cache] - optional cache object with predefined blocks
	 * @returns {!Promise<Block>}
	 */
	async getParent({cache} = {}) {
		if (!this.parent) {
			return null;
		}

		const
			block = cache ? cache.get(this.parent) : await this.constructor.get(this.parent);

		if (!block) {
			blockNotFound(this.parent);
		}

		return block;
	}

	/**
	 * Returns manifests of all block dependencies
	 *
	 * @param {boolean=} [onlyOwn] - if true parent dependencies also included
	 * @param {Map<string, !Block>=} [cache] - optional cache object with predefined blocks
	 * @returns {!Promise<!Map<string, !Block>>}
	 */
	async getDependencies({onlyOwn, cache} = {}) {
		let
			names = this.dependencies.slice();

		if (!onlyOwn) {
			let
				parent = await this.getParent({cache});

			while (parent) {
				names = [...parent.dependencies, ...names];
				parent = await parent.getParent({cache});
			}
		}

		if (cache) {
			return $C(names).to(createSerializableMap()).reduce((map, name) => {
				const
					block = cache.get(name);

				if (!block) {
					blockNotFound(name);
				}

				return map.set(name, block);
			});
		}

		return this.constructor.getAll([...new Set(names)]);
	}

	/**
	 * Returns manifests of all block libraries
	 *
	 * @param {boolean=} [onlyOwn] - if true parent libraries also included
	 * @param {Map<string, !Block>=} [cache] - optional cache object with predefined blocks
	 * @returns {!Promise<!Set<string>>}
	 */
	async getLibs({onlyOwn, cache} = {}) {
		if (onlyOwn) {
			if ('resolvedOwnLibs' in this) {
				return this.resolvedOwnLibs;
			}

		} else if ('resolvedLibs' in this) {
			return this.resolvedLibs;
		}

		let
			libs = this.libs.slice();

		if (!onlyOwn) {
			let
				parent = await this.getParent({cache});

			while (parent) {
				libs = [...parent.libs, ...libs];
				parent = await parent.getParent({cache});
			}
		}

		return createSerializableSet(libs);
	}

	/**
	 * Returns an object with block runtime dependencies
	 *
	 * @param {Map<string, !Block>=} [cache] - optional cache object with predefined blocks
	 * @returns {!Promise<{runtime: !Map<string, !Block>, parents: !Map<string, !Block>, libs: !Set<string>}>}
	 */
	async getRuntimeDependencies({cache} = {}) {
		let
			runtime = createSerializableMap(),
			parents = createSerializableMap();

		const get = async (name, isParent) => {
			const
				block = name === this.name ? this : cache ? cache.get(name) : await this.constructor.get(name);

			if (!block) {
				blockNotFound(name);
			}

			runtime = createSerializableMap([[name, block], ...runtime.entries()]);
			if (!isParent && parents.has(name)) {
				parents.delete(name);
			}

			await $C(await block.getDependencies({onlyOwn: true, cache})).async.forEach(async (block) => {
				runtime = createSerializableMap([[block.name, block], ...runtime.entries()]);
				await get(block.name, false, runtime, parents);
			});

			const
				parentName = block.parent;

			if (parentName) {
				const
					parent = await block.getParent({cache});

				if (!runtime.has(parentName)) {
					parents = createSerializableMap([[parentName, parent], ...parents.entries()]);
					runtime = createSerializableMap([[parentName, parent], ...runtime.entries()]);
				}

				await get(parentName, true, runtime, parents);
			}
		};

		const [libs] = await Promise.all([
			this.getLibs({cache}),
			get(this.name)
		]);

		return {runtime, parents, libs};
	}
}

module.exports = Block;
<|endoftext|>


<commit_before>var util = require('util');
var EventEmitter = require('events');
util.inherits(Block, EventEmitter);

function Block(board, x = 0, y = 0) {
  this.board = board;
  this.x = x;
  this.y = y;
  this.active = true;
};

Block.prototype.inactive = function() {
  return this.active = false;
};

// check the position of the block on the board difficulty #spicy / medium

Block.prototype.blockIsAtBottomOfBoard = function() {
  return this.y + 1 > this.board.rows;
};

Block.prototype.blockIsAtLeftSideOfBoard = function() {
  return this.x - 1 < this.board.columns;
};

Block.prototype.blockIsAtRightSideOfBoard = function() {
  return this.x + 1 > this.board.columns;
};

//

// can check whether their is a block to the right/left/belown #spicy

Block.prototype.isThereABlockOnTheRight = function() {
  if (this.board.findBlockOnBoard(this.x + 1, this.y)) { return true }
};

Block.prototype.isThereABlockOnTheLeft = function() {
  if (this.board.findBlockOnBoard(this.x - 1, this.y)) { return true }
};

Block.prototype.isThereABlockBelow = function() {
  if (this.board.findBlockOnBoard(this.x, this.y + 1)) { return true }
};

//

// check whether the block can move left, right, or down on board difficulty #medium

Block.prototype.canMoveDown = function() {
  if (this.inactive){ return this.active = false }

  // check conditionals below to confirm falling block moves correctly

  if (this.blockIsAtBottomOfBoard){ return this.inactive }

  if (this.isThereABlockBelow) { return false }
};

Block.prototype.canMoveLeft = function(){

  if (this.inactive) { return this.active = false}

  // check conditionals below to confirm falling block moves correctly

  if (this.blockIsAtLeftSideOfBoard) {return this.inactive}

  if (this.isThereABlockOnTheLeft) { return false }
};

Block.prototype.canMoveRight = function(){

  if (this.inactive) { return this.active = false }

  // check conditionals below to confirm falling block moves correctly

  if (this.blockIsAtRightSideOfBoard) { return this.inactive }

  if (this.isThereABlockOnTheRight) { return false }
};

// move block on board if it meets conditionals [done in class]

Block.prototype.moveDown = function() {
  if (this.canMoveDown) { this.y++ };
  return this;
};

Block.prototype.moveRight = function() {
  if (this.canMoveRight) { this.x++ };
  return this;
};

Block.prototype.moveLeft = function() {
  if (this.canMoveLeft) { this.x-- };
  return this;
};

module.exports = Block;
<commit_msg>added difficulties<commit_after>var util = require('util');
var EventEmitter = require('events');
util.inherits(Block, EventEmitter);

function Block(board, x = 0, y = 0) {
  this.board = board;
  this.x = x;
  this.y = y;
  this.active = true;
};

Block.prototype.inactive = function() {
  return this.active = false;
};

// check the position of the block on the board difficulty #will do in class

Block.prototype.blockIsAtBottomOfBoard = function() {
  return this.y + 1 > this.board.rows;
};

Block.prototype.blockIsAtLeftSideOfBoard = function() {
  return this.x - 1 < this.board.columns;
};

Block.prototype.blockIsAtRightSideOfBoard = function() {
  return this.x + 1 > this.board.columns;
};

//

// can check whether their is a block to the right/left/belown #medium

Block.prototype.isThereABlockOnTheRight = function() {
  if (this.board.findBlockOnBoard(this.x + 1, this.y)) { return true }
};

Block.prototype.isThereABlockOnTheLeft = function() {
  if (this.board.findBlockOnBoard(this.x - 1, this.y)) { return true }
};

Block.prototype.isThereABlockBelow = function() {
  if (this.board.findBlockOnBoard(this.x, this.y + 1)) { return true }
};

//

// check whether the block can move left, right, or down on board difficulty #medium

Block.prototype.canMoveDown = function() {
  if (this.inactive){ return this.active = false }

  // check conditionals below to confirm falling block moves correctly

  if (this.blockIsAtBottomOfBoard){ return this.inactive }

  if (this.isThereABlockBelow) { return false }
};

Block.prototype.canMoveLeft = function(){

  if (this.inactive) { return this.active = false}

  // check conditionals below to confirm falling block moves correctly

  if (this.blockIsAtLeftSideOfBoard) {return this.inactive}

  if (this.isThereABlockOnTheLeft) { return false }
};

Block.prototype.canMoveRight = function(){

  if (this.inactive) { return this.active = false }

  // check conditionals below to confirm falling block moves correctly

  if (this.blockIsAtRightSideOfBoard) { return this.inactive }

  if (this.isThereABlockOnTheRight) { return false }
};

// move block on board if it meets conditionals [done in class]

Block.prototype.moveDown = function() {
  if (this.canMoveDown) { this.y++ };
  return this;
};

Block.prototype.moveRight = function() {
  if (this.canMoveRight) { this.x++ };
  return this;
};

Block.prototype.moveLeft = function() {
  if (this.canMoveLeft) { this.x-- };
  return this;
};

module.exports = Block;
<|endoftext|>


<commit_before>var Square = require('./square');
var Rook = require('./pieces/rook');
var Bishop = require('./pieces/bishop');
var Queen = require('./pieces/queen');
var Knight = require('./pieces/knight');
var King = require('./pieces/king');
var BlackPawn = require('./pieces/black-pawn');
var WhitePawn = require('./pieces/white-pawn');

function Board (context, canvas) {
  this.gameOver = false;
  this.nextTurn = false;
  this.context = context;
  this.canvas = canvas;
  this.squares = [];
};

Board.prototype.drawBoard = function () {
  for (var i = 0; i < 4; i++) {
    for (var j = 0; j < 4; j++) {
      var x = j * 200;
      var y = i * 200;
      this.context.fillStyle = "grey";
      this.context.fillRect(x + 100, y, 100, 100);
      this.context.fillRect(x, y + 100, 100, 100);
    };
  };

  for (var i = 0; i < this.squares.length; i++) {
  var context = this.context;
    var square = this.squares[i]
    if (square.piece) {
      addIcon (square, this.context)
    }
  }
};

function addIcon (square, context) {
  var icon = new Image(80, 80);
  icon.src = square.piece.src;

  icon.onload = function () {
    var x = (square.xCoordinate * 100 + 20)
    var y = (square.yCoordinate * 100 + 10)
    context.drawImage(icon, x, y);
  };
}

Board.prototype.addSquaresToBoard = function () {
  for (var i = 0; i <= 7; i++) {
    for (var j = 0; j <= 7; j++) {
      this.squares.push(new Square(this, j, i));
    };
  };
};

Board.prototype.makeBoardClickable = function () {
  var board = this;
  this.canvas.addEventListener("mousedown", doMouseDown, false);

  function doMouseDown (event) {
    var canvas_x = event.pageX;
    var canvas_y = event.pageY;

    var indexOfSquare = (Math.floor(canvas_y / 100) * 8) + Math.floor(canvas_x / 100)
    var square = board.squares[indexOfSquare]

    alert("X = " + square.xCoordinate + "Y = " + square.yCoordinate)
    // square.piece.constructor.name
  }
};

Board.prototype.addPiecesToBoard = function () {
  loadBlack(this);
  loadWhite(this);
}

function loadBlack (board) {
  board.squares[0].piece = new Rook (board.squares[0], "black");
  board.squares[1].piece = new Knight (board.squares[1], "black");
  board.squares[2].piece = new Bishop (board.squares[2], "black");
  board.squares[3].piece = new Queen (board.squares[3], "black");
  board.squares[4].piece = new King (board.squares[4], "black");
  board.squares[5].piece = new Bishop (board.squares[5], "black");
  board.squares[6].piece = new Knight (board.squares[6], "black");
  board.squares[7].piece = new Rook (board.squares[7], "black");
  for (var i = 8; i < 16; i++) {
    board.squares[i].piece = new BlackPawn (board.squares[i], "black");
  }
};

function loadWhite (board) {
  for (var i = 48; i < 56; i++) {
    board.squares[i].piece = new WhitePawn (board.squares[i], "white");
  }
  board.squares[56].piece = new Rook (board.squares[56], "white");
  board.squares[57].piece = new Knight (board.squares[57], "white");
  board.squares[58].piece = new Bishop (board.squares[58], "white");
  board.squares[59].piece = new Queen (board.squares[59], "white");
  board.squares[60].piece = new King (board.squares[60], "white");
  board.squares[61].piece = new Bishop (board.squares[61], "white");
  board.squares[62].piece = new Knight (board.squares[62], "white");
  board.squares[63].piece = new Rook (board.squares[63], "white");
};

Board.prototype.canMoveHere = function (square) {
  var pieces = [];
  for (var i = 0; i < this.squares.length; i++) {
    //filter by player turn/color eventually
    if (this.squares[i].piece) {
      pieces.push(this.squares[i].piece);
    }
  }
  return pieces;
};

module.exports = Board;
<commit_msg>Adds piece count functionality to board.<commit_after>var Square = require('./square');
var Rook = require('./pieces/rook');
var Bishop = require('./pieces/bishop');
var Queen = require('./pieces/queen');
var Knight = require('./pieces/knight');
var King = require('./pieces/king');
var BlackPawn = require('./pieces/black-pawn');
var WhitePawn = require('./pieces/white-pawn');

function Board (context, canvas) {
  this.gameOver = false;
  this.nextTurn = false;
  this.context = context;
  this.canvas = canvas;
  this.squares = [];
};

Board.prototype.drawBoard = function () {
  for (var i = 0; i < 4; i++) {
    for (var j = 0; j < 4; j++) {
      var x = j * 200;
      var y = i * 200;
      this.context.fillStyle = "grey";
      this.context.fillRect(x + 100, y, 100, 100);
      this.context.fillRect(x, y + 100, 100, 100);
    };
  };

  for (var i = 0; i < this.squares.length; i++) {
  var context = this.context;
    var square = this.squares[i]
    if (square.piece) {
      addIcon (square, this.context)
    }
  }
};

function addIcon (square, context) {
  var icon = new Image(80, 80);
  icon.src = square.piece.src;

  icon.onload = function () {
    var x = (square.xCoordinate * 100 + 20)
    var y = (square.yCoordinate * 100 + 10)
    context.drawImage(icon, x, y);
  };
}

Board.prototype.addSquaresToBoard = function () {
  for (var i = 0; i <= 7; i++) {
    for (var j = 0; j <= 7; j++) {
      this.squares.push(new Square(this, j, i));
    };
  };
};

Board.prototype.makeBoardClickable = function () {
  var board = this;
  this.canvas.addEventListener("mousedown", doMouseDown, false);

  function doMouseDown (event) {
    var canvas_x = event.pageX;
    var canvas_y = event.pageY;

    var indexOfSquare = (Math.floor(canvas_y / 100) * 8) + Math.floor(canvas_x / 100)
    var square = board.squares[indexOfSquare]

    alert("X = " + square.xCoordinate + "Y = " + square.yCoordinate)
    // square.piece.constructor.name
  }
};

Board.prototype.addPiecesToBoard = function () {
  loadBlack(this);
  loadWhite(this);
}

function loadBlack (board) {
  board.squares[0].piece = new Rook (board.squares[0], "black");
  board.squares[1].piece = new Knight (board.squares[1], "black");
  board.squares[2].piece = new Bishop (board.squares[2], "black");
  board.squares[3].piece = new Queen (board.squares[3], "black");
  board.squares[4].piece = new King (board.squares[4], "black");
  board.squares[5].piece = new Bishop (board.squares[5], "black");
  board.squares[6].piece = new Knight (board.squares[6], "black");
  board.squares[7].piece = new Rook (board.squares[7], "black");
  for (var i = 8; i < 16; i++) {
    board.squares[i].piece = new BlackPawn (board.squares[i], "black");
  }
};

function loadWhite (board) {
  for (var i = 48; i < 56; i++) {
    board.squares[i].piece = new WhitePawn (board.squares[i], "white");
  }
  board.squares[56].piece = new Rook (board.squares[56], "white");
  board.squares[57].piece = new Knight (board.squares[57], "white");
  board.squares[58].piece = new Bishop (board.squares[58], "white");
  board.squares[59].piece = new Queen (board.squares[59], "white");
  board.squares[60].piece = new King (board.squares[60], "white");
  board.squares[61].piece = new Bishop (board.squares[61], "white");
  board.squares[62].piece = new Knight (board.squares[62], "white");
  board.squares[63].piece = new Rook (board.squares[63], "white");
};

Board.prototype.canMoveHere = function (square) {
  var pieces = [];
  for (var i = 0; i < this.squares.length; i++) {
    //filter by player turn/color eventually
    if (this.squares[i].piece) {
      pieces.push(this.squares[i].piece);
    }
  }
  return pieces;
};

Board.prototype.pieceCount = function () {
  return this.squares.reduce(function (count, square) {
    if (square.piece) {
      count++;
    }
    return count;
  }, 0);
};

module.exports = Board;
<|endoftext|>


<commit_before>function invalidOpt(a) {
	return a === NULL || !$.isPlainObject(a);
}

function invalidContent(c) {
	return !$.isFunction(c) && (
		(!c && !c.attr) || c.length < 1 || ('object' === typeof c && !c.jquery && !c.then)
	);
}

// Option object sanitizer
function sanitizeOptions(opts) {
	var content, text, ajax, once;

	if(invalidOpt(opts)) { return FALSE; }

	if(invalidOpt(opts.metadata)) {
		opts.metadata = { type: opts.metadata };
	}

	if('content' in opts) {
		content = opts.content;

		if(invalidOpt(content) || content.jquery || content.done) {
			content = opts.content = {
				text: (text = invalidContent(content) ? FALSE : content)
			};
		}

		// DEPRECATED - Old content.ajax plugin functionality
		// Converts it into the proper Deferred syntax
		if('ajax' in content) {
			ajax = content.ajax;
			once = ajax && ajax.once !== FALSE;
			content.ajax = null;

			content.text = function(event, api) {
				var deferred = $.ajax(
					$.extend({}, ajax, { context: api })
				)
				.then(function(content) {
					if(once) { api.set('content.text', content); }
					return content;
				},
				function(xhr, status, error) {
					if(api.destroyed || xhr.status === 0) { return; }
					api.set('content.text', status + ': ' + error);
				});

				return !once ? deferred : (text || $(this).attr(api.options.content.attr) || 'Loading...');
			};
		}

		if('title' in content) {
			if(!invalidOpt(content.title)) {
				content.button = content.title.button;
				content.title = content.title.text;
			}

			if(invalidContent(content.title || FALSE)) {
				content.title = FALSE;
			}
		}
	}

	if('position' in opts && invalidOpt(opts.position)) {
		opts.position = { my: opts.position, at: opts.position };
	}

	if('show' in opts && invalidOpt(opts.show)) {
		opts.show = opts.show.jquery ? { target: opts.show } :
			opts.show === TRUE ? { ready: TRUE } : { event: opts.show };
	}

	if('hide' in opts && invalidOpt(opts.hide)) {
		opts.hide = opts.hide.jquery ? { target: opts.hide } : { event: opts.hide };
	}

	if('style' in opts && invalidOpt(opts.style)) {
		opts.style = { classes: opts.style };
	}

	// Sanitize plugin options
	$.each(PLUGINS, function() {
		this.sanitize && this.sanitize(opts);
	});

	return opts;
}

// Setup builtin .set() option checks
CHECKS = PROTOTYPE.checks = {
	builtin: {
		// Core checks
		'^id$': function(obj, o, v, prev) {
			var id = v === TRUE ? QTIP.nextid : v,
				new_id = NAMESPACE + '-' + id;

			if(id !== FALSE && id.length > 0 && !$('#'+new_id).length) {
				this._id = new_id;

				if(this.rendered) {
					this.tooltip[0].id = this._id;
					this.elements.content[0].id = this._id + '-content';
					this.elements.title[0].id = this._id + '-title';
				}
			}
			else { obj[o] = prev; }
		},
		'^prerender': function(obj, o, v) {
			v && !this.rendered && this.render(this.options.show.ready);
		},

		// Content checks
		'^content.text$': function(obj, o, v) {
			this._updateContent(v);
		},
		'^content.attr$': function(obj, o, v, prev) {
			if(this.options.content.text === this.target.attr(prev)) {
				this._updateContent( this.target.attr(v) );
			}
		},
		'^content.title$': function(obj, o, v) {
			// Remove title if content is null
			if(!v) { return this._removeTitle(); }

			// If title isn't already created, create it now and update
			v && !this.elements.title && this._createTitle();
			this._updateTitle(v);
		},
		'^content.button$': function(obj, o, v) {
			this._updateButton(v);
		},
		'^content.title.(text|button)$': function(obj, o, v) {
			this.set('content.'+o, v); // Backwards title.text/button compat
		},

		// Position checks
		'^position.(my|at)$': function(obj, o, v){
			'string' === typeof v && (obj[o] = new PLUGINS.Corner(v, o === 'at'));
		},
		'^position.container$': function(obj, o, v){
			this.tooltip.appendTo(v);
		},

		// Show checks
		'^show.ready$': function(obj, o, v) {
			v && (!this.rendered && this.render(TRUE) || this.toggle(TRUE));
		},

		// Style checks
		'^style.classes$': function(obj, o, v) {
			this.tooltip.attr('class',
				[ NAMESPACE, defaultClass, v, NAMESPACE + '-pos-' + this.options.position.my.abbrev() ].join(' ')
			);
		},
		'^style.width|height': function(obj, o, v) {
			this.tooltip.css(o, v);
		},
		'^style.widget|content.title': function() {
			this._setWidget();
		},
		'^style.def': function(obj, o, v) {
			this.tooltip.toggleClass(defaultClass, !!v);
		},

		// Events check
		'^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) {
			tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v);
		},

		// Properties which require event reassignment
		'^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() {
			var posOptions = this.options.position;

			// Set tracking flag
			tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse);

			// Reassign events
			this._unassignEvents();
			this._assignEvents();
		}
	}
};

// Dot notation converter
function convertNotation(options, notation) {
	var i = 0, obj, option = options,

	// Split notation into array
	levels = notation.split('.');

	// Loop through
	while( option = option[ levels[i++] ] ) {
		if(i < levels.length) { obj = option; }
	}

	return [obj || options, levels.pop()];
}

PROTOTYPE.get = function(notation) {
	if(this.destroyed) { return this; }

	var o = convertNotation(this.options, notation.toLowerCase()),
		result = o[0][ o[1] ];

	return result.precedance ? result.string() : result;
};

function setCallback(notation, args) {
	var category, rule, match;

	for(category in this.checks) {
		for(rule in this.checks[category]) {
			if(match = (new RegExp(rule, 'i')).exec(notation)) {
				args.push(match);

				if(category === 'builtin' || this.plugins[category]) {
					this.checks[category][rule].apply(
						this.plugins[category] || this, args
					);
				}
			}
		}
	}
}

var rmove = /^position\.(my|at|adjust|target|container|viewport)|style|content|show\.ready/i,
	rrender = /^prerender|show\.ready/i;

PROTOTYPE.set = function(option, value) {
	if(this.destroyed) { return this; }

	var rendered = this.rendered,
		reposition = FALSE,
		options = this.options,
		checks = this.checks,
		name;

	// Convert singular option/value pair into object form
	if('string' === typeof option) {
		name = option; option = {}; option[name] = value;
	}
	else { option = $.extend({}, option); }

	// Set all of the defined options to their new values
	$.each(option, function(notation, value) {
		if(!rendered && !rrender.test(notation)) {
			delete option[notation]; return;
		}

		// Set new obj value
		var obj = convertNotation(options, notation.toLowerCase()), previous;
		previous = obj[0][ obj[1] ];
		obj[0][ obj[1] ] = 'object' === typeof value && value.nodeType ? $(value) : value;

		// Also check if we need to reposition
		reposition = rmove.test(notation) || reposition;

		// Set the new params for the callback
		option[notation] = [obj[0], obj[1], value, previous];
	});

	// Re-sanitize options
	sanitizeOptions(options);

	/*
	 * Execute any valid callbacks for the set options
	 * Also set positioning flag so we don't get loads of redundant repositioning calls.
	 */
	this.positioning = TRUE;
	$.each(option, $.proxy(setCallback, this));
	this.positioning = FALSE;

	// Update position if needed
	if(this.rendered && this.tooltip[0].offsetWidth > 0 && reposition) {
		this.reposition( options.position.target === 'mouse' ? NULL : this.cache.event );
	}

	return this;
};<commit_msg>Use delete instead of setting to null. Fixes #505<commit_after>function invalidOpt(a) {
	return a === NULL || $.type(a) !== 'object';
}

function invalidContent(c) {
	return !( $.isFunction(c) || (c && c.attr) || c.length || ($.type(c) === 'object' && (c.jquery || c.then) ));
}

// Option object sanitizer
function sanitizeOptions(opts) {
	var content, text, ajax, once;

	if(invalidOpt(opts)) { return FALSE; }

	if(invalidOpt(opts.metadata)) {
		opts.metadata = { type: opts.metadata };
	}

	if('content' in opts) {
		content = opts.content;

		if(invalidOpt(content) || content.jquery || content.done) {
			content = opts.content = {
				text: (text = invalidContent(content) ? FALSE : content)
			};
		}

		// DEPRECATED - Old content.ajax plugin functionality
		// Converts it into the proper Deferred syntax
		if('ajax' in content) {
			ajax = content.ajax;
			once = ajax && ajax.once !== FALSE;
			delete content.ajax;

			content.text = function(event, api) {
				var deferred = $.ajax(
					$.extend({}, ajax, { context: api })
				)
				.then(function(content) {
					if(once) { api.set('content.text', content); }
					console.log(once, api.get('content.text') );
					return content;
				},
				function(xhr, status, error) {
					if(api.destroyed || xhr.status === 0) { return; }
					api.set('content.text', status + ': ' + error);
				});

				return !once ? deferred : (text || $(this).attr(api.options.content.attr) || 'Loading...');
			};
		}

		if('title' in content) {
			if(!invalidOpt(content.title)) {
				content.button = content.title.button;
				content.title = content.title.text;
			}

			if(invalidContent(content.title || FALSE)) {
				content.title = FALSE;
			}
		}
	}

	if('position' in opts && invalidOpt(opts.position)) {
		opts.position = { my: opts.position, at: opts.position };
	}

	if('show' in opts && invalidOpt(opts.show)) {
		opts.show = opts.show.jquery ? { target: opts.show } :
			opts.show === TRUE ? { ready: TRUE } : { event: opts.show };
	}

	if('hide' in opts && invalidOpt(opts.hide)) {
		opts.hide = opts.hide.jquery ? { target: opts.hide } : { event: opts.hide };
	}

	if('style' in opts && invalidOpt(opts.style)) {
		opts.style = { classes: opts.style };
	}

	// Sanitize plugin options
	$.each(PLUGINS, function() {
		this.sanitize && this.sanitize(opts);
	});

	return opts;
}

// Setup builtin .set() option checks
CHECKS = PROTOTYPE.checks = {
	builtin: {
		// Core checks
		'^id$': function(obj, o, v, prev) {
			var id = v === TRUE ? QTIP.nextid : v,
				new_id = NAMESPACE + '-' + id;

			if(id !== FALSE && id.length > 0 && !$('#'+new_id).length) {
				this._id = new_id;

				if(this.rendered) {
					this.tooltip[0].id = this._id;
					this.elements.content[0].id = this._id + '-content';
					this.elements.title[0].id = this._id + '-title';
				}
			}
			else { obj[o] = prev; }
		},
		'^prerender': function(obj, o, v) {
			v && !this.rendered && this.render(this.options.show.ready);
		},

		// Content checks
		'^content.text$': function(obj, o, v) {
			this._updateContent(v);
		},
		'^content.attr$': function(obj, o, v, prev) {
			if(this.options.content.text === this.target.attr(prev)) {
				this._updateContent( this.target.attr(v) );
			}
		},
		'^content.title$': function(obj, o, v) {
			// Remove title if content is null
			if(!v) { return this._removeTitle(); }

			// If title isn't already created, create it now and update
			v && !this.elements.title && this._createTitle();
			this._updateTitle(v);
		},
		'^content.button$': function(obj, o, v) {
			this._updateButton(v);
		},
		'^content.title.(text|button)$': function(obj, o, v) {
			this.set('content.'+o, v); // Backwards title.text/button compat
		},

		// Position checks
		'^position.(my|at)$': function(obj, o, v){
			'string' === typeof v && (obj[o] = new PLUGINS.Corner(v, o === 'at'));
		},
		'^position.container$': function(obj, o, v){
			this.tooltip.appendTo(v);
		},

		// Show checks
		'^show.ready$': function(obj, o, v) {
			v && (!this.rendered && this.render(TRUE) || this.toggle(TRUE));
		},

		// Style checks
		'^style.classes$': function(obj, o, v) {
			this.tooltip.attr('class',
				[ NAMESPACE, defaultClass, v, NAMESPACE + '-pos-' + this.options.position.my.abbrev() ].join(' ')
			);
		},
		'^style.width|height': function(obj, o, v) {
			this.tooltip.css(o, v);
		},
		'^style.widget|content.title': function() {
			this._setWidget();
		},
		'^style.def': function(obj, o, v) {
			this.tooltip.toggleClass(defaultClass, !!v);
		},

		// Events check
		'^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) {
			tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v);
		},

		// Properties which require event reassignment
		'^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() {
			var posOptions = this.options.position;

			// Set tracking flag
			tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse);

			// Reassign events
			this._unassignEvents();
			this._assignEvents();
		}
	}
};

// Dot notation converter
function convertNotation(options, notation) {
	var i = 0, obj, option = options,

	// Split notation into array
	levels = notation.split('.');

	// Loop through
	while( option = option[ levels[i++] ] ) {
		if(i < levels.length) { obj = option; }
	}

	return [obj || options, levels.pop()];
}

PROTOTYPE.get = function(notation) {
	if(this.destroyed) { return this; }

	var o = convertNotation(this.options, notation.toLowerCase()),
		result = o[0][ o[1] ];

	return result.precedance ? result.string() : result;
};

function setCallback(notation, args) {
	var category, rule, match;

	for(category in this.checks) {
		for(rule in this.checks[category]) {
			if(match = (new RegExp(rule, 'i')).exec(notation)) {
				args.push(match);

				if(category === 'builtin' || this.plugins[category]) {
					this.checks[category][rule].apply(
						this.plugins[category] || this, args
					);
				}
			}
		}
	}
}

var rmove = /^position\.(my|at|adjust|target|container|viewport)|style|content|show\.ready/i,
	rrender = /^prerender|show\.ready/i;

PROTOTYPE.set = function(option, value) {
	if(this.destroyed) { return this; }

	var rendered = this.rendered,
		reposition = FALSE,
		options = this.options,
		checks = this.checks,
		name;

	// Convert singular option/value pair into object form
	if('string' === typeof option) {
		name = option; option = {}; option[name] = value;
	}
	else { option = $.extend({}, option); }

	// Set all of the defined options to their new values
	$.each(option, function(notation, value) {
		if(!rendered && !rrender.test(notation)) {
			delete option[notation]; return;
		}

		// Set new obj value
		var obj = convertNotation(options, notation.toLowerCase()), previous;
		previous = obj[0][ obj[1] ];
		obj[0][ obj[1] ] = value && value.nodeType ? $(value) : value;

		// Also check if we need to reposition
		reposition = rmove.test(notation) || reposition;

		// Set the new params for the callback
		option[notation] = [obj[0], obj[1], value, previous];
	});

	console.log(options.content.text, option);

	// Re-sanitize options
	sanitizeOptions(options);

	console.log(options.content.text, option);

	/*
	 * Execute any valid callbacks for the set options
	 * Also set positioning flag so we don't get loads of redundant repositioning calls.
	 */
	this.positioning = TRUE;
	$.each(option, $.proxy(setCallback, this));
	this.positioning = FALSE;

	// Update position if needed
	if(this.rendered && this.tooltip[0].offsetWidth > 0 && reposition) {
		this.reposition( options.position.target === 'mouse' ? NULL : this.cache.event );
	}

	return this;
};<|endoftext|>


<commit_before>/**
 * @class Oskari.mapframework.ui.module.common.mapmodule.DrawPlugin
 */
Oskari.clazz.define('Oskari.mapframework.ui.module.common.mapmodule.DrawPlugin', function (config) {
    this.mapModule = null;
    this.pluginName = null;
    this._sandbox = null;
    this._map = null;
    this.drawControls = null;
    this.drawLayer = null;
    this.editMode = false;
    this.currentDrawMode = null;
    this.prefix = "DrawPlugin.";
    this.creatorId = undefined;

    if (config && config.id) {
        // Note that the events and requests need to match the configured
        // prefix based on the id!
        this.prefix = config.id + ".";
        this.creatorId = config.id;
    }
    // graphicFill, instance
    if (config && config.graphicFill) {
        this.graphicFill = config.graphicFill;
    }
    this.multipart = (config && config.multipart === true);
}, {
    __name: 'DrawPlugin',

    getName: function () {
        return this.prefix + this.pluginName;
    },
    getMapModule: function () {
        return this.mapModule;
    },
    setMapModule: function (mapModule) {
        this.mapModule = mapModule;

        if (mapModule) {
            this._map = mapModule.getMap();
            this.pluginName = mapModule.getName() + this.__name;
        } else {
            this._map = null;
            this.pluginName = null;
        }
    },
    /**
     * Enables the draw control for given params.drawMode.
     * Clears the layer of any previously drawn features.
     * TODO: draws the given params.geometry with params.style
     * @param params includes drawMode, geometry and style
     * @method
     */
    startDrawing: function (params) {
        if (params.isModify) {
            // preselect it for modification
            this.modifyControls.select.select(this.drawLayer.features[0]);
        } else {
            // remove possible old drawing
            this.drawLayer.destroyFeatures();
            if (params.geometry) {
                // sent existing geometry == edit mode
                this.editMode = true;
                // add feature to draw layer
                var features = [new OpenLayers.Feature.Vector(params.geometry)];
                this.drawLayer.addFeatures(features);
                // preselect it for modification
                this.modifyControls.select.select(this.drawLayer.features[0]);
            } else {
                // otherwise activate requested draw control for new geometry
                this.editMode = false;
                this.toggleControl(params.drawMode);
            }
        }

    },
    /**
     * Disables all draw controls and
     * clears the layer of any drawn features
     * @method
     */
    stopDrawing: function () {
        // disable all draw controls
        this.toggleControl();
        // clear drawing
        if (this.drawLayer) this.drawLayer.destroyFeatures();
    },

    forceFinishDraw: function () {
        try {
            //needed when preparing unfinished objects but causes unwanted features into the layer:
            //this.drawControls[this.currentDrawMode].finishSketch();
            this.finishedDrawing(true);
        } catch (error) {
            // happens when the sketch isn't even started -> reset state
            this.stopDrawing();
            var evtBuilder = this._sandbox.getEventBuilder('DrawPlugin.SelectedDrawingEvent');
            var event = evtBuilder(null, null, this.creatorId);
            this._sandbox.notifyAll(event);
        }
    },

    /**
     * Called when drawing is finished.
     * Disables all draw controls and
     * sends a '[this.prefix] + FinishedDrawingEvent' with the drawn the geometry.
     * @method
     */
    finishedDrawing: function (isForced) {
        if (!this.multipart || isForced) {
            // not a multipart, stop editing
            var activeControls = this._getActiveDrawControls(),
                i,
                components;
            for (i = 0; i < activeControls.length; i++) {
                // only lines and polygons have the finishGeometry function
                if (typeof this.drawControls[activeControls[i]].handler.finishGeometry === typeof Function) {
                    // No need to finish geometry if already finished
                    switch (activeControls[i]) {
                    case "line":
                        if (this.drawControls.line.handler.line.geometry.components.length < 2) {
                            continue;
                        }
                        break;
                    case "area":
                        components = this.drawControls.area.handler.polygon.geometry.components;
                        if (components[components.length - 1].components.length < 3) {
                            continue;
                        }
                        break;
                    }
                    this.drawControls[activeControls[i]].handler.finishGeometry();
                }
            }
            this.toggleControl();
        }

        if (!this.editMode) {
            // programmatically select the drawn feature ("not really supported by openlayers")
            // http://lists.osgeo.org/pipermail/openlayers-users/2009-February/010601.html
            var lastIndex = this.drawLayer.features.length - 1;
            this.modifyControls.select.select(this.drawLayer.features[lastIndex]);
        }

        var evtBuilder, event;
        if (!this.multipart || isForced) {
            evtBuilder = this._sandbox.getEventBuilder('DrawPlugin.FinishedDrawingEvent');
            event = evtBuilder(this.getDrawing(), this.editMode, this.creatorId);
            this._sandbox.notifyAll(event);
        } else {
            evtBuilder = this._sandbox.getEventBuilder('DrawPlugin.AddedFeatureEvent');
            event = evtBuilder(this.getDrawing(), this.currentDrawMode, this.creatorId);
            this._sandbox.notifyAll(event);
        }
    },
    /**
     * Enables the given draw control
     * Disables all the other draw controls
     * @param drawMode draw control to activate (if undefined, disables all
     * controls)
     * @method
     */
    toggleControl: function (drawMode) {
        this.currentDrawMode = drawMode;
        var key, control, activeDrawing, event;

        for (key in this.drawControls) {
            if (this.drawControls.hasOwnProperty(key)) {
                control = this.drawControls[key];
                if (drawMode === key) {
                    control.activate();
                } else {
                    control.deactivate();
                }
            }
        }
    },
    /**
     * Initializes the plugin:
     * - layer that is used for drawing
     * - drawControls
     * - registers for listening to requests
     * @param sandbox reference to Oskari sandbox
     * @method
     */
    init: function (sandbox) {
        var me = this;
        this.requestHandlers = {
            startDrawingHandler: Oskari.clazz.create('Oskari.mapframework.ui.module.common.mapmodule.DrawPlugin.request.StartDrawingRequestPluginHandler', sandbox, me),
            stopDrawingHandler: Oskari.clazz.create('Oskari.mapframework.ui.module.common.mapmodule.DrawPlugin.request.StopDrawingRequestPluginHandler', sandbox, me),
            getGeometryHandler: Oskari.clazz.create('Oskari.mapframework.ui.module.common.mapmodule.DrawPlugin.request.GetGeometryRequestPluginHandler', sandbox, me)
        };

        this.drawLayer = new OpenLayers.Layer.Vector(this.prefix + "DrawLayer", {
            /*style: {
             strokeColor: "#ff00ff",
             strokeWidth: 3,
             fillOpacity: 0,
             cursor: "pointer"
             },*/
            eventListeners: {
                "featuresadded": function (layer) {
                    // send an event that the drawing has been completed
                    me.finishedDrawing();
                },
                'vertexmodified': function (event) {
                    me._sendActiveGeometry(me.getDrawing());
                }
            }
        });

        this.drawControls = {
            point: new OpenLayers.Control.DrawFeature(me.drawLayer,
                OpenLayers.Handler.Point),
            line: new OpenLayers.Control.DrawFeature(me.drawLayer,
                OpenLayers.Handler.Path, {
                    callbacks: {
                        modify: function (geom, feature) {
                            me._sendActiveGeometry(me.getActiveDrawing(feature.geometry), 'line');
                        }
                    }
                }),
            area: new OpenLayers.Control.DrawFeature(me.drawLayer,
                OpenLayers.Handler.Polygon, {
                    handlerOptions: {
                        holeModifier: "altKey"
                    },
                    callbacks: {
                        modify: function (geom, feature) {
                            me._sendActiveGeometry(me.getActiveDrawing(feature.geometry), 'area');
                        }
                    }
                }),
            /*cut : new OpenLayers.Control.DrawFeature(me.drawLayer,
                                                      OpenLayers.Handler.Polygon,
                                                      {handlerOptions:{drawingHole: true}}),*/
            box: new OpenLayers.Control.DrawFeature(me.drawLayer,
                OpenLayers.Handler.RegularPolygon, {
                    handlerOptions: {
                        sides: 4,
                        irregular: true
                    }
                })
        };

        if (this.graphicFill !== null && this.graphicFill !== undefined) {
            var str = this.graphicFill,
                format = new OpenLayers.Format.SLD(),
                obj = format.read(str),
                p;
            if (obj && obj.namedLayers) {
                for (p in obj.namedLayers) {
                    if (obj.namedLayers.hasOwnProperty(p)) {
                        this.drawLayer.styleMap.styles["default"] = obj.namedLayers[p].userStyles[0];
                        this.drawLayer.redraw();
                        break;
                    }
                }
            }
        }

        // doesn't really need to be in array, but lets keep it for future development
        this.modifyControls = {
            modify: new OpenLayers.Control.ModifyFeature(me.drawLayer, {
                standalone: true
            })
        };
        this.modifyControls.select = new OpenLayers.Control.SelectFeature(me.drawLayer, {
            onBeforeSelect: this.modifyControls.modify.beforeSelectFeature,
            onSelect: this.modifyControls.modify.selectFeature,
            onUnselect: this.modifyControls.modify.unselectFeature,
            scope: this.modifyControls.modify
        });

        this._map.addLayers([me.drawLayer]);
        var key;
        for (key in this.drawControls) {
            if (this.drawControls.hasOwnProperty(key)) {
                this._map.addControl(this.drawControls[key]);
            }
        }
        for (key in this.modifyControls) {
            if (this.modifyControls.hasOwnProperty(key)) {
                this._map.addControl(this.modifyControls[key]);
            }
        }
        // no harm in activating straight away
        this.modifyControls.modify.activate();
    },
    /**
     * Returns the drawn geometry from the draw layer
     * @method
     */
    getDrawing: function () {
        if (this.drawLayer.features.length === 0) {
            return null;
        }
        var featClass = this.drawLayer.features[0].geometry.CLASS_NAME;
        if ((featClass === "OpenLayers.Geometry.MultiPoint") ||
            (featClass === "OpenLayers.Geometry.MultiLineString") ||
            (featClass === "OpenLayers.Geometry.MultiPolygon")) {
            return this.drawLayer.features[0].geometry;
        }
        var drawing = null,
            components = [],
            i,
            geom;
        for (i = 0; i < this.drawLayer.features.length; i++) {
            geom = this.drawLayer.features[i].geometry;
            // Remove unfinished polygons
            if (geom.CLASS_NAME === 'OpenLayers.Geometry.Polygon' && geom.components.length && geom.components[0].CLASS_NAME === 'OpenLayers.Geometry.LinearRing' && geom.components[0].components.length < 4) {
                // Unfinished poly, ignore.
            } else {
                components.push(geom);
            }
        }
        switch (featClass) {
        case "OpenLayers.Geometry.Point":
            drawing = new OpenLayers.Geometry.MultiPoint(components);
            break;
        case "OpenLayers.Geometry.LineString":
            drawing = new OpenLayers.Geometry.MultiLineString(components);
            break;
        case "OpenLayers.Geometry.Polygon":
            drawing = new OpenLayers.Geometry.MultiPolygon(components);
            break;
        }
        return drawing;
    },

    /**
     * Clones the drawing on the map and adds the geometry
     * currently being drawn to it.
     *
     * @method getActiveDrawing
     * @param  {OpenLayers.Geometry} geometry
     * @return {OpenLayers.Geometry}
     */
    getActiveDrawing: function (geometry) {
        var prevGeom = this.getDrawing(),
            composedGeom;

        if (prevGeom !== null && prevGeom !== undefined) {
            composedGeom = prevGeom.clone();
            composedGeom.addComponent(geometry);
            return composedGeom;
        }
        return geometry;
    },

    /**
     * Returns active draw control names
     * @method
     */
    _getActiveDrawControls: function () {
        var activeDrawControls = [],
            drawControl;
        for (drawControl in this.drawControls) {
            if (this.drawControls.hasOwnProperty(drawControl)) {
                if (this.drawControls[drawControl].active) {
                    activeDrawControls.push(drawControl);
                }
            }
        }
        return activeDrawControls;
    },

    _sendActiveGeometry: function (geometry, drawMode) {
        var eventBuilder = this._sandbox.getEventBuilder('DrawPlugin.ActiveDrawingEvent'),
            event,
            featClass;

        if (drawMode === null || drawMode === undefined) {
            featClass = geometry.CLASS_NAME;
            switch (featClass) {
            case "OpenLayers.Geometry.LineString":
            case "OpenLayers.Geometry.MultiLineString":
                drawMode = 'line';
                break;
            case "OpenLayers.Geometry.Polygon":
            case "OpenLayers.Geometry.MultiPolygon":
                drawMode = 'area';
                break;
            default:
                return;
            }
        }

        if (eventBuilder) {
            event = eventBuilder(geometry, drawMode, this.creatorId);
            this._sandbox.notifyAll(event);
        }
    },

    register: function () {

    },
    unregister: function () {},
    startPlugin: function (sandbox) {
        this._sandbox = sandbox;

        sandbox.register(this);
        sandbox.addRequestHandler('DrawPlugin.StartDrawingRequest', this.requestHandlers.startDrawingHandler);
        sandbox.addRequestHandler('DrawPlugin.StopDrawingRequest', this.requestHandlers.stopDrawingHandler);
        sandbox.addRequestHandler('DrawPlugin.GetGeometryRequest', this.requestHandlers.getGeometryHandler);

    },
    stopPlugin: function (sandbox) {
        this.toggleControl();

        if (this.drawLayer) {
            this.drawLayer.destroyFeatures();
            this._map.removeLayer(this.drawLayer);
            this.drawLayer = undefined;
        }

        sandbox.removeRequestHandler('DrawPlugin.StartDrawingRequest', this.requestHandlers.startDrawingHandler);
        sandbox.removeRequestHandler('DrawPlugin.StopDrawingRequest', this.requestHandlers.stopDrawingHandler);
        sandbox.removeRequestHandler('DrawPlugin.GetGeometryRequest', this.requestHandlers.getGeometryHandler);
        sandbox.unregister(this);

        this._sandbox = null;
    },
    /* @method start
     * called from sandbox
     */
    start: function (sandbox) {},
    /**
     * @method stop
     * called from sandbox
     *
     */
    stop: function (sandbox) {}
}, {
    'protocol': ["Oskari.mapframework.module.Module", "Oskari.mapframework.ui.module.common.mapmodule.Plugin"]
});
<commit_msg>Drawplugin now tries to bring drawlayer on top of the map so other controls dont interfere drawings<commit_after>/**
 * @class Oskari.mapframework.ui.module.common.mapmodule.DrawPlugin
 */
Oskari.clazz.define('Oskari.mapframework.ui.module.common.mapmodule.DrawPlugin', function (config) {
    this.mapModule = null;
    this.pluginName = null;
    this._sandbox = null;
    this._map = null;
    this.drawControls = null;
    this.drawLayer = null;
    this.editMode = false;
    this.currentDrawMode = null;
    this.prefix = "DrawPlugin.";
    this.creatorId = undefined;

    if (config && config.id) {
        // Note that the events and requests need to match the configured
        // prefix based on the id!
        this.prefix = config.id + ".";
        this.creatorId = config.id;
    }
    // graphicFill, instance
    if (config && config.graphicFill) {
        this.graphicFill = config.graphicFill;
    }
    this.multipart = (config && config.multipart === true);
}, {
    __name: 'DrawPlugin',

    getName: function () {
        return this.prefix + this.pluginName;
    },
    getMapModule: function () {
        return this.mapModule;
    },
    setMapModule: function (mapModule) {
        this.mapModule = mapModule;

        if (mapModule) {
            this._map = mapModule.getMap();
            this.pluginName = mapModule.getName() + this.__name;
        } else {
            this._map = null;
            this.pluginName = null;
        }
    },
    /**
     * Enables the draw control for given params.drawMode.
     * Clears the layer of any previously drawn features.
     * TODO: draws the given params.geometry with params.style
     * @param params includes drawMode, geometry and style
     * @method
     */
    startDrawing: function (params) {
        // make the drawlayer go on top, just using 1000 as delta for now (quickfix)
        // TODO: add a "bringToTop" function to mapmodule and add proper layer indexing
        this._map.raiseLayer(this.drawLayer, 1000);
        if (params.isModify) {
            // preselect it for modification
            this.modifyControls.select.select(this.drawLayer.features[0]);
        } else {
            // remove possible old drawing
            this.drawLayer.destroyFeatures();
            if (params.geometry) {
                // sent existing geometry == edit mode
                this.editMode = true;
                // add feature to draw layer
                var features = [new OpenLayers.Feature.Vector(params.geometry)];
                this.drawLayer.addFeatures(features);
                // preselect it for modification
                this.modifyControls.select.select(this.drawLayer.features[0]);
            } else {
                // otherwise activate requested draw control for new geometry
                this.editMode = false;
                this.toggleControl(params.drawMode);
            }
        }

    },
    /**
     * Disables all draw controls and
     * clears the layer of any drawn features
     * @method
     */
    stopDrawing: function () {
        // disable all draw controls
        this.toggleControl();
        // clear drawing
        if (this.drawLayer) this.drawLayer.destroyFeatures();
    },

    forceFinishDraw: function () {
        try {
            //needed when preparing unfinished objects but causes unwanted features into the layer:
            //this.drawControls[this.currentDrawMode].finishSketch();
            this.finishedDrawing(true);
        } catch (error) {
            // happens when the sketch isn't even started -> reset state
            this.stopDrawing();
            var evtBuilder = this._sandbox.getEventBuilder('DrawPlugin.SelectedDrawingEvent');
            var event = evtBuilder(null, null, this.creatorId);
            this._sandbox.notifyAll(event);
        }
    },

    /**
     * Called when drawing is finished.
     * Disables all draw controls and
     * sends a '[this.prefix] + FinishedDrawingEvent' with the drawn the geometry.
     * @method
     */
    finishedDrawing: function (isForced) {
        if (!this.multipart || isForced) {
            // not a multipart, stop editing
            var activeControls = this._getActiveDrawControls(),
                i,
                components;
            for (i = 0; i < activeControls.length; i++) {
                // only lines and polygons have the finishGeometry function
                if (typeof this.drawControls[activeControls[i]].handler.finishGeometry === typeof Function) {
                    // No need to finish geometry if already finished
                    switch (activeControls[i]) {
                    case "line":
                        if (this.drawControls.line.handler.line.geometry.components.length < 2) {
                            continue;
                        }
                        break;
                    case "area":
                        components = this.drawControls.area.handler.polygon.geometry.components;
                        if (components[components.length - 1].components.length < 3) {
                            continue;
                        }
                        break;
                    }
                    this.drawControls[activeControls[i]].handler.finishGeometry();
                }
            }
            this.toggleControl();
        }

        if (!this.editMode) {
            // programmatically select the drawn feature ("not really supported by openlayers")
            // http://lists.osgeo.org/pipermail/openlayers-users/2009-February/010601.html
            var lastIndex = this.drawLayer.features.length - 1;
            this.modifyControls.select.select(this.drawLayer.features[lastIndex]);
        }

        var evtBuilder, event;
        if (!this.multipart || isForced) {
            evtBuilder = this._sandbox.getEventBuilder('DrawPlugin.FinishedDrawingEvent');
            event = evtBuilder(this.getDrawing(), this.editMode, this.creatorId);
            this._sandbox.notifyAll(event);
        } else {
            evtBuilder = this._sandbox.getEventBuilder('DrawPlugin.AddedFeatureEvent');
            event = evtBuilder(this.getDrawing(), this.currentDrawMode, this.creatorId);
            this._sandbox.notifyAll(event);
        }
    },
    /**
     * Enables the given draw control
     * Disables all the other draw controls
     * @param drawMode draw control to activate (if undefined, disables all
     * controls)
     * @method
     */
    toggleControl: function (drawMode) {
        this.currentDrawMode = drawMode;
        var key, control, activeDrawing, event;

        for (key in this.drawControls) {
            if (this.drawControls.hasOwnProperty(key)) {
                control = this.drawControls[key];
                if (drawMode === key) {
                    control.activate();
                } else {
                    control.deactivate();
                }
            }
        }
    },
    /**
     * Initializes the plugin:
     * - layer that is used for drawing
     * - drawControls
     * - registers for listening to requests
     * @param sandbox reference to Oskari sandbox
     * @method
     */
    init: function (sandbox) {
        var me = this;
        this.requestHandlers = {
            startDrawingHandler: Oskari.clazz.create('Oskari.mapframework.ui.module.common.mapmodule.DrawPlugin.request.StartDrawingRequestPluginHandler', sandbox, me),
            stopDrawingHandler: Oskari.clazz.create('Oskari.mapframework.ui.module.common.mapmodule.DrawPlugin.request.StopDrawingRequestPluginHandler', sandbox, me),
            getGeometryHandler: Oskari.clazz.create('Oskari.mapframework.ui.module.common.mapmodule.DrawPlugin.request.GetGeometryRequestPluginHandler', sandbox, me)
        };

        this.drawLayer = new OpenLayers.Layer.Vector(this.prefix + "DrawLayer", {
            /*style: {
             strokeColor: "#ff00ff",
             strokeWidth: 3,
             fillOpacity: 0,
             cursor: "pointer"
             },*/
            eventListeners: {
                "featuresadded": function (layer) {
                    // send an event that the drawing has been completed
                    me.finishedDrawing();
                },
                'vertexmodified': function (event) {
                    me._sendActiveGeometry(me.getDrawing());
                }
            }
        });

        this.drawControls = {
            point: new OpenLayers.Control.DrawFeature(me.drawLayer,
                OpenLayers.Handler.Point),
            line: new OpenLayers.Control.DrawFeature(me.drawLayer,
                OpenLayers.Handler.Path, {
                    callbacks: {
                        modify: function (geom, feature) {
                            me._sendActiveGeometry(me.getActiveDrawing(feature.geometry), 'line');
                        }
                    }
                }),
            area: new OpenLayers.Control.DrawFeature(me.drawLayer,
                OpenLayers.Handler.Polygon, {
                    handlerOptions: {
                        holeModifier: "altKey"
                    },
                    callbacks: {
                        modify: function (geom, feature) {
                            me._sendActiveGeometry(me.getActiveDrawing(feature.geometry), 'area');
                        }
                    }
                }),
            /*cut : new OpenLayers.Control.DrawFeature(me.drawLayer,
                                                      OpenLayers.Handler.Polygon,
                                                      {handlerOptions:{drawingHole: true}}),*/
            box: new OpenLayers.Control.DrawFeature(me.drawLayer,
                OpenLayers.Handler.RegularPolygon, {
                    handlerOptions: {
                        sides: 4,
                        irregular: true
                    }
                })
        };

        if (this.graphicFill !== null && this.graphicFill !== undefined) {
            var str = this.graphicFill,
                format = new OpenLayers.Format.SLD(),
                obj = format.read(str),
                p;
            if (obj && obj.namedLayers) {
                for (p in obj.namedLayers) {
                    if (obj.namedLayers.hasOwnProperty(p)) {
                        this.drawLayer.styleMap.styles["default"] = obj.namedLayers[p].userStyles[0];
                        this.drawLayer.redraw();
                        break;
                    }
                }
            }
        }

        // doesn't really need to be in array, but lets keep it for future development
        this.modifyControls = {
            modify: new OpenLayers.Control.ModifyFeature(me.drawLayer, {
                standalone: true
            })
        };
        this.modifyControls.select = new OpenLayers.Control.SelectFeature(me.drawLayer, {
            onBeforeSelect: this.modifyControls.modify.beforeSelectFeature,
            onSelect: this.modifyControls.modify.selectFeature,
            onUnselect: this.modifyControls.modify.unselectFeature,
            scope: this.modifyControls.modify
        });

        this._map.addLayers([me.drawLayer]);
        var key;
        for (key in this.drawControls) {
            if (this.drawControls.hasOwnProperty(key)) {
                this._map.addControl(this.drawControls[key]);
            }
        }
        for (key in this.modifyControls) {
            if (this.modifyControls.hasOwnProperty(key)) {
                this._map.addControl(this.modifyControls[key]);
            }
        }
        // no harm in activating straight away
        this.modifyControls.modify.activate();
    },
    /**
     * Returns the drawn geometry from the draw layer
     * @method
     */
    getDrawing: function () {
        if (this.drawLayer.features.length === 0) {
            return null;
        }
        var featClass = this.drawLayer.features[0].geometry.CLASS_NAME;
        if ((featClass === "OpenLayers.Geometry.MultiPoint") ||
            (featClass === "OpenLayers.Geometry.MultiLineString") ||
            (featClass === "OpenLayers.Geometry.MultiPolygon")) {
            return this.drawLayer.features[0].geometry;
        }
        var drawing = null,
            components = [],
            i,
            geom;
        for (i = 0; i < this.drawLayer.features.length; i++) {
            geom = this.drawLayer.features[i].geometry;
            // Remove unfinished polygons
            if (geom.CLASS_NAME === 'OpenLayers.Geometry.Polygon' && geom.components.length && geom.components[0].CLASS_NAME === 'OpenLayers.Geometry.LinearRing' && geom.components[0].components.length < 4) {
                // Unfinished poly, ignore.
            } else {
                components.push(geom);
            }
        }
        switch (featClass) {
        case "OpenLayers.Geometry.Point":
            drawing = new OpenLayers.Geometry.MultiPoint(components);
            break;
        case "OpenLayers.Geometry.LineString":
            drawing = new OpenLayers.Geometry.MultiLineString(components);
            break;
        case "OpenLayers.Geometry.Polygon":
            drawing = new OpenLayers.Geometry.MultiPolygon(components);
            break;
        }
        return drawing;
    },

    /**
     * Clones the drawing on the map and adds the geometry
     * currently being drawn to it.
     *
     * @method getActiveDrawing
     * @param  {OpenLayers.Geometry} geometry
     * @return {OpenLayers.Geometry}
     */
    getActiveDrawing: function (geometry) {
        var prevGeom = this.getDrawing(),
            composedGeom;

        if (prevGeom !== null && prevGeom !== undefined) {
            composedGeom = prevGeom.clone();
            composedGeom.addComponent(geometry);
            return composedGeom;
        }
        return geometry;
    },

    /**
     * Returns active draw control names
     * @method
     */
    _getActiveDrawControls: function () {
        var activeDrawControls = [],
            drawControl;
        for (drawControl in this.drawControls) {
            if (this.drawControls.hasOwnProperty(drawControl)) {
                if (this.drawControls[drawControl].active) {
                    activeDrawControls.push(drawControl);
                }
            }
        }
        return activeDrawControls;
    },

    _sendActiveGeometry: function (geometry, drawMode) {
        var eventBuilder = this._sandbox.getEventBuilder('DrawPlugin.ActiveDrawingEvent'),
            event,
            featClass;

        if (drawMode === null || drawMode === undefined) {
            featClass = geometry.CLASS_NAME;
            switch (featClass) {
            case "OpenLayers.Geometry.LineString":
            case "OpenLayers.Geometry.MultiLineString":
                drawMode = 'line';
                break;
            case "OpenLayers.Geometry.Polygon":
            case "OpenLayers.Geometry.MultiPolygon":
                drawMode = 'area';
                break;
            default:
                return;
            }
        }

        if (eventBuilder) {
            event = eventBuilder(geometry, drawMode, this.creatorId);
            this._sandbox.notifyAll(event);
        }
    },

    register: function () {

    },
    unregister: function () {},
    startPlugin: function (sandbox) {
        this._sandbox = sandbox;

        sandbox.register(this);
        sandbox.addRequestHandler('DrawPlugin.StartDrawingRequest', this.requestHandlers.startDrawingHandler);
        sandbox.addRequestHandler('DrawPlugin.StopDrawingRequest', this.requestHandlers.stopDrawingHandler);
        sandbox.addRequestHandler('DrawPlugin.GetGeometryRequest', this.requestHandlers.getGeometryHandler);

    },
    stopPlugin: function (sandbox) {
        this.toggleControl();

        if (this.drawLayer) {
            this.drawLayer.destroyFeatures();
            this._map.removeLayer(this.drawLayer);
            this.drawLayer = undefined;
        }

        sandbox.removeRequestHandler('DrawPlugin.StartDrawingRequest', this.requestHandlers.startDrawingHandler);
        sandbox.removeRequestHandler('DrawPlugin.StopDrawingRequest', this.requestHandlers.stopDrawingHandler);
        sandbox.removeRequestHandler('DrawPlugin.GetGeometryRequest', this.requestHandlers.getGeometryHandler);
        sandbox.unregister(this);

        this._sandbox = null;
    },
    /* @method start
     * called from sandbox
     */
    start: function (sandbox) {},
    /**
     * @method stop
     * called from sandbox
     *
     */
    stop: function (sandbox) {}
}, {
    'protocol': ["Oskari.mapframework.module.Module", "Oskari.mapframework.ui.module.common.mapmodule.Plugin"]
});
<|endoftext|>


<commit_before>import { call, put, takeEvery, takeLatest } from 'redux-saga/effects';

import { loginUser, registerUser, getRooms, getOneRoom, getLightStates } from './api';

export function* loginSaga({ data }) {
    try {
        const { token } = yield call(loginUser, data);
        yield put({ type: 'LOGIN_SUCCESSFUL', data: token });
    }
    catch (e) {
        yield put({ type: 'LOGIN_FAILED', data: e.message });
    }
}

export function* registerSaga({ data }) {
    try {
        yield call(registerUser, data);
        yield put({ type: 'REGISTRATION_SUCCESSFUL', data: 'Registration successful' });
    }
    catch (e) {
        yield put({ type: 'REGISTRATION_FAILED', data: e.message });
    }
}

export function* getRoomsSaga({ data }) {
    try {
        const rooms = yield call(getRooms, data);
        yield put({ type: 'GET_ROOMS_SUCCESSFUL', data: rooms });
    }
    catch (e) {
        yield put({ type: 'GET_ROOMS_FAILED', data: e.message });
    }
}

export function* getOneRoomSaga({ data }) {
    try {
        const room = yield call(getOneRoom, ...data);
        yield put({ type: 'GET_ONE_ROOM_SUCCESSFUL', data: room });
    }
    catch (e) {
        yield put({ type: 'GET_ONE_ROOM_FAILED', data: e.message });
    }
}

export function* getLights({ data }) {
    try {
        const lightState = yield call(getLightStates, ...data);
        yield put({ type: 'GET_LIGHTS_SUCCESSFUL', data: lightState });
    }
    catch (e) {
        yield put({ type: 'GET_LIGHTS_FAILED', data: e.message });
    }
}

export default function* rootSaga() {
    yield [
        takeLatest('LOGIN_REQUESTED', loginSaga),
        takeLatest('REGISTRATION_REQUESTED', registerSaga),
        takeEvery('GET_ROOMS_REQUESTED', getRoomsSaga),
        takeEvery('GET_ONE_ROOM_REQUESTED', getOneRoomSaga),
        takeEvery('GET_LIGHTS_REQUESTED', getLights)
    ];
}
<commit_msg>added a set one room saga<commit_after>import { call, put, takeEvery, takeLatest } from 'redux-saga/effects';

import { loginUser, registerUser, getRooms, getOneRoom, setOneRoom, getLightStates } from './api';

export function* loginSaga({ data }) {
    try {
        const { token } = yield call(loginUser, data);
        yield put({ type: 'LOGIN_SUCCESSFUL', data: token });
    }
    catch (e) {
        yield put({ type: 'LOGIN_FAILED', data: e.message });
    }
}

export function* registerSaga({ data }) {
    try {
        yield call(registerUser, data);
        yield put({ type: 'REGISTRATION_SUCCESSFUL', data: 'Registration successful' });
    }
    catch (e) {
        yield put({ type: 'REGISTRATION_FAILED', data: e.message });
    }
}

export function* getRoomsSaga({ data }) {
    try {
        const rooms = yield call(getRooms, data);
        yield put({ type: 'GET_ROOMS_SUCCESSFUL', data: rooms });
    }
    catch (e) {
        yield put({ type: 'GET_ROOMS_FAILED', data: e.message });
    }
}

export function* getOneRoomSaga({ data }) {
    try {
        const room = yield call(getOneRoom, ...data);
        yield put({ type: 'GET_ONE_ROOM_SUCCESSFUL', data: room });
    }
    catch (e) {
        yield put({ type: 'GET_ONE_ROOM_FAILED', data: e.message });
    }
}

async function* setOneRoomSaga({ data }) {
    try {
        const room = yield call(setOneRoom, ...data);
        yield put({ type: 'GET_ONE_ROOM_SUCCESSFUL', data: room });
    }
    catch (e) {
        yield put({ type: 'GET_ONE_ROOM_FAILED', data: e.message });
    }
}

export function* getLights({ data }) {
    try {
        const lightState = yield call(getLightStates, ...data);
        yield put({ type: 'GET_LIGHTS_SUCCESSFUL', data: lightState });
    }
    catch (e) {
        yield put({ type: 'GET_LIGHTS_FAILED', data: e.message });
    }
}

export default function* rootSaga() {
    yield [
        takeLatest('LOGIN_REQUESTED', loginSaga),
        takeLatest('REGISTRATION_REQUESTED', registerSaga),
        takeEvery('GET_ROOMS_REQUESTED', getRoomsSaga),
        takeEvery('GET_ONE_ROOM_REQUESTED', getOneRoomSaga),
        takeEvery('SET_ONE_ROOM_REQUESTED', setOneRoomSaga),
        takeEvery('GET_LIGHTS_REQUESTED', getLights)
    ];
}
<|endoftext|>