<commit_before>'use strict';

var assert = require('assert');

var isModuleExports = require('./lib/is-module-exports.js');
var serialize = require('../serialize.js');
var ASTVerifier = require('./ast-verifier.js');
var TypeInference = require('./type-inference.js');
var SubTypeChecker = require('./sub-type.js');
var NarrowType = require('./narrow-type.js');
var FileScope = require('./scope.js').FileScope;
var FunctionScope = require('./scope.js').FunctionScope;
var BranchScope = require('./scope.js').BranchScope;

var fileExtRegex = /.js$/;

module.exports = ProgramMeta;

function ProgramMeta(checker, ast, fileName, source) {
    this.checker = checker;
    this.ast = ast;
    this.fileName = fileName;
    this.source = source;
    this.sourceLines = source.split('\n');

    this.identifiers = {};
    this.operators = {};
    this.virtualTypes = {};

    this.type = 'program';

    this.moduleExportsNode = null;
    this.moduleExportsType = null;
    this.moduleExportsName = null;

    this.globalScope = checker.globalScope;
    this.currentScope = new FileScope(this.globalScope);
    this.currentScope.loadModuleTokens();

    this.fatalError = false;

    this.headerFile = null;
    this.subType = new SubTypeChecker(this);
    this.verifier = new ASTVerifier(this, this.checker, this.fileName);
    this.inference = new TypeInference(this);
    this.narrow = new NarrowType(this);
}

ProgramMeta.prototype.serializeType =
function serializeType(type, opts) {
    return serialize(type, opts);
};

ProgramMeta.prototype.serializeAST =
function serializeAST(ast) {
    var startLine = ast.loc.start.line;
    var endLine = ast.loc.end.line;

    if (startLine === endLine) {
        return this.sourceLines[startLine - 1].slice(
            ast.loc.start.column, ast.loc.end.column
        );
    }

    var segments = [
        this.sourceLines[startLine - 1].slice(ast.loc.start.column)
    ];

    for (var i = startLine + 1; i < endLine - 1; i++) {
        segments.push(this.sourceLines[i - 1]);
    }

    segments.push(
        this.sourceLines[endLine - 1].slice(0, -ast.loc.end.column)
    );

    return segments.join('\n');
};

ProgramMeta.prototype.getOperator = function getOperator(id) {
    return this.globalScope.getOperator(id);
};

ProgramMeta.prototype.getVirtualType = function getVirtualType(id) {
    return this.globalScope.getVirtualType(id);
};

ProgramMeta.prototype.verify = function verify() {
    var node = this.ast;

    this.verifyNode(node);
};

ProgramMeta.prototype.verifyNode = function verifyNode(node) {
    if (this.fatalError) {
        return null;
    }

    return this.verifier.verifyNode(node);
};

ProgramMeta.prototype.inferType = function inferType(node) {
    if (this.fatalError) {
        return null;
    }

    return this.inference.inferType(node);
};

ProgramMeta.prototype.narrowType =
function narrowType(node, ifBranch, elseBranch) {
    if (this.fatalError) {
        return null;
    }

    return this.narrow.narrowType(node, ifBranch, elseBranch);
};

ProgramMeta.prototype.resolveGeneric =
function resolveGeneric(funcType, node) {
    if (this.fatalError) {
        return null;
    }

    return this.inference.resolveGeneric(funcType, node);
};

ProgramMeta.prototype.setModuleExportsNode =
function setModuleExportsNode(astNode) {
    var moduleExports = null;
    for (var i = 0; i < astNode.body.length; i++) {
        if (isModuleExports(astNode.body[i])) {
            moduleExports = astNode.body[i];
        }
    }

    if (moduleExports) {
        this.moduleExportsNode = moduleExports.expression.right;
    }
};

ProgramMeta.prototype.setModuleExportsType =
function setModuleExportsType(typeDefn, astNode) {
    this.moduleExportsName = astNode.name;
    this.moduleExportsType = typeDefn;
};

ProgramMeta.prototype.addError = function addError(error) {
    error.fileName = this.fileName;
    this.checker.addError(error);
};

ProgramMeta.prototype.countErrors = function countErrors() {
    return this.checker.countErrors();
};

ProgramMeta.prototype.checkSubType =
function checkSubType(node, leftType, rightType) {
    var err = this.subType.checkSubType(node, leftType, rightType);
    if (err) {
        this.addError(err);
        return false;
    }
    return true;
};

ProgramMeta.prototype.checkSubTypeRaw =
function checkSubTypeRaw(node, leftType, rightType) {
    return this.subType.checkSubType(node, leftType, rightType);
};

ProgramMeta.prototype.isSubType =
function isSubType(node, leftType, rightType) {
    var e = this.subType.checkSubType(node, leftType, rightType);
    // console.log('e', e);
    return !e;
};

/*
Each program has a mandatory header file. This contains
type definitions for a subset of the program.

*/
ProgramMeta.prototype.loadHeaderFile =
function loadHeaderFile() {
    var headerFileName = this.fileName.replace(fileExtRegex, '.hjs');

    this.headerFile = this.checker.getOrCreateHeaderFile(headerFileName);
    if (!this.headerFile) {
        return;
    }

    var assignments = this.headerFile.getResolvedAssignments();
    for (var i = 0; i < assignments.length; i++) {
        var expr = assignments[i];

        this.currentScope.addVar(expr.identifier, expr.typeExpression);
    }
};

ProgramMeta.prototype.allocateBranchScope =
function allocateBranchScope() {
    return new BranchScope(this.currentScope);
};

ProgramMeta.prototype.enterBranchScope =
function enterBranchScope(scope) {
    assert(scope, 'must have a scope to enter');

    this.currentScope = scope;
};

ProgramMeta.prototype.exitBranchScope =
function exitBranchScope() {
    this.currentScope = this.currentScope.parent;
};

ProgramMeta.prototype.enterFunctionScope =
function enterFunctionScope(funcNode, typeDefn) {
    var funcScope = new FunctionScope(
        this.currentScope, funcNode.id.name, funcNode
    );
    funcScope.loadTypes(funcNode, typeDefn);

    this.currentScope.addFunctionScope(funcScope);
    this.currentScope = funcScope;
};

ProgramMeta.prototype.exitFunctionScope =
function exitFunctionScope() {
    this.currentScope = this.currentScope.parent;
};
<commit_msg>fix off by one<commit_after>'use strict';

var assert = require('assert');

var isModuleExports = require('./lib/is-module-exports.js');
var serialize = require('../serialize.js');
var ASTVerifier = require('./ast-verifier.js');
var TypeInference = require('./type-inference.js');
var SubTypeChecker = require('./sub-type.js');
var NarrowType = require('./narrow-type.js');
var FileScope = require('./scope.js').FileScope;
var FunctionScope = require('./scope.js').FunctionScope;
var BranchScope = require('./scope.js').BranchScope;

var fileExtRegex = /.js$/;

module.exports = ProgramMeta;

function ProgramMeta(checker, ast, fileName, source) {
    this.checker = checker;
    this.ast = ast;
    this.fileName = fileName;
    this.source = source;
    this.sourceLines = source.split('\n');

    this.identifiers = {};
    this.operators = {};
    this.virtualTypes = {};

    this.type = 'program';

    this.moduleExportsNode = null;
    this.moduleExportsType = null;
    this.moduleExportsName = null;

    this.globalScope = checker.globalScope;
    this.currentScope = new FileScope(this.globalScope);
    this.currentScope.loadModuleTokens();

    this.fatalError = false;

    this.headerFile = null;
    this.subType = new SubTypeChecker(this);
    this.verifier = new ASTVerifier(this, this.checker, this.fileName);
    this.inference = new TypeInference(this);
    this.narrow = new NarrowType(this);
}

ProgramMeta.prototype.serializeType =
function serializeType(type, opts) {
    return serialize(type, opts);
};

ProgramMeta.prototype.serializeAST =
function serializeAST(ast) {
    var startLine = ast.loc.start.line;
    var endLine = ast.loc.end.line;

    if (startLine === endLine) {
        return this.sourceLines[startLine - 1].slice(
            ast.loc.start.column, ast.loc.end.column
        );
    }

    var segments = [
        this.sourceLines[startLine - 1].slice(ast.loc.start.column)
    ];

    for (var i = startLine + 1; i < endLine; i++) {
        segments.push(this.sourceLines[i - 1]);
    }

    segments.push(
        this.sourceLines[endLine - 1].slice(0, -ast.loc.end.column)
    );

    return segments.join('\n');
};

ProgramMeta.prototype.getOperator = function getOperator(id) {
    return this.globalScope.getOperator(id);
};

ProgramMeta.prototype.getVirtualType = function getVirtualType(id) {
    return this.globalScope.getVirtualType(id);
};

ProgramMeta.prototype.verify = function verify() {
    var node = this.ast;

    this.verifyNode(node);
};

ProgramMeta.prototype.verifyNode = function verifyNode(node) {
    if (this.fatalError) {
        return null;
    }

    return this.verifier.verifyNode(node);
};

ProgramMeta.prototype.inferType = function inferType(node) {
    if (this.fatalError) {
        return null;
    }

    return this.inference.inferType(node);
};

ProgramMeta.prototype.narrowType =
function narrowType(node, ifBranch, elseBranch) {
    if (this.fatalError) {
        return null;
    }

    return this.narrow.narrowType(node, ifBranch, elseBranch);
};

ProgramMeta.prototype.resolveGeneric =
function resolveGeneric(funcType, node) {
    if (this.fatalError) {
        return null;
    }

    return this.inference.resolveGeneric(funcType, node);
};

ProgramMeta.prototype.setModuleExportsNode =
function setModuleExportsNode(astNode) {
    var moduleExports = null;
    for (var i = 0; i < astNode.body.length; i++) {
        if (isModuleExports(astNode.body[i])) {
            moduleExports = astNode.body[i];
        }
    }

    if (moduleExports) {
        this.moduleExportsNode = moduleExports.expression.right;
    }
};

ProgramMeta.prototype.setModuleExportsType =
function setModuleExportsType(typeDefn, astNode) {
    this.moduleExportsName = astNode.name;
    this.moduleExportsType = typeDefn;
};

ProgramMeta.prototype.addError = function addError(error) {
    error.fileName = this.fileName;
    this.checker.addError(error);
};

ProgramMeta.prototype.countErrors = function countErrors() {
    return this.checker.countErrors();
};

ProgramMeta.prototype.checkSubType =
function checkSubType(node, leftType, rightType) {
    var err = this.subType.checkSubType(node, leftType, rightType);
    if (err) {
        this.addError(err);
        return false;
    }
    return true;
};

ProgramMeta.prototype.checkSubTypeRaw =
function checkSubTypeRaw(node, leftType, rightType) {
    return this.subType.checkSubType(node, leftType, rightType);
};

ProgramMeta.prototype.isSubType =
function isSubType(node, leftType, rightType) {
    var e = this.subType.checkSubType(node, leftType, rightType);
    // console.log('e', e);
    return !e;
};

/*
Each program has a mandatory header file. This contains
type definitions for a subset of the program.

*/
ProgramMeta.prototype.loadHeaderFile =
function loadHeaderFile() {
    var headerFileName = this.fileName.replace(fileExtRegex, '.hjs');

    this.headerFile = this.checker.getOrCreateHeaderFile(headerFileName);
    if (!this.headerFile) {
        return;
    }

    var assignments = this.headerFile.getResolvedAssignments();
    for (var i = 0; i < assignments.length; i++) {
        var expr = assignments[i];

        this.currentScope.addVar(expr.identifier, expr.typeExpression);
    }
};

ProgramMeta.prototype.allocateBranchScope =
function allocateBranchScope() {
    return new BranchScope(this.currentScope);
};

ProgramMeta.prototype.enterBranchScope =
function enterBranchScope(scope) {
    assert(scope, 'must have a scope to enter');

    this.currentScope = scope;
};

ProgramMeta.prototype.exitBranchScope =
function exitBranchScope() {
    this.currentScope = this.currentScope.parent;
};

ProgramMeta.prototype.enterFunctionScope =
function enterFunctionScope(funcNode, typeDefn) {
    var funcScope = new FunctionScope(
        this.currentScope, funcNode.id.name, funcNode
    );
    funcScope.loadTypes(funcNode, typeDefn);

    this.currentScope.addFunctionScope(funcScope);
    this.currentScope = funcScope;
};

ProgramMeta.prototype.exitFunctionScope =
function exitFunctionScope() {
    this.currentScope = this.currentScope.parent;
};
<|endoftext|>


<commit_before>(function($)
{
	function TextExtTags()
	{
	};

	$.fn.textext.TextExtTags = TextExtTags;
	$.fn.textext.addPlugin('tags', TextExtTags);

	var p = TextExtTags.prototype,

		CSS_DOT             = '.',
		CSS_TAGS_ON_TOP     = 'text-tags-on-top',
		CSS_DOT_TAGS_ON_TOP = CSS_DOT + CSS_TAGS_ON_TOP,
		CSS_TAG             = 'text-tag',
		CSS_DOT_TAG         = CSS_DOT + CSS_TAG,
		CSS_TAGS            = 'text-tags',
		CSS_DOT_TAGS        = CSS_DOT + CSS_TAGS,

		OPT_ENABLED   = 'tags.enabled',
		OPT_HTML_TAG  = 'html.tag',
		OPT_HTML_TAGS = 'html.tags',
		OPT_ITEMS     = 'items',

		EVENT_IS_TAG_ALLOWED = 'isTagAllowed',
		EVENT_SET_DATA       = 'setData',
		EVENT_SELECT_ITEM    = 'selectItem',

		DEFAULT_OPTS = {
			tagsEnabled : true,
			items : [],

			html : {
				tags : '
',
				tag  : '

'
			}
		}
		;

	p.init = function(parent)
	{
		this.baseInit(parent, DEFAULT_OPTS);

		var self  = this,
			input = self.input()
			;

		if(self.opts(OPT_ENABLED))
		{
			input.after(self.opts(OPT_HTML_TAGS));

			self.on({
				enterKeyUp       : self.onEnterKeyUp,
				preInvalidate    : self.onPreInvalidate,
				selectItem       : self.onSelectItem,
				backspaceKeyDown : self.onBackspaceKeyDown,
				postInit         : self.onPostInit
			});

			self.getContainer()
				.click(function(e) { self.onClick(e) })
				.mousemove(function(e) { self.onContainerMouseMove(e) })
				;

			input
				.mousemove(function(e) { self.onInputMouseMove(e) })
				;
		}

		self._originalPadding = {
			left : parseInt(input.css('paddingLeft') || 0),
			top  : parseInt(input.css('paddingTop') || 0)
		};

		self._paddingBox = {
			left : 0,
			top  : 0
		};
	};

	/**
	 * Returns HTML element in which all tag HTML elements are residing.
	 * @author agorbatchev
	 * @date 2011/08/15
	 */
	p.getContainer = function()
	{
		return this.core().input().siblings(CSS_DOT_TAGS);
	};

	//--------------------------------------------------------------------------------
	// Event handlers

	/**
	 * Reacts to the `postInit` event triggered by the core and sets default tags
	 * if any were specified.
	 * @author agorbatchev
	 * @date 2011/08/09
	 */
	p.onPostInit = function(e)
	{
		var self = this;
		self.addTags(self.opts(OPT_ITEMS));
	};

	/**
	 * Reacts to user moving mouse over the text area when cursor is over the text
	 * and not over the tags. Whenever mouse cursor is over the area covered by
	 * tags, the tags container is flipped to be on top of the text area which
	 * makes all tags functional with the mouse.
	 * @author agorbatchev
	 * @date 2011/08/08
	 */
	p.onInputMouseMove = function(e)
	{
		this.toggleZIndex(e);
	};

	/**
	 * Reacts to user moving mouse over the tags. Whenever the cursor moves out
	 * of the tags and back into where the text input is happening visually,
	 * the tags container is sent back under the text area which allows user
	 * to interact with the text using mouse cursor as expected.
	 * @author agorbatchev
	 * @date 2011/08/08
	 */
	p.onContainerMouseMove = function(e)
	{
		this.toggleZIndex(e);
	};

	/**
	 * Reacts to `selectItem` event triggered by the Autocomplete plugin.
	 * @author agorbatchev
	 * @date 2011/08/02
	 */
	p.onSelectItem = function(e, suggestion)
	{
		this.addTagFromInput();
	};

	/**
	 * When backspace key is pressed in an empty text field, deletes last tag in the list.
	 * @author agorbatchev
	 * @date 2011/08/02
	 */
	p.onBackspaceKeyDown = function(e)
	{
		var self    = this,
			lastTag = self.getAllTagElements().last()
			;

		if(self.input().val().length == 0)
			self.removeTag(lastTag);
	};

	p.onPreInvalidate = function()
	{
		var self    = this,
			lastTag = self.getAllTagElements().last(),
			pos     = lastTag.position()
			;

		if(lastTag.length > 0)
			pos.left += lastTag.innerWidth();
		else
			pos = self._originalPadding;

		self._paddingBox = pos;

		self.input().css({
			paddingLeft : pos.left,
			paddingTop  : pos.top
		});
	};

	p.onClick = function(e)
	{
		var self   = this,
			source = $(e.target),
			focus  = 0
			;

		if(source.is(CSS_DOT_TAGS))
		{
			focus = 1;
		}
		else if(source.is('.text-remove'))
		{
			self.removeTag(source.parents(CSS_DOT_TAG + ':first'));
			focus = 1;
		}

		if(focus)
			self.core().focusInput();
	};

	p.onEnterKeyUp = function(e)
	{
		var self = this;

		if(self.opts(OPT_ENABLED))
			self.trigger(EVENT_SELECT_ITEM, self.input().val());
	};

	//--------------------------------------------------------------------------------
	// Core functionality

	/**
	 * Triggeres `setData` event with array of tag values. The `setData` event is
	 * processed by the core which in turn sets original text input's value.
	 * @author agorbatchev
	 * @date 2011/08/09
	 */
	p.setData = function()
	{
		var self   = this,
			result = []
			;

		self.getAllTagElements().each(function()
		{
			result.push($(this).data(CSS_TAG));
		});

		self.trigger(EVENT_SET_DATA, result, result.length > 0);
	};

	/**
	 * Toggles tag container to be on top of the text area or under based on where
	 * the mouse cursor is located. When cursor is above the text input and out of
	 * any of the tags, the tags container is sent under the text area. If cursor
	 * is over any of the tags, the tag container is brought to be over the text
	 * area.
	 * @param e event Original mouse event.
	 * @author agorbatchev
	 * @date 2011/08/08
	 */
	p.toggleZIndex = function(e)
	{
		var self            = this,
			offset          = self.input().offset(),
			mouseX          = e.clientX - offset.left,
			mouseY          = e.clientY - offset.top,
			box             = self._paddingBox,
			container       = self.getContainer(),
			isOnTop         = container.is(CSS_DOT_TAGS_ON_TOP),
			isMouseOverText = mouseX > box.left && mouseY > box.top
			;

		if(!isOnTop && !isMouseOverText || isOnTop && isMouseOverText)
			container[(!isOnTop ? 'add' : 'remove') + 'Class'](CSS_TAGS_ON_TOP);
	};

	p.addTagFromInput = function()
	{
		var self  = this,
			input = self.core().input(),
			val   = input.val(),
			tag
			;

		if(val.length == 0)
			return;

		tag = self.itemManager().stringToItem(val);

		if(self.isTagAllowed(tag) == false)
			return;

		// add the tag
		self.addTag(tag);
		// clear the textarea after it was grabbed as a tag
		input.val('');
		// refocus the textarea just in case it lost the focus
		input.focus();
	};

	p.getAllTagElements = function()
	{
		return this.getContainer().find(CSS_DOT_TAG);
	};

	p.isTagAllowed = function(tag)
	{
		var opts = { tag : tag, result : true };
		this.trigger(EVENT_IS_TAG_ALLOWED, opts);
		return opts.result;
	};

	p.addTags = function(tags)
	{
		if(!tags || tags.length == 0)
			return;

		var self      = this,
			container = self.getContainer(),
			i, tag
			;

		for(i = 0; i < tags.length, tag = tags[i]; i++)
			if(self.isTagAllowed(tag))
				container.append(self.renderTag(tag));

		self.setData();
		self.core().invalidateBounds();
	};

	p.addTag = function(tag)
	{
		this.addTags([tag]);
	};

	p.getTagElement = function(tag)
	{
		var self = this,
			list = self.getAllTagElements(),
			i, item
			;

		for(i = 0; i < list.length, item = $(list[i]); i++)
			if(self.itemManager().compareItems(item.data(CSS_TAG), tag))
				return item;
	};

	p.removeTag = function(tag)
	{
		var self = this,
			element
			;

		if(tag instanceof $)
		{
			element = tag;
			tag = tag.data(CSS_TAG);
		}
		else
		{
			element = self.getTagElement(tag);
		}

		element.remove();
		self.setData();
		self.core().invalidateBounds();
	};

	p.renderTag = function(tag)
	{
		var self = this,
			node = $(self.opts(OPT_HTML_TAG))
			;

		node.find('.text-label').text(self.itemManager().itemToString(tag));
		node.data(CSS_TAG, tag);
		return node;
	};
})(jQuery);
<commit_msg>Fixed `selectItem` handler in Tags not using passed tag.<commit_after>(function($)
{
	function TextExtTags()
	{
	};

	$.fn.textext.TextExtTags = TextExtTags;
	$.fn.textext.addPlugin('tags', TextExtTags);

	var p = TextExtTags.prototype,

		CSS_DOT             = '.',
		CSS_TAGS_ON_TOP     = 'text-tags-on-top',
		CSS_DOT_TAGS_ON_TOP = CSS_DOT + CSS_TAGS_ON_TOP,
		CSS_TAG             = 'text-tag',
		CSS_DOT_TAG         = CSS_DOT + CSS_TAG,
		CSS_TAGS            = 'text-tags',
		CSS_DOT_TAGS        = CSS_DOT + CSS_TAGS,

		OPT_ENABLED   = 'tags.enabled',
		OPT_HTML_TAG  = 'html.tag',
		OPT_HTML_TAGS = 'html.tags',
		OPT_ITEMS     = 'items',

		EVENT_IS_TAG_ALLOWED = 'isTagAllowed',
		EVENT_SET_DATA       = 'setData',
		EVENT_SELECT_ITEM    = 'selectItem',

		DEFAULT_OPTS = {
			tagsEnabled : true,
			items : [],

			html : {
				tags : '
',
				tag  : '

'
			}
		}
		;

	p.init = function(parent)
	{
		this.baseInit(parent, DEFAULT_OPTS);

		var self  = this,
			input = self.input()
			;

		if(self.opts(OPT_ENABLED))
		{
			input.after(self.opts(OPT_HTML_TAGS));

			self.on({
				enterKeyUp       : self.onEnterKeyUp,
				preInvalidate    : self.onPreInvalidate,
				selectItem       : self.onSelectItem,
				backspaceKeyDown : self.onBackspaceKeyDown,
				postInit         : self.onPostInit
			});

			self.getContainer()
				.click(function(e) { self.onClick(e) })
				.mousemove(function(e) { self.onContainerMouseMove(e) })
				;

			input
				.mousemove(function(e) { self.onInputMouseMove(e) })
				;
		}

		self._originalPadding = {
			left : parseInt(input.css('paddingLeft') || 0),
			top  : parseInt(input.css('paddingTop') || 0)
		};

		self._paddingBox = {
			left : 0,
			top  : 0
		};
	};

	/**
	 * Returns HTML element in which all tag HTML elements are residing.
	 * @author agorbatchev
	 * @date 2011/08/15
	 */
	p.getContainer = function()
	{
		return this.core().input().siblings(CSS_DOT_TAGS);
	};

	//--------------------------------------------------------------------------------
	// Event handlers

	/**
	 * Reacts to the `postInit` event triggered by the core and sets default tags
	 * if any were specified.
	 * @author agorbatchev
	 * @date 2011/08/09
	 */
	p.onPostInit = function(e)
	{
		var self = this;
		self.addTags(self.opts(OPT_ITEMS));
	};

	/**
	 * Reacts to user moving mouse over the text area when cursor is over the text
	 * and not over the tags. Whenever mouse cursor is over the area covered by
	 * tags, the tags container is flipped to be on top of the text area which
	 * makes all tags functional with the mouse.
	 * @author agorbatchev
	 * @date 2011/08/08
	 */
	p.onInputMouseMove = function(e)
	{
		this.toggleZIndex(e);
	};

	/**
	 * Reacts to user moving mouse over the tags. Whenever the cursor moves out
	 * of the tags and back into where the text input is happening visually,
	 * the tags container is sent back under the text area which allows user
	 * to interact with the text using mouse cursor as expected.
	 * @author agorbatchev
	 * @date 2011/08/08
	 */
	p.onContainerMouseMove = function(e)
	{
		this.toggleZIndex(e);
	};

	/**
	 * Reacts to `selectItem` event triggered by the Autocomplete plugin.
	 * @author agorbatchev
	 * @date 2011/08/02
	 */
	p.onSelectItem = function(e, tag)
	{
		this.addTag(tag);
	};

	/**
	 * When backspace key is pressed in an empty text field, deletes last tag in the list.
	 * @author agorbatchev
	 * @date 2011/08/02
	 */
	p.onBackspaceKeyDown = function(e)
	{
		var self    = this,
			lastTag = self.getAllTagElements().last()
			;

		if(self.input().val().length == 0)
			self.removeTag(lastTag);
	};

	p.onPreInvalidate = function()
	{
		var self    = this,
			lastTag = self.getAllTagElements().last(),
			pos     = lastTag.position()
			;

		if(lastTag.length > 0)
			pos.left += lastTag.innerWidth();
		else
			pos = self._originalPadding;

		self._paddingBox = pos;

		self.input().css({
			paddingLeft : pos.left,
			paddingTop  : pos.top
		});
	};

	p.onClick = function(e)
	{
		var self   = this,
			source = $(e.target),
			focus  = 0
			;

		if(source.is(CSS_DOT_TAGS))
		{
			focus = 1;
		}
		else if(source.is('.text-remove'))
		{
			self.removeTag(source.parents(CSS_DOT_TAG + ':first'));
			focus = 1;
		}

		if(focus)
			self.core().focusInput();
	};

	p.onEnterKeyUp = function(e)
	{
		var self = this,
			input = self.input(),
			val, tag
			;

		if(self.opts(OPT_ENABLED))
		{
			val = input.val();
			tag = self.itemManager().stringToItem(val);
			self.trigger(EVENT_SELECT_ITEM, tag);
			// clear the textarea after it was grabbed as a tag
			input.val('');
			// refocus the textarea just in case it lost the focus
			input.focus();
		}
	};

	//--------------------------------------------------------------------------------
	// Core functionality

	/**
	 * Triggeres `setData` event with array of tag values. The `setData` event is
	 * processed by the core which in turn sets original text input's value.
	 * @author agorbatchev
	 * @date 2011/08/09
	 */
	p.setData = function()
	{
		var self   = this,
			result = []
			;

		self.getAllTagElements().each(function()
		{
			result.push($(this).data(CSS_TAG));
		});

		self.trigger(EVENT_SET_DATA, result, result.length > 0);
	};

	/**
	 * Toggles tag container to be on top of the text area or under based on where
	 * the mouse cursor is located. When cursor is above the text input and out of
	 * any of the tags, the tags container is sent under the text area. If cursor
	 * is over any of the tags, the tag container is brought to be over the text
	 * area.
	 * @param e event Original mouse event.
	 * @author agorbatchev
	 * @date 2011/08/08
	 */
	p.toggleZIndex = function(e)
	{
		var self            = this,
			offset          = self.input().offset(),
			mouseX          = e.clientX - offset.left,
			mouseY          = e.clientY - offset.top,
			box             = self._paddingBox,
			container       = self.getContainer(),
			isOnTop         = container.is(CSS_DOT_TAGS_ON_TOP),
			isMouseOverText = mouseX > box.left && mouseY > box.top
			;

		if(!isOnTop && !isMouseOverText || isOnTop && isMouseOverText)
			container[(!isOnTop ? 'add' : 'remove') + 'Class'](CSS_TAGS_ON_TOP);
	};

	p.getAllTagElements = function()
	{
		return this.getContainer().find(CSS_DOT_TAG);
	};

	p.isTagAllowed = function(tag)
	{
		var opts = { tag : tag, result : true };
		this.trigger(EVENT_IS_TAG_ALLOWED, opts);
		return opts.result === true;
	};

	p.addTags = function(tags)
	{
		if(!tags || tags.length == 0)
			return;

		var self      = this,
			container = self.getContainer(),
			i, tag
			;

		for(i = 0; i < tags.length, tag = tags[i]; i++)
			if(self.isTagAllowed(tag))
				container.append(self.renderTag(tag));

		self.setData();
		self.core().invalidateBounds();
	};

	p.addTag = function(tag)
	{
		this.addTags([tag]);
	};

	p.getTagElement = function(tag)
	{
		var self = this,
			list = self.getAllTagElements(),
			i, item
			;

		for(i = 0; i < list.length, item = $(list[i]); i++)
			if(self.itemManager().compareItems(item.data(CSS_TAG), tag))
				return item;
	};

	p.removeTag = function(tag)
	{
		var self = this,
			element
			;

		if(tag instanceof $)
		{
			element = tag;
			tag = tag.data(CSS_TAG);
		}
		else
		{
			element = self.getTagElement(tag);
		}

		element.remove();
		self.setData();
		self.core().invalidateBounds();
	};

	p.renderTag = function(tag)
	{
		var self = this,
			node = $(self.opts(OPT_HTML_TAG))
			;

		node.find('.text-label').text(self.itemManager().itemToString(tag));
		node.data(CSS_TAG, tag);
		return node;
	};
})(jQuery);
<|endoftext|>


<commit_before>import { TilesRendererBase } from '../base/TilesRendererBase.js';
import { B3DMLoader } from './B3DMLoader.js';
import { TilesGroup } from './TilesGroup.js';
import {
	Matrix4,
	Box3,
	Sphere,
	Vector3,
	Group,
	Vector2,
	Math as MathUtils,
	Box3Helper,
	Quaternion,
	Frustum
} from 'three';
import { raycastTraverse, raycastTraverseFirstHit } from './raycastTraverse.js';

const DEG2RAD = MathUtils.DEG2RAD;
const tempMat = new Matrix4();
const tempQuaternion = new Quaternion();
const tempVector = new Vector3();
const resVector = new Vector2();
const vecX = new Vector3();
const vecY = new Vector3();
const vecZ = new Vector3();
const _sphere = new Sphere();

function emptyRaycast() {}

export class TilesRenderer extends TilesRendererBase {

	get displayBounds() {

		return this._displayBounds;

	}

	set displayBounds( val ) {

		if ( val !== this.displayBounds ) {

			this._displayBounds = val;
			this.traverse( t => {

				const scene = t.cached.scene;
				const boxHelper = t.cached.boxHelperGroup;
				if ( scene ) {

					if ( val ) {

						scene.add( boxHelper );
						boxHelper.updateMatrixWorld( true );

					} else {

						scene.remove( boxHelper );

					}

				}

			} );

		}

	}

	constructor( url, cameras, renderer ) {

		super( url );
		this.group = new TilesGroup( this );
		this.cameras = Array.isArray( cameras ) ? cameras : [ cameras ];
		this.frustums = [];
		this.renderer = renderer;
		this.activeSet = new Set();
		this.visibleSet = new Set();

	}

	/* Public API */
	getBounds( box ) {

		if ( ! this.root ) {

			return false;

		}

		const cached = this.root.cached;
		const boundingBox = cached.box;
		const obbMat = cached.boxTransform;
		const transformMat = cached.transform;

		box.copy( boundingBox );
		tempMat.multiplyMatrices( transformMat, obbMat );
		box.applyMatrix4( tempMat );

		return true;

	}

	raycast( raycaster, intersects ) {

		if ( ! this.root ) return;

		if ( raycaster.firstHitOnly ) {

			const hit = raycastTraverseFirstHit( this.root, this.group, this.activeSet, raycaster );
			if ( hit ) {

				intersects.push( hit );

			}

		} else {

			raycastTraverse( this.root, this.group, this.activeSet, raycaster, intersects );

		}

	}

	/* Overriden */
	update() {

		const group = this.group;
		const renderer = this.renderer;
		const cameras = this.cameras;
		const frustums = this.frustums;

		// automatically scale the array of frustums to match the cameras
		while ( frustums.length > cameras.length ) {

			frustums.pop();

		}

		while ( frustums.length < cameras.length ) {

			frustums.push( new Frustum() );

		}

		// store the camera frustums in the 3d tiles root frame
		for ( let i = 0, l = frustums.length; i < l; i ++ ) {

			const camera = cameras[ i ];
			const frustum = frustums[ i ];

			tempMat.copy( group.matrixWorld );
			tempMat.premultiply( camera.matrixWorldInverse );
			tempMat.premultiply( camera.projectionMatrix );
			frustum.setFromMatrix( tempMat );

		}

		// store the resolution of the render
		renderer.getSize( resVector );
		resVector.multiplyScalar( renderer.getPixelRatio() );

		super.update();

	}

	preprocessNode( tile, parentTile, tileSetDir ) {

		super.preprocessNode( tile, parentTile, tileSetDir );

		const transform = new Matrix4();
		if ( tile.transform ) {

			const transformArr = tile.transform;
			for ( let i = 0; i < 16; i ++ ) {

				transform.elements[ i ] = transformArr[ i ];

			}

		} else {

			transform.identity();

		}

		if ( parentTile ) {

			transform.multiply( parentTile.cached.transform );

		}

		let box = null;
		let boxTransform = null;
		if ( 'box' in tile.boundingVolume ) {

			const data = tile.boundingVolume.box;
			box = new Box3();
			boxTransform = new Matrix4();

			// get the extents of the bounds in each axis
			vecX.set( data[ 3 ], data[ 4 ], data[ 5 ] );
			vecY.set( data[ 6 ], data[ 7 ], data[ 8 ] );
			vecZ.set( data[ 9 ], data[ 10 ], data[ 11 ] );

			const scaleX = vecX.length();
			const scaleY = vecY.length();
			const scaleZ = vecZ.length();

			vecX.normalize();
			vecY.normalize();
			vecZ.normalize();

			// create the oriented frame that the box exists in
			boxTransform.set(
				vecX.x, vecY.x, vecZ.x, data[ 0 ],
				vecX.y, vecY.y, vecZ.y, data[ 1 ],
				vecX.z, vecY.z, vecZ.z, data[ 2 ],
				0, 0, 0, 1
			);

			// scale the box by the extents
			box.min.set( - scaleX, - scaleY, - scaleZ );
			box.max.set( scaleX, scaleY, scaleZ );

		}

		let sphere = null;
		if ( 'sphere' in tile.boundingVolume ) {

			const data = tile.boundingVolume.sphere;
			sphere = new Sphere();
			sphere.center.set( data[ 0 ], data[ 1 ], data[ 2 ] );
			sphere.radius = data[ 3 ];

		} else if ( 'box' in tile.boundingVolume ) {

			sphere = new Sphere();
			box.getBoundingSphere( sphere );
			boxTransform.decompose( sphere.center, tempQuaternion, tempVector );

		}

		let region = null;
		if ( 'region' in tile.boundingVolume ) {

			console.warn( 'ThreeTilesRenderer: region bounding volume not supported.' );

		}

		tile.cached = {

			loadIndex: 0,
			transform,
			active: false,

			box,
			boxTransform,
			sphere,
			region,

			boxHelper: null,
			scene: null,

		};

	}

	parseTile( buffer, tile ) {

		tile._loadIndex = tile._loadIndex || 0;
		tile._loadIndex ++;

		// TODO: 90 degree rotation must be applied to GLTF file to resolve "up"
		const loadIndex = tile._loadIndex;
		return new B3DMLoader().parse( buffer ).then( res => {

			if ( tile._loadIndex !== loadIndex ) {

				return;

			}

			const cached = tile.cached;
			const cachedBox = cached.box;
			const cachedBoxMat = cached.boxTransform;

			// add a helper group to represent the obb rotation matrix
			const boxHelper = new Box3Helper( cachedBox );
			const boxHelperGroup = new Group();
			cachedBoxMat.decompose( boxHelperGroup.position, boxHelperGroup.quaternion, boxHelperGroup.scale );
			boxHelperGroup.add( boxHelper );
			boxHelperGroup.updateMatrixWorld( true );

			const scene = res.scene;
			cached.transform.decompose( scene.position, scene.quaternion, scene.scale );
			scene.traverse( c => c.frustumCulled = false );

			cached.boxHelperGroup = boxHelperGroup;
			cached.scene = scene;

			if ( this.displayBounds ) {

				scene.add( cached.boxHelperGroup );

			}

			// We handle raycasting in a custom way so remove it from here
			boxHelper.raycast = emptyRaycast;
			scene.traverse( c => {

				c.raycast = emptyRaycast;

			} );

		} );

	}

	disposeTile( tile ) {

		const cached = tile.cached;
		if ( cached.scene ) {

			const scene = cached.scene;
			if ( scene.parent ) {

				scene.parent.remove( scene );

			}

			scene.traverse( c => {

				if ( c.geometry ) {

					c.geometry.dispose();

				}

				if ( c.material ) {

					c.material.dispose();

					// TODO: dispose textures

				}

			} );
			cached.scene = null;
			cached.boxHelperGroup = null;

		}

		tile._loadIndex ++;

	}

	setTileVisible( tile, visible ) {

		const scene = tile.cached.scene;
		const visibleSet = this.visibleSet;
		const group = this.group;
		if ( visible ) {

			if ( scene && ! scene.parent ) {

				group.add( scene );
				visibleSet.add( scene );
				scene.updateMatrixWorld( true );

			}

		} else {

			group.remove( scene );
			visibleSet.delete( scene );

		}

	}

	setTileActive( tile, active ) {

		const cached = tile.cached;
		const activeSet = this.activeSet;
		if ( active !== cached.active ) {

			cached.active = active;
			if ( active ) {

				activeSet.add( cached.scene );

			} else {

				activeSet.delete( cached.scene );

			}

		}

	}

	calculateError( tile ) {

		const cached = tile.cached;
		const cameras = this.cameras;

		// TODO: Use the content bounding volume here?
		const boundingVolume = tile.boundingVolume;
		const transformMat = cached.transform;

		if ( 'box' in boundingVolume ) {

			const group = this.group;
			const boundingBox = cached.box;
			const obbMat = cached.boxTransform;

			// TODO: these can likely be cached? Or the world transform mat can be used
			// transformMat can be rolled into oobMat
			tempMat.copy( group.matrixWorld );
			tempMat.multiply( transformMat );
			tempMat.multiply( obbMat );
			tempMat.getInverse( tempMat );

			// NOTE: scale is inverted here.
			// assume the scales on all axes are uniform.
			let invScale;

			// account for tile scale.
			tempVector.setFromMatrixScale( tempMat );
			invScale = tempVector.x;

			if ( Math.abs( Math.max( tempVector.x - tempVector.y, tempVector.x - tempVector.z ) ) > 1e-6 ) {

				console.warn( 'ThreeTilesRenderer : Non uniform scale used for tile which may cause issues when calculating screen space error.' );

			}

			let minError = Infinity;
			for ( let i = 0, l = cameras.length; i < l; i ++ ) {

				// transform camera position into local frame of the tile bounding box
				const cam = cameras[ i ];
				tempVector.copy( cam.position );
				tempVector.applyMatrix4( tempMat );

				let error;
				if ( cam.isOrthographicCamera ) {

					const w = cam.right - cam.left;
					const h = cam.top - cam.bottom;
					const pixelSize = Math.max( h / resVector.height, w / resVector.width );
					error = tile.geometricError / ( pixelSize * invScale );

				} else {

					const distance = boundingBox.distanceToPoint( tempVector );
					const scaledDistance = distance * invScale;
					const sseDenominator = 2 * Math.tan( 0.5 * cam.fov * DEG2RAD );
					error = ( tile.geometricError * resVector.height ) / ( scaledDistance * sseDenominator );

				}

				minError = Math.min( minError, error );

			}

			return minError;

		} else if ( 'sphere' in boundingVolume ) {

			// const sphere = cached.sphere;

			console.warn( 'ThreeTilesRenderer : Sphere bounds not supported.' );

		} else if ( 'region' in boundingVolume ) {

			// unsupported
			console.warn( 'ThreeTilesRenderer : Region bounds not supported.' );

		}

		return Infinity;

	}

	tileInView( tile ) {

		// TODO: we should use the more precise bounding volumes here if possible
		// cache the root-space planes

		const sphere = tile.cached.sphere;
		if ( sphere ) {

			_sphere.copy( sphere );
			_sphere.applyMatrix4( tile.cached.transform );

			const frustums = this.frustums;
			for ( let i = 0, l = frustums.length; i < l; i ++ ) {

				const frustum = frustums[ i ];
				if ( frustum.intersectsSphere( _sphere ) ) {

					return true;

				}

			}

			return false;

		}

		return true;

	}

}
<commit_msg>add disposal of textures<commit_after>import { TilesRendererBase } from '../base/TilesRendererBase.js';
import { B3DMLoader } from './B3DMLoader.js';
import { TilesGroup } from './TilesGroup.js';
import {
	Matrix4,
	Box3,
	Sphere,
	Vector3,
	Group,
	Vector2,
	Math as MathUtils,
	Box3Helper,
	Quaternion,
	Frustum
} from 'three';
import { raycastTraverse, raycastTraverseFirstHit } from './raycastTraverse.js';

const DEG2RAD = MathUtils.DEG2RAD;
const tempMat = new Matrix4();
const tempQuaternion = new Quaternion();
const tempVector = new Vector3();
const resVector = new Vector2();
const vecX = new Vector3();
const vecY = new Vector3();
const vecZ = new Vector3();
const _sphere = new Sphere();

function emptyRaycast() {}

export class TilesRenderer extends TilesRendererBase {

	get displayBounds() {

		return this._displayBounds;

	}

	set displayBounds( val ) {

		if ( val !== this.displayBounds ) {

			this._displayBounds = val;
			this.traverse( t => {

				const scene = t.cached.scene;
				const boxHelper = t.cached.boxHelperGroup;
				if ( scene ) {

					if ( val ) {

						scene.add( boxHelper );
						boxHelper.updateMatrixWorld( true );

					} else {

						scene.remove( boxHelper );

					}

				}

			} );

		}

	}

	constructor( url, cameras, renderer ) {

		super( url );
		this.group = new TilesGroup( this );
		this.cameras = Array.isArray( cameras ) ? cameras : [ cameras ];
		this.frustums = [];
		this.renderer = renderer;
		this.activeSet = new Set();
		this.visibleSet = new Set();

	}

	/* Public API */
	getBounds( box ) {

		if ( ! this.root ) {

			return false;

		}

		const cached = this.root.cached;
		const boundingBox = cached.box;
		const obbMat = cached.boxTransform;
		const transformMat = cached.transform;

		box.copy( boundingBox );
		tempMat.multiplyMatrices( transformMat, obbMat );
		box.applyMatrix4( tempMat );

		return true;

	}

	raycast( raycaster, intersects ) {

		if ( ! this.root ) return;

		if ( raycaster.firstHitOnly ) {

			const hit = raycastTraverseFirstHit( this.root, this.group, this.activeSet, raycaster );
			if ( hit ) {

				intersects.push( hit );

			}

		} else {

			raycastTraverse( this.root, this.group, this.activeSet, raycaster, intersects );

		}

	}

	/* Overriden */
	update() {

		const group = this.group;
		const renderer = this.renderer;
		const cameras = this.cameras;
		const frustums = this.frustums;

		// automatically scale the array of frustums to match the cameras
		while ( frustums.length > cameras.length ) {

			frustums.pop();

		}

		while ( frustums.length < cameras.length ) {

			frustums.push( new Frustum() );

		}

		// store the camera frustums in the 3d tiles root frame
		for ( let i = 0, l = frustums.length; i < l; i ++ ) {

			const camera = cameras[ i ];
			const frustum = frustums[ i ];

			tempMat.copy( group.matrixWorld );
			tempMat.premultiply( camera.matrixWorldInverse );
			tempMat.premultiply( camera.projectionMatrix );
			frustum.setFromMatrix( tempMat );

		}

		// store the resolution of the render
		renderer.getSize( resVector );
		resVector.multiplyScalar( renderer.getPixelRatio() );

		super.update();

	}

	preprocessNode( tile, parentTile, tileSetDir ) {

		super.preprocessNode( tile, parentTile, tileSetDir );

		const transform = new Matrix4();
		if ( tile.transform ) {

			const transformArr = tile.transform;
			for ( let i = 0; i < 16; i ++ ) {

				transform.elements[ i ] = transformArr[ i ];

			}

		} else {

			transform.identity();

		}

		if ( parentTile ) {

			transform.multiply( parentTile.cached.transform );

		}

		let box = null;
		let boxTransform = null;
		if ( 'box' in tile.boundingVolume ) {

			const data = tile.boundingVolume.box;
			box = new Box3();
			boxTransform = new Matrix4();

			// get the extents of the bounds in each axis
			vecX.set( data[ 3 ], data[ 4 ], data[ 5 ] );
			vecY.set( data[ 6 ], data[ 7 ], data[ 8 ] );
			vecZ.set( data[ 9 ], data[ 10 ], data[ 11 ] );

			const scaleX = vecX.length();
			const scaleY = vecY.length();
			const scaleZ = vecZ.length();

			vecX.normalize();
			vecY.normalize();
			vecZ.normalize();

			// create the oriented frame that the box exists in
			boxTransform.set(
				vecX.x, vecY.x, vecZ.x, data[ 0 ],
				vecX.y, vecY.y, vecZ.y, data[ 1 ],
				vecX.z, vecY.z, vecZ.z, data[ 2 ],
				0, 0, 0, 1
			);

			// scale the box by the extents
			box.min.set( - scaleX, - scaleY, - scaleZ );
			box.max.set( scaleX, scaleY, scaleZ );

		}

		let sphere = null;
		if ( 'sphere' in tile.boundingVolume ) {

			const data = tile.boundingVolume.sphere;
			sphere = new Sphere();
			sphere.center.set( data[ 0 ], data[ 1 ], data[ 2 ] );
			sphere.radius = data[ 3 ];

		} else if ( 'box' in tile.boundingVolume ) {

			sphere = new Sphere();
			box.getBoundingSphere( sphere );
			boxTransform.decompose( sphere.center, tempQuaternion, tempVector );

		}

		let region = null;
		if ( 'region' in tile.boundingVolume ) {

			console.warn( 'ThreeTilesRenderer: region bounding volume not supported.' );

		}

		tile.cached = {

			loadIndex: 0,
			transform,
			active: false,

			box,
			boxTransform,
			sphere,
			region,

			boxHelper: null,
			scene: null,

		};

	}

	parseTile( buffer, tile ) {

		tile._loadIndex = tile._loadIndex || 0;
		tile._loadIndex ++;

		// TODO: 90 degree rotation must be applied to GLTF file to resolve "up"
		const loadIndex = tile._loadIndex;
		return new B3DMLoader().parse( buffer ).then( res => {

			if ( tile._loadIndex !== loadIndex ) {

				return;

			}

			const cached = tile.cached;
			const cachedBox = cached.box;
			const cachedBoxMat = cached.boxTransform;

			// add a helper group to represent the obb rotation matrix
			const boxHelper = new Box3Helper( cachedBox );
			const boxHelperGroup = new Group();
			cachedBoxMat.decompose( boxHelperGroup.position, boxHelperGroup.quaternion, boxHelperGroup.scale );
			boxHelperGroup.add( boxHelper );
			boxHelperGroup.updateMatrixWorld( true );

			const scene = res.scene;
			cached.transform.decompose( scene.position, scene.quaternion, scene.scale );
			scene.traverse( c => c.frustumCulled = false );

			cached.boxHelperGroup = boxHelperGroup;
			cached.scene = scene;

			if ( this.displayBounds ) {

				scene.add( cached.boxHelperGroup );

			}

			// We handle raycasting in a custom way so remove it from here
			boxHelper.raycast = emptyRaycast;
			scene.traverse( c => {

				c.raycast = emptyRaycast;

			} );

		} );

	}

	disposeTile( tile ) {

		const cached = tile.cached;
		if ( cached.scene ) {

			const scene = cached.scene;
			if ( scene.parent ) {

				scene.parent.remove( scene );

			}

			scene.traverse( c => {

				const geometry = c.geometry;
				const material = c.material;

				if ( geometry ) {

					geometry.dispose();

				}

				if ( material ) {

					material.dispose();
					for ( const key in material ) {

						const value = material[ key ];
						if ( value && value.isTexture ) {

							value.dispose();

						}

					}

				}

			} );
			cached.scene = null;
			cached.boxHelperGroup = null;

		}

		tile._loadIndex ++;

	}

	setTileVisible( tile, visible ) {

		const scene = tile.cached.scene;
		const visibleSet = this.visibleSet;
		const group = this.group;
		if ( visible ) {

			if ( scene && ! scene.parent ) {

				group.add( scene );
				visibleSet.add( scene );
				scene.updateMatrixWorld( true );

			}

		} else {

			group.remove( scene );
			visibleSet.delete( scene );

		}

	}

	setTileActive( tile, active ) {

		const cached = tile.cached;
		const activeSet = this.activeSet;
		if ( active !== cached.active ) {

			cached.active = active;
			if ( active ) {

				activeSet.add( cached.scene );

			} else {

				activeSet.delete( cached.scene );

			}

		}

	}

	calculateError( tile ) {

		const cached = tile.cached;
		const cameras = this.cameras;

		// TODO: Use the content bounding volume here?
		const boundingVolume = tile.boundingVolume;
		const transformMat = cached.transform;

		if ( 'box' in boundingVolume ) {

			const group = this.group;
			const boundingBox = cached.box;
			const obbMat = cached.boxTransform;

			// TODO: these can likely be cached? Or the world transform mat can be used
			// transformMat can be rolled into oobMat
			tempMat.copy( group.matrixWorld );
			tempMat.multiply( transformMat );
			tempMat.multiply( obbMat );
			tempMat.getInverse( tempMat );

			// NOTE: scale is inverted here.
			// assume the scales on all axes are uniform.
			let invScale;

			// account for tile scale.
			tempVector.setFromMatrixScale( tempMat );
			invScale = tempVector.x;

			if ( Math.abs( Math.max( tempVector.x - tempVector.y, tempVector.x - tempVector.z ) ) > 1e-6 ) {

				console.warn( 'ThreeTilesRenderer : Non uniform scale used for tile which may cause issues when calculating screen space error.' );

			}

			let minError = Infinity;
			for ( let i = 0, l = cameras.length; i < l; i ++ ) {

				// transform camera position into local frame of the tile bounding box
				const cam = cameras[ i ];
				tempVector.copy( cam.position );
				tempVector.applyMatrix4( tempMat );

				let error;
				if ( cam.isOrthographicCamera ) {

					const w = cam.right - cam.left;
					const h = cam.top - cam.bottom;
					const pixelSize = Math.max( h / resVector.height, w / resVector.width );
					error = tile.geometricError / ( pixelSize * invScale );

				} else {

					const distance = boundingBox.distanceToPoint( tempVector );
					const scaledDistance = distance * invScale;
					const sseDenominator = 2 * Math.tan( 0.5 * cam.fov * DEG2RAD );
					error = ( tile.geometricError * resVector.height ) / ( scaledDistance * sseDenominator );

				}

				minError = Math.min( minError, error );

			}

			return minError;

		} else if ( 'sphere' in boundingVolume ) {

			// const sphere = cached.sphere;

			console.warn( 'ThreeTilesRenderer : Sphere bounds not supported.' );

		} else if ( 'region' in boundingVolume ) {

			// unsupported
			console.warn( 'ThreeTilesRenderer : Region bounds not supported.' );

		}

		return Infinity;

	}

	tileInView( tile ) {

		// TODO: we should use the more precise bounding volumes here if possible
		// cache the root-space planes

		const sphere = tile.cached.sphere;
		if ( sphere ) {

			_sphere.copy( sphere );
			_sphere.applyMatrix4( tile.cached.transform );

			const frustums = this.frustums;
			for ( let i = 0, l = frustums.length; i < l; i ++ ) {

				const frustum = frustums[ i ];
				if ( frustum.intersectsSphere( _sphere ) ) {

					return true;

				}

			}

			return false;

		}

		return true;

	}

}
<|endoftext|>


<commit_before>/**
* Copyright 2012-2020, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var Lib = require('../../lib');
var Registry = require('../../registry');
var Color = require('../../components/color');
var handlePeriodDefaults = require('../scatter/period_defaults');
var handleGroupingDefaults = require('../bar/defaults').handleGroupingDefaults;
var autoType = require('../../plots/cartesian/axis_autotype');
var attributes = require('./attributes');

function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
    function coerce(attr, dflt) {
        return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
    }

    handleSampleDefaults(traceIn, traceOut, coerce, layout);
    if(traceOut.visible === false) return;

    handlePeriodDefaults(traceIn, traceOut, layout, coerce);

    var hasPreCompStats = traceOut._hasPreCompStats;

    if(hasPreCompStats) {
        coerce('lowerfence');
        coerce('upperfence');
    }

    coerce('line.color', (traceIn.marker || {}).color || defaultColor);
    coerce('line.width');
    coerce('fillcolor', Color.addOpacity(traceOut.line.color, 0.5));

    var boxmeanDflt = false;
    if(hasPreCompStats) {
        var mean = coerce('mean');
        var sd = coerce('sd');
        if(mean && mean.length) {
            boxmeanDflt = true;
            if(sd && sd.length) boxmeanDflt = 'sd';
        }
    }
    coerce('boxmean', boxmeanDflt);

    coerce('whiskerwidth');
    coerce('width');
    coerce('quartilemethod');

    var notchedDflt = false;
    if(hasPreCompStats) {
        var notchspan = coerce('notchspan');
        if(notchspan && notchspan.length) {
            notchedDflt = true;
        }
    } else if(Lib.validate(traceIn.notchwidth, attributes.notchwidth)) {
        notchedDflt = true;
    }
    var notched = coerce('notched', notchedDflt);
    if(notched) coerce('notchwidth');

    handlePointsDefaults(traceIn, traceOut, coerce, {prefix: 'box'});
}

function handleSampleDefaults(traceIn, traceOut, coerce, layout) {
    function getDims(arr) {
        var dims = 0;
        if(arr && arr.length) {
            dims += 1;
            if(Lib.isArrayOrTypedArray(arr[0]) && arr[0].length) {
                dims += 1;
            }
        }
        return dims;
    }

    function valid(astr) {
        return Lib.validate(traceIn[astr], attributes[astr]);
    }

    var y = coerce('y');
    var x = coerce('x');

    var sLen;
    if(traceOut.type === 'box') {
        var q1 = coerce('q1');
        var median = coerce('median');
        var q3 = coerce('q3');

        traceOut._hasPreCompStats = (
            q1 && q1.length &&
            median && median.length &&
            q3 && q3.length
        );
        sLen = Math.min(
            Lib.minRowLength(q1),
            Lib.minRowLength(median),
            Lib.minRowLength(q3)
        );
    }

    var yDims = getDims(y);
    var xDims = getDims(x);
    var yLen = yDims && Lib.minRowLength(y);
    var xLen = xDims && Lib.minRowLength(x);

    var calendar = 'gregorian'; // TODO: should we use ax.calendar here?
    var opts = {
        autotypenumbers: 'convert types' // TODO: should we use ax.autotypenumbers here?
    };

    var defaultOrientation, len;
    if(traceOut._hasPreCompStats) {
        switch(String(xDims) + String(yDims)) {
            // no x / no y
            case '00':
                var setInX = valid('x0') || valid('dx');
                var setInY = valid('y0') || valid('dy');

                if(setInY && !setInX) {
                    defaultOrientation = 'h';
                } else {
                    defaultOrientation = 'v';
                }

                len = sLen;
                break;
            // just x
            case '10':
                defaultOrientation = 'v';
                len = Math.min(sLen, xLen);
                break;
            case '20':
                defaultOrientation = 'h';
                len = Math.min(sLen, x.length);
                break;
            // just y
            case '01':
                defaultOrientation = 'h';
                len = Math.min(sLen, yLen);
                break;
            case '02':
                defaultOrientation = 'v';
                len = Math.min(sLen, y.length);
                break;
            // both
            case '12':
                defaultOrientation = 'v';
                len = Math.min(sLen, xLen, y.length);
                break;
            case '21':
                defaultOrientation = 'h';
                len = Math.min(sLen, x.length, yLen);
                break;
            case '11':
                // this one is ill-defined
                len = 0;
                break;
            case '22':
                var hasCategories = false;
                var i;
                for(i = 0; i < x.length; i++) {
                    if(autoType(x[i], calendar, opts) === 'category') {
                        hasCategories = true;
                        break;
                    }
                }

                if(hasCategories) {
                    defaultOrientation = 'v';
                    len = Math.min(sLen, xLen, y.length);
                } else {
                    for(i = 0; i < y.length; i++) {
                        if(autoType(y[i], calendar, opts) === 'category') {
                            hasCategories = true;
                            break;
                        }
                    }

                    if(hasCategories) {
                        defaultOrientation = 'h';
                        len = Math.min(sLen, x.length, yLen);
                    } else {
                        defaultOrientation = 'v';
                        len = Math.min(sLen, xLen, y.length);
                    }
                }
                break;
        }
    } else if(yDims > 0) {
        defaultOrientation = 'v';
        if(xDims > 0) {
            len = Math.min(xLen, yLen);
        } else {
            len = Math.min(yLen);
        }
    } else if(xDims > 0) {
        defaultOrientation = 'h';
        len = Math.min(xLen);
    } else {
        len = 0;
    }

    if(!len) {
        traceOut.visible = false;
        return;
    }
    traceOut._length = len;

    var orientation = coerce('orientation', defaultOrientation);

    // these are just used for positioning, they never define the sample
    if(traceOut._hasPreCompStats) {
        if(orientation === 'v' && xDims === 0) {
            coerce('x0', 0);
            coerce('dx', 1);
        } else if(orientation === 'h' && yDims === 0) {
            coerce('y0', 0);
            coerce('dy', 1);
        }
    } else {
        if(orientation === 'v' && xDims === 0) {
            coerce('x0');
        } else if(orientation === 'h' && yDims === 0) {
            coerce('y0');
        }
    }

    var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
    handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
}

function handlePointsDefaults(traceIn, traceOut, coerce, opts) {
    var prefix = opts.prefix;

    var outlierColorDflt = Lib.coerce2(traceIn, traceOut, attributes, 'marker.outliercolor');
    var lineoutliercolor = coerce('marker.line.outliercolor');

    var modeDflt = 'outliers';
    if(traceOut._hasPreCompStats) {
        modeDflt = 'all';
    } else if(outlierColorDflt || lineoutliercolor) {
        modeDflt = 'suspectedoutliers';
    }

    var mode = coerce(prefix + 'points', modeDflt);

    if(mode) {
        coerce('jitter', mode === 'all' ? 0.3 : 0);
        coerce('pointpos', mode === 'all' ? -1.5 : 0);

        coerce('marker.symbol');
        coerce('marker.opacity');
        coerce('marker.size');
        coerce('marker.color', traceOut.line.color);
        coerce('marker.line.color');
        coerce('marker.line.width');

        if(mode === 'suspectedoutliers') {
            coerce('marker.line.outliercolor', traceOut.marker.color);
            coerce('marker.line.outlierwidth');
        }

        coerce('selected.marker.color');
        coerce('unselected.marker.color');
        coerce('selected.marker.size');
        coerce('unselected.marker.size');

        coerce('text');
        coerce('hovertext');
    } else {
        delete traceOut.marker;
    }

    var hoveron = coerce('hoveron');
    if(hoveron === 'all' || hoveron.indexOf('points') !== -1) {
        coerce('hovertemplate');
    }

    Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
}

function crossTraceDefaults(fullData, fullLayout) {
    var traceIn, traceOut;

    function coerce(attr) {
        return Lib.coerce(traceOut._input, traceOut, attributes, attr);
    }

    for(var i = 0; i < fullData.length; i++) {
        traceOut = fullData[i];
        var traceType = traceOut.type;

        if(traceType === 'box' || traceType === 'violin') {
            traceIn = traceOut._input;
            if(fullLayout[traceType + 'mode'] === 'group') {
                handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce);
            }
        }
    }
}

module.exports = {
    supplyDefaults: supplyDefaults,
    crossTraceDefaults: crossTraceDefaults,

    handleSampleDefaults: handleSampleDefaults,
    handlePointsDefaults: handlePointsDefaults
};
<commit_msg>apply layout.calnedar and layout.autotypenumbers in box defaults<commit_after>/**
* Copyright 2012-2020, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var Lib = require('../../lib');
var Registry = require('../../registry');
var Color = require('../../components/color');
var handlePeriodDefaults = require('../scatter/period_defaults');
var handleGroupingDefaults = require('../bar/defaults').handleGroupingDefaults;
var autoType = require('../../plots/cartesian/axis_autotype');
var attributes = require('./attributes');

function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
    function coerce(attr, dflt) {
        return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
    }

    handleSampleDefaults(traceIn, traceOut, coerce, layout);
    if(traceOut.visible === false) return;

    handlePeriodDefaults(traceIn, traceOut, layout, coerce);

    var hasPreCompStats = traceOut._hasPreCompStats;

    if(hasPreCompStats) {
        coerce('lowerfence');
        coerce('upperfence');
    }

    coerce('line.color', (traceIn.marker || {}).color || defaultColor);
    coerce('line.width');
    coerce('fillcolor', Color.addOpacity(traceOut.line.color, 0.5));

    var boxmeanDflt = false;
    if(hasPreCompStats) {
        var mean = coerce('mean');
        var sd = coerce('sd');
        if(mean && mean.length) {
            boxmeanDflt = true;
            if(sd && sd.length) boxmeanDflt = 'sd';
        }
    }
    coerce('boxmean', boxmeanDflt);

    coerce('whiskerwidth');
    coerce('width');
    coerce('quartilemethod');

    var notchedDflt = false;
    if(hasPreCompStats) {
        var notchspan = coerce('notchspan');
        if(notchspan && notchspan.length) {
            notchedDflt = true;
        }
    } else if(Lib.validate(traceIn.notchwidth, attributes.notchwidth)) {
        notchedDflt = true;
    }
    var notched = coerce('notched', notchedDflt);
    if(notched) coerce('notchwidth');

    handlePointsDefaults(traceIn, traceOut, coerce, {prefix: 'box'});
}

function handleSampleDefaults(traceIn, traceOut, coerce, layout) {
    function getDims(arr) {
        var dims = 0;
        if(arr && arr.length) {
            dims += 1;
            if(Lib.isArrayOrTypedArray(arr[0]) && arr[0].length) {
                dims += 1;
            }
        }
        return dims;
    }

    function valid(astr) {
        return Lib.validate(traceIn[astr], attributes[astr]);
    }

    var y = coerce('y');
    var x = coerce('x');

    var sLen;
    if(traceOut.type === 'box') {
        var q1 = coerce('q1');
        var median = coerce('median');
        var q3 = coerce('q3');

        traceOut._hasPreCompStats = (
            q1 && q1.length &&
            median && median.length &&
            q3 && q3.length
        );
        sLen = Math.min(
            Lib.minRowLength(q1),
            Lib.minRowLength(median),
            Lib.minRowLength(q3)
        );
    }

    var yDims = getDims(y);
    var xDims = getDims(x);
    var yLen = yDims && Lib.minRowLength(y);
    var xLen = xDims && Lib.minRowLength(x);

    var calendar = layout.calendar;
    var opts = {
        autotypenumbers: layout.autotypenumbers
    };

    var defaultOrientation, len;
    if(traceOut._hasPreCompStats) {
        switch(String(xDims) + String(yDims)) {
            // no x / no y
            case '00':
                var setInX = valid('x0') || valid('dx');
                var setInY = valid('y0') || valid('dy');

                if(setInY && !setInX) {
                    defaultOrientation = 'h';
                } else {
                    defaultOrientation = 'v';
                }

                len = sLen;
                break;
            // just x
            case '10':
                defaultOrientation = 'v';
                len = Math.min(sLen, xLen);
                break;
            case '20':
                defaultOrientation = 'h';
                len = Math.min(sLen, x.length);
                break;
            // just y
            case '01':
                defaultOrientation = 'h';
                len = Math.min(sLen, yLen);
                break;
            case '02':
                defaultOrientation = 'v';
                len = Math.min(sLen, y.length);
                break;
            // both
            case '12':
                defaultOrientation = 'v';
                len = Math.min(sLen, xLen, y.length);
                break;
            case '21':
                defaultOrientation = 'h';
                len = Math.min(sLen, x.length, yLen);
                break;
            case '11':
                // this one is ill-defined
                len = 0;
                break;
            case '22':
                var hasCategories = false;
                var i;
                for(i = 0; i < x.length; i++) {
                    if(autoType(x[i], calendar, opts) === 'category') {
                        hasCategories = true;
                        break;
                    }
                }

                if(hasCategories) {
                    defaultOrientation = 'v';
                    len = Math.min(sLen, xLen, y.length);
                } else {
                    for(i = 0; i < y.length; i++) {
                        if(autoType(y[i], calendar, opts) === 'category') {
                            hasCategories = true;
                            break;
                        }
                    }

                    if(hasCategories) {
                        defaultOrientation = 'h';
                        len = Math.min(sLen, x.length, yLen);
                    } else {
                        defaultOrientation = 'v';
                        len = Math.min(sLen, xLen, y.length);
                    }
                }
                break;
        }
    } else if(yDims > 0) {
        defaultOrientation = 'v';
        if(xDims > 0) {
            len = Math.min(xLen, yLen);
        } else {
            len = Math.min(yLen);
        }
    } else if(xDims > 0) {
        defaultOrientation = 'h';
        len = Math.min(xLen);
    } else {
        len = 0;
    }

    if(!len) {
        traceOut.visible = false;
        return;
    }
    traceOut._length = len;

    var orientation = coerce('orientation', defaultOrientation);

    // these are just used for positioning, they never define the sample
    if(traceOut._hasPreCompStats) {
        if(orientation === 'v' && xDims === 0) {
            coerce('x0', 0);
            coerce('dx', 1);
        } else if(orientation === 'h' && yDims === 0) {
            coerce('y0', 0);
            coerce('dy', 1);
        }
    } else {
        if(orientation === 'v' && xDims === 0) {
            coerce('x0');
        } else if(orientation === 'h' && yDims === 0) {
            coerce('y0');
        }
    }

    var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
    handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
}

function handlePointsDefaults(traceIn, traceOut, coerce, opts) {
    var prefix = opts.prefix;

    var outlierColorDflt = Lib.coerce2(traceIn, traceOut, attributes, 'marker.outliercolor');
    var lineoutliercolor = coerce('marker.line.outliercolor');

    var modeDflt = 'outliers';
    if(traceOut._hasPreCompStats) {
        modeDflt = 'all';
    } else if(outlierColorDflt || lineoutliercolor) {
        modeDflt = 'suspectedoutliers';
    }

    var mode = coerce(prefix + 'points', modeDflt);

    if(mode) {
        coerce('jitter', mode === 'all' ? 0.3 : 0);
        coerce('pointpos', mode === 'all' ? -1.5 : 0);

        coerce('marker.symbol');
        coerce('marker.opacity');
        coerce('marker.size');
        coerce('marker.color', traceOut.line.color);
        coerce('marker.line.color');
        coerce('marker.line.width');

        if(mode === 'suspectedoutliers') {
            coerce('marker.line.outliercolor', traceOut.marker.color);
            coerce('marker.line.outlierwidth');
        }

        coerce('selected.marker.color');
        coerce('unselected.marker.color');
        coerce('selected.marker.size');
        coerce('unselected.marker.size');

        coerce('text');
        coerce('hovertext');
    } else {
        delete traceOut.marker;
    }

    var hoveron = coerce('hoveron');
    if(hoveron === 'all' || hoveron.indexOf('points') !== -1) {
        coerce('hovertemplate');
    }

    Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
}

function crossTraceDefaults(fullData, fullLayout) {
    var traceIn, traceOut;

    function coerce(attr) {
        return Lib.coerce(traceOut._input, traceOut, attributes, attr);
    }

    for(var i = 0; i < fullData.length; i++) {
        traceOut = fullData[i];
        var traceType = traceOut.type;

        if(traceType === 'box' || traceType === 'violin') {
            traceIn = traceOut._input;
            if(fullLayout[traceType + 'mode'] === 'group') {
                handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce);
            }
        }
    }
}

module.exports = {
    supplyDefaults: supplyDefaults,
    crossTraceDefaults: crossTraceDefaults,

    handleSampleDefaults: handleSampleDefaults,
    handlePointsDefaults: handlePointsDefaults
};
<|endoftext|>


<commit_before>import {bindable} from 'aurelia-templating';
import {inject} from 'aurelia-dependency-injection';
import {getLogger} from 'aurelia-logging';
import {bindingMode} from 'aurelia-binding';
import {NodeModel} from './node-model';
import {TreeNode} from './tree-node';
import {fireEvent} from '../common/events';

@inject(Element)
export class TreeView {
  @bindable() expandOnFocus: boolean = false;
  @bindable() nodes: NodeModel[];
  @bindable() multiSelect: boolean = false;
  @bindable({
    defaultBindingMode: bindingMode.twoWay
  }) focused: NodeModel = null;
  @bindable({
    defaultBindingMode: bindingMode.twoWay
  }) selected: NodeModel[] = [];
  subscriptions = [];

  // comparers
  @bindable() compareEquality = null;

  bind() {
    this.multiSelect = (this.multiSelect === true || this.multiSelect === 'true');
  }

  constructor(element) {
    this.element = element;
    this.log = getLogger('tree-view');
    this.compareEquality = (args) => { return args.a === args.b; };

    let templateElement = this.element.querySelector('tree-node-template');
    if (templateElement) {
      this.templateElement = templateElement;
    } else {
      // this.log.warn('ctor - no template element');
    }
  }

  attached() { }

  detached() { }

  created() {
    if (this.templateElement) {
      if (this.templateElement.au) {
        let viewModel = this.templateElement.au.controller.viewModel;
        this.log.debug('viewModel', viewModel);
      } else {
        this.log.warn('no viewmodel found for template', this.templateElement);
      }
    } else {
      // this.log.warn('created - no template element');
    }
  }

  nodesChanged(newValue, oldValue) {
    if (newValue) {
      // && this.templateElement
      this.enhanceNodes(newValue);
      this.preselectNodes(newValue);
    }
  }

  enhanceNodes(nodes: NodeModel[]) {
    nodes.forEach(node => {
      if (node.children && typeof node.children !== 'function') {
        this.enhanceNodes(node.children);
      }
      if (this.templateElement) {
        node._template = this.templateElement.au.controller.viewModel.template;
      }
      // node._tree = this;
      node._tree = {
        focusNode: this.focusNode.bind(this),
        selectNode: this.selectNode.bind(this),
        deselectNode: this.deselectNode.bind(this),
        multiSelect: this.multiSelect
      };
    });
  }

  preselectNodes(nodes: NodeModel[]) {
    nodes.forEach(node => {
      if (this.selected.find(n => this.compareEquality({a: node, b: n}))) {
        node.selected = true;
        node.expandNode().then(() => {
          this.preselectNodes(node.children);
        });
      }
    });
  }

  _suspendUpdate = false;
  focusNode(node: NodeModel) {
    if (!this._suspendUpdate && node !== this.focused) {
      if (this.focused) {
        this._suspendUpdate = true;
        this.focused.focused = false;
        this._suspendUpdate = false;
      }
      this.focused = node;
      fireEvent(this.element, 'focused', { node });
      if (this.expandOnFocus) {
        node.expandNode();
      }
      if (!this.multiSelect) {
        this.selected.splice(0);
        this.selectNode(node);
      }
    }
  }

  selectNode(node: NodeModel) {
    let existing = this.selected.findIndex(n => this.compareEquality({a: node, b: n}));
      if (!existing) {
      this.log.debug('selecting node', node);
      this.selected.push(node);
      fireEvent(this.element, 'selection-changed', { nodes: this.selected });
    }
  }

  deselectNode(node: NodeModel) {
    this.log.debug('deselecting node', node);
    // let index = this.selected.indexOf(node);
    let index = this.selected.findIndex(n => this.compareEquality({a: node, b: n}));
    if (index === -1) {
      this.log.error('node not found in selected', node);
    } else {
      this.selected.splice(index, 1);
      fireEvent(this.element, 'selection-changed', { nodes: this.selected });
    }
  }

  expandOnFocusChanged(newValue) {
    this.expandOnFocus = (newValue === true || newValue === 'true');
  }

  // moveNode(node: TreeNode, target: TreeNode | TreeView) {
  //   console.log('moveNode', node, target);
  //   if (target instanceof TreeNode) {
  //     target.model.children.push(node.model);
  //   }
  //   // target.model.children.push(node.model);
  //   let parent = node.element.parentNode;
  //   let children;
  //   while (parent !== null && parent.tagName !== 'TREE-NODE') {
  //     parent = parent.parentNode;
  //   }
  //   if (parent === null) {
  //     children = this.nodes;
  //   } else {
  //     children = parent.au['tree-node'].viewModel.model.children;
  //   }
  //   let pos = children.indexOf(node.model);
  //   children.splice(pos, 1);
  // }

  moveNode(node: TreeNode, target: TreeNode | TreeView, sibling: TreeNode) {
    this.log.debug('moveNode', node, target, sibling);

    if (target instanceof TreeNode) {
      // target.model.children.push(node.model);
      target.insertChild(node, sibling);
      let parent = node.element.parentNode;
      while (parent !== null && parent.tagName !== 'TREE-NODE') {
        parent = parent.parentNode;
      }
      if (parent === null) {
        parent = this;
        parent.removeNode(node);
      } else {
        parent.au['tree-node'].viewModel.removeChild(node.model);
      }
    }
  }

  removeNode(node: TreeNode) {
    console.warn('removeNode not implemented');
  }
}
<commit_msg>fix(tree-view): prevent nodes from being selected twice<commit_after>import {bindable} from 'aurelia-templating';
import {inject} from 'aurelia-dependency-injection';
import {getLogger} from 'aurelia-logging';
import {bindingMode} from 'aurelia-binding';
import {NodeModel} from './node-model';
import {TreeNode} from './tree-node';
import {fireEvent} from '../common/events';

@inject(Element)
export class TreeView {
  @bindable() expandOnFocus: boolean = false;
  @bindable() nodes: NodeModel[];
  @bindable() multiSelect: boolean = false;
  @bindable({
    defaultBindingMode: bindingMode.twoWay
  }) focused: NodeModel = null;
  @bindable({
    defaultBindingMode: bindingMode.twoWay
  }) selected: NodeModel[] = [];
  subscriptions = [];

  // comparers
  @bindable() compareEquality = null;

  bind() {
    this.multiSelect = (this.multiSelect === true || this.multiSelect === 'true');
  }

  constructor(element) {
    this.element = element;
    this.log = getLogger('tree-view');
    this.compareEquality = (args) => { return args.a === args.b; };

    let templateElement = this.element.querySelector('tree-node-template');
    if (templateElement) {
      this.templateElement = templateElement;
    } else {
      // this.log.warn('ctor - no template element');
    }
  }

  attached() { }

  detached() { }

  created() {
    if (this.templateElement) {
      if (this.templateElement.au) {
        let viewModel = this.templateElement.au.controller.viewModel;
        this.log.debug('viewModel', viewModel);
      } else {
        this.log.warn('no viewmodel found for template', this.templateElement);
      }
    } else {
      // this.log.warn('created - no template element');
    }
  }

  nodesChanged(newValue, oldValue) {
    if (newValue) {
      // && this.templateElement
      this.enhanceNodes(newValue);
      this.preselectNodes(newValue);
    }
  }

  enhanceNodes(nodes: NodeModel[]) {
    nodes.forEach(node => {
      if (node.children && typeof node.children !== 'function') {
        this.enhanceNodes(node.children);
      }
      if (this.templateElement) {
        node._template = this.templateElement.au.controller.viewModel.template;
      }
      // node._tree = this;
      node._tree = {
        focusNode: this.focusNode.bind(this),
        selectNode: this.selectNode.bind(this),
        deselectNode: this.deselectNode.bind(this),
        multiSelect: this.multiSelect
      };
    });
  }

  preselectNodes(nodes: NodeModel[]) {
    nodes.forEach(node => {
      if (this.selected.find(n => this.compareEquality({a: node, b: n}))) {
        node.selected = true;
        node.expandNode().then(() => {
          this.preselectNodes(node.children);
        });
      }
    });
  }

  _suspendUpdate = false;
  focusNode(node: NodeModel) {
    if (!this._suspendUpdate && node !== this.focused) {
      if (this.focused) {
        this._suspendUpdate = true;
        this.focused.focused = false;
        this._suspendUpdate = false;
      }
      this.focused = node;
      fireEvent(this.element, 'focused', { node });
      if (this.expandOnFocus) {
        node.expandNode();
      }
      if (!this.multiSelect) {
        this.selected.splice(0);
        this.selectNode(node);
      }
    }
  }

  selectNode(node: NodeModel) {
    let existing = this.selected.findIndex(n => this.compareEquality({a: node, b: n}));
      if (existing === -1) {
      this.log.debug('selecting node', node);
      this.selected.push(node);
      fireEvent(this.element, 'selection-changed', { nodes: this.selected });
    }
  }

  deselectNode(node: NodeModel) {
    this.log.debug('deselecting node', node);
    // let index = this.selected.indexOf(node);
    let index = this.selected.findIndex(n => this.compareEquality({a: node, b: n}));
    if (index === -1) {
      this.log.error('node not found in selected', node);
    } else {
      this.selected.splice(index, 1);
      fireEvent(this.element, 'selection-changed', { nodes: this.selected });
    }
  }

  expandOnFocusChanged(newValue) {
    this.expandOnFocus = (newValue === true || newValue === 'true');
  }

  // moveNode(node: TreeNode, target: TreeNode | TreeView) {
  //   console.log('moveNode', node, target);
  //   if (target instanceof TreeNode) {
  //     target.model.children.push(node.model);
  //   }
  //   // target.model.children.push(node.model);
  //   let parent = node.element.parentNode;
  //   let children;
  //   while (parent !== null && parent.tagName !== 'TREE-NODE') {
  //     parent = parent.parentNode;
  //   }
  //   if (parent === null) {
  //     children = this.nodes;
  //   } else {
  //     children = parent.au['tree-node'].viewModel.model.children;
  //   }
  //   let pos = children.indexOf(node.model);
  //   children.splice(pos, 1);
  // }

  moveNode(node: TreeNode, target: TreeNode | TreeView, sibling: TreeNode) {
    this.log.debug('moveNode', node, target, sibling);

    if (target instanceof TreeNode) {
      // target.model.children.push(node.model);
      target.insertChild(node, sibling);
      let parent = node.element.parentNode;
      while (parent !== null && parent.tagName !== 'TREE-NODE') {
        parent = parent.parentNode;
      }
      if (parent === null) {
        parent = this;
        parent.removeNode(node);
      } else {
        parent.au['tree-node'].viewModel.removeChild(node.model);
      }
    }
  }

  removeNode(node: TreeNode) {
    console.warn('removeNode not implemented');
  }
}
<|endoftext|>


<commit_before>// box.js
(function($) {
    // Is native?
    if($) return;

    var c_XflowObserverList = [];

    var XML3DDataObserver = function(callback){
        this.callback = callback;
        this.observed = [];
    }
    window.XML3DDataObserver = XML3DDataObserver;

    XML3DDataObserver.prototype.observe = function(node, options){
        if(this.observed.length == 0)
            c_XflowObserverList.push(this);
        var dataAdapter = XML3D.base.resourceManager.getAdapter(node, XML3D.data);
        if(dataAdapter){

            var entry = {
                node: node,
                changed: false,
                request: null
            };

            var names = options && options['names'];
            var typeOfNames = Object.prototype.toString.call(names).slice(8, -1);
            if (typeOfNames === "String") {
                names = [names];
            }

            entry.request = dataAdapter.getComputeRequest(names, function(request, changeType){
                entry.changed = true;
            });
            // Fetch result to synchronize Xflow structures and connect to callbacks
            // TODO: Find an option to connect request to callback structure without computing result
            entry.request.getResult();

            this.observed.push(entry);
        }

    }

    XML3DDataObserver.prototype.disconnect = function(){
        for(var i = 0; i < this.observed.length; ++i){
            this.observed[i].request.clear();
        }
        this.observed = [];
        var i = c_XflowObserverList.length;
        while(i--){
            if(c_XflowObserverList[i] == this)
                c_XflowObserverList.splice(i, 1);
        }
    }

    XML3D.updateXflowObserver = function(){
        for(var i = 0; i < c_XflowObserverList.length; ++i){
            var observer = c_XflowObserverList[i];
            var records = [];
            for(var j = 0; j < observer.observed.length; ++j){
                var entry = observer.observed[j];
                if(entry.changed){
                    entry.changed = false;
                    var result = entry.request.getResult();
                    var dataResult = new XML3DDataResult(result);
                    records.push( new XML3DDataRecord(entry.node, dataResult));
                }
            }
            if(records.length > 0 && observer.callback){
                observer.callback(records, observer);
            }
        }
    }

    var XML3DDataRecord = function(target, result){
        this.target = target;
        this.result = result;
    }
    window.XML3DDataRecord = XML3DDataRecord;

    var XML3DDataResult = function(result){
        this._entries = {};
        constructDataResult(this, result);
    }
    window.XML3DDataResult = XML3DDataResult;

    XML3DDataResult.prototype.getValue = function(name) {
        if (this._entries[name])
            return this._entries[name].value;
        return null;
    }

    XML3DDataResult.prototype.getType = function(name) {
        if (this._entries[name])
            return this._entries[name].type;
        return null;
    }

    XML3DDataResult.prototype.getNames = function(){
        var result = [];
        for(var name in this._entries){
            result.push(name);
        }
        return result;
    }

    XML3DDataResult.FLOAT  = 0;
    XML3DDataResult.FLOAT2 = 1;
    XML3DDataResult.FLOAT3 = 2;
    XML3DDataResult.FLOAT4 = 3;
    XML3DDataResult.FLOAT4X4 = 4;
    XML3DDataResult.INT = 10;
    XML3DDataResult.INT4 = 11;
    XML3DDataResult.BOOL = 20;
    XML3DDataResult.TEXTURE = 30;
    XML3DDataResult.BYTE = 40;
    XML3DDataResult.UBYTE = 50;

    function constructDataResult(dataResult, result){
        for(var i = 0; i < result.outputNames.length; ++i){
            var name = result.outputNames[i];
            var entry = result.getOutputData(name);
            var value = entry && entry.getValue();
            if (value !== null) {
                var type = getXML3DDataType(entry.type);
                dataResult._entries[name] = { type: type, value: value};
            }
        }
    }

    function getXML3DDataType(type){
        switch(type){
            case Xflow.DATA_TYPE.FLOAT : return XML3DDataResult.FLOAT;
            case Xflow.DATA_TYPE.FLOAT2 : return XML3DDataResult.FLOAT2;
            case Xflow.DATA_TYPE.FLOAT3 : return XML3DDataResult.FLOAT3;
            case Xflow.DATA_TYPE.FLOAT4 : return XML3DDataResult.FLOAT4;
            case Xflow.DATA_TYPE.FLOAT4X4 : return XML3DDataResult.FLOAT4X4;
            case Xflow.DATA_TYPE.INT : return XML3DDataResult.INT;
            case Xflow.DATA_TYPE.INT4 : return XML3DDataResult.INT4;
            case Xflow.DATA_TYPE.BOOL : return XML3DDataResult.BOOL;
            case Xflow.DATA_TYPE.TEXTURE : return XML3DDataResult.TEXTURE;
            case Xflow.DATA_TYPE.BYTE : return XML3DDataResult.BYTE;
            case Xflow.DATA_TYPE.UBYTE : return XML3DDataResult.UBYTE;
            default: throw new Error("WHAT IS THIS I DON'T EVEN...");
        }
    }

    var XML3DDataChannelInfo = function(type, origin, originalName, seqLength, seqMinKey, seqMaxKey){
        this.type = getXML3DDataType(type);
        this.origin = origin;
        this.originalName = originalName;
        this.seqLength = seqLength;
        this.seqMinKey = seqMinKey;
        this.seqMaxKey = seqMaxKey;
    }
    window.XML3DDataChannelInfo = XML3DDataChannelInfo;

    XML3DDataChannelInfo.ORIGIN_CHILD = 1;
    XML3DDataChannelInfo.ORIGIN_COMPUTE = 2;
    XML3DDataChannelInfo.ORIGIN_PROTO = 3;

}(XML3D._native));
<commit_msg>Added some helpful error messages to XML3DDataObserver.<commit_after>// box.js
(function($) {
    // Is native?
    if($) return;

    var c_XflowObserverList = [];

    var XML3DDataObserver = function(callback){
        this.callback = callback;
        this.observed = [];
    }
    window.XML3DDataObserver = XML3DDataObserver;

    XML3DDataObserver.prototype.observe = function(node, options){
        if(!node)
            throw new Error("The node to observe is null.");

        if(!node._configured)
            throw new Error("Note to observe is not configured (yet). Make sure to pass an XML3D node and to execute " +
                "this function after XML3D has been configured e.g. inside a DOMContentLoaded listener.");

        var dataAdapter = XML3D.base.resourceManager.getAdapter(node, XML3D.data);
        if(!dataAdapter)
            throw new Error("Can't observe node. XML3DataObserver can only observe data containers such as <data>, <mesh> or <shader>");

        if(this.observed.length == 0)
            c_XflowObserverList.push(this);

        var entry = {
            node: node,
            changed: false,
            request: null
        };

        var names = options && options['names'];
        var typeOfNames = Object.prototype.toString.call(names).slice(8, -1);
        if (typeOfNames === "String") {
            names = [names];
        }

        entry.request = dataAdapter.getComputeRequest(names, function(request, changeType){
            entry.changed = true;
        });
        // Fetch result to synchronize Xflow structures and connect to callbacks
        // TODO: Find an option to connect request to callback structure without computing result
        entry.request.getResult();

        this.observed.push(entry);
    }

    XML3DDataObserver.prototype.disconnect = function(){
        for(var i = 0; i < this.observed.length; ++i){
            this.observed[i].request.clear();
        }
        this.observed = [];
        var i = c_XflowObserverList.length;
        while(i--){
            if(c_XflowObserverList[i] == this)
                c_XflowObserverList.splice(i, 1);
        }
    }

    XML3D.updateXflowObserver = function(){
        for(var i = 0; i < c_XflowObserverList.length; ++i){
            var observer = c_XflowObserverList[i];
            var records = [];
            for(var j = 0; j < observer.observed.length; ++j){
                var entry = observer.observed[j];
                if(entry.changed){
                    entry.changed = false;
                    var result = entry.request.getResult();
                    var dataResult = new XML3DDataResult(result);
                    records.push( new XML3DDataRecord(entry.node, dataResult));
                }
            }
            if(records.length > 0 && observer.callback){
                observer.callback(records, observer);
            }
        }
    }

    var XML3DDataRecord = function(target, result){
        this.target = target;
        this.result = result;
    }
    window.XML3DDataRecord = XML3DDataRecord;

    var XML3DDataResult = function(result){
        this._entries = {};
        constructDataResult(this, result);
    }
    window.XML3DDataResult = XML3DDataResult;

    XML3DDataResult.prototype.getValue = function(name) {
        if (this._entries[name])
            return this._entries[name].value;
        return null;
    }

    XML3DDataResult.prototype.getType = function(name) {
        if (this._entries[name])
            return this._entries[name].type;
        return null;
    }

    XML3DDataResult.prototype.getNames = function(){
        var result = [];
        for(var name in this._entries){
            result.push(name);
        }
        return result;
    }

    XML3DDataResult.FLOAT  = 0;
    XML3DDataResult.FLOAT2 = 1;
    XML3DDataResult.FLOAT3 = 2;
    XML3DDataResult.FLOAT4 = 3;
    XML3DDataResult.FLOAT4X4 = 4;
    XML3DDataResult.INT = 10;
    XML3DDataResult.INT4 = 11;
    XML3DDataResult.BOOL = 20;
    XML3DDataResult.TEXTURE = 30;
    XML3DDataResult.BYTE = 40;
    XML3DDataResult.UBYTE = 50;

    function constructDataResult(dataResult, result){
        for(var i = 0; i < result.outputNames.length; ++i){
            var name = result.outputNames[i];
            var entry = result.getOutputData(name);
            var value = entry && entry.getValue();
            if (value !== null) {
                var type = getXML3DDataType(entry.type);
                dataResult._entries[name] = { type: type, value: value};
            }
        }
    }

    function getXML3DDataType(type){
        switch(type){
            case Xflow.DATA_TYPE.FLOAT : return XML3DDataResult.FLOAT;
            case Xflow.DATA_TYPE.FLOAT2 : return XML3DDataResult.FLOAT2;
            case Xflow.DATA_TYPE.FLOAT3 : return XML3DDataResult.FLOAT3;
            case Xflow.DATA_TYPE.FLOAT4 : return XML3DDataResult.FLOAT4;
            case Xflow.DATA_TYPE.FLOAT4X4 : return XML3DDataResult.FLOAT4X4;
            case Xflow.DATA_TYPE.INT : return XML3DDataResult.INT;
            case Xflow.DATA_TYPE.INT4 : return XML3DDataResult.INT4;
            case Xflow.DATA_TYPE.BOOL : return XML3DDataResult.BOOL;
            case Xflow.DATA_TYPE.TEXTURE : return XML3DDataResult.TEXTURE;
            case Xflow.DATA_TYPE.BYTE : return XML3DDataResult.BYTE;
            case Xflow.DATA_TYPE.UBYTE : return XML3DDataResult.UBYTE;
            default: throw new Error("WHAT IS THIS I DON'T EVEN...");
        }
    }

    var XML3DDataChannelInfo = function(type, origin, originalName, seqLength, seqMinKey, seqMaxKey){
        this.type = getXML3DDataType(type);
        this.origin = origin;
        this.originalName = originalName;
        this.seqLength = seqLength;
        this.seqMinKey = seqMinKey;
        this.seqMaxKey = seqMaxKey;
    }
    window.XML3DDataChannelInfo = XML3DDataChannelInfo;

    XML3DDataChannelInfo.ORIGIN_CHILD = 1;
    XML3DDataChannelInfo.ORIGIN_COMPUTE = 2;
    XML3DDataChannelInfo.ORIGIN_PROTO = 3;

}(XML3D._native));
<|endoftext|>


<commit_before>/**
 * @fileoverview
 * Classes related to defining settings for views. Settings are dict-like
 * objects, providing get/set/exists methods. Settings are persisted by
 * calling out to the global storage ogject
 *
 * @see storage
 */

/**
 * @constructor
 */
var SettingsBase = function()
{
  /**
   * Update the view according to the new value of the setting key.
   * @private
   */
  var syncView = function(key, value)
  {
    var
    switches = document.getElementsByTagName('toolbar-switches'),
    _switch = null,
    butttons = null,
    button = null,
    i = 0,
    j = 0,
    key_id = this.view_id + '.' + key,
    force_reflow = false;

    for( ; _switch = switches[i]; i++)
    {
      force_reflow = false;
      buttons = _switch.getElementsByTagName('button');
      for( j = 0; button = buttons[j]; j++)
      {
        if( button.getAttribute('key') == key_id )
        {
          button.setAttribute('is-active' , value ? "true" : "false" );
          force_reflow = true;
        }
      }
      if( force_reflow )
      {
        _switch.innerHTML += "";
      }
    }
  }

  /**
   * Set the value of key.
   */
  this.set = function(key, value, sync_switches)
  {
    window.localStorage.setItem(key, JSON.stringify(this.map[key] = value))
    if( sync_switches && typeof value == 'boolean' )
    {
      syncView(key, value);
    }
  }

  /**
   * Returns the value assosciated with "key". If the key does not exist,
   * returns undefined
   * @argument {string} key whos value to get
   */
  this.get = function(key)
  {
    var val = "";
    return (
      this.map[key] ||
      (this.map[key] = ((val = window.localStorage.getItem(key)) ? JSON.parse(val) : null))
    );
  }

  /**
   * Check if a particular key exist in the settings object
   */
  this.exists = function(key)
  {
    return key in this.map;
  }

  this.init = function(view_id, key_map, label_map, setting_map, templates, group)
  {
    this.map = {};
    this.view_id = view_id;
    this.label_map = label_map;
    this.setting_map = setting_map;
    this.templates = templates || {};
    this.group = group;
    var stored_map = key_map, key = '', val = '';
    for( key in stored_map)
    {
      val = window.localStorage.getItem(key);
      this.map[key] = (val === undefined || val === null) ? key_map[key] : JSON.parse(val);
    }
    if(!window.settings)
    {
      window.settings = {};
    }
    window.settings[arguments[0]] = this;

    // Add a context menu
    var contextmenu = setting_map.contextmenu;
    if (contextmenu)
    {
      var items = [];
      for (var i = 0, item; item = contextmenu[i]; i++)
      {
        items.push({
          label: label_map[item],
          id: item,
          setting: true,
          handler: function(event, target) {
            var item = target.getAttribute("data-handler-id");
            settings[view_id].set(item, !settings[view_id].get(item), true);
            views[view_id].update();
          }
        });
      }
      ContextMenu.register(view_id, items);
    }
  }

  if(!window.localStorage)
  {
    window.localStorage =
    {
      setItem: function(name, value)
      {
        document.cookie = name + "="+
          encodeURIComponent(value)+
          "; expires="+(new Date(new Date().getTime()+ (360 * 24 * 60 * 60 * 1000 ))).toGMTString()+
          "; path=/";
      },
      getItem: function(name)
      {
        var match = null;
        if (match = new RegExp(name+'\=([^;]*);','').exec(document.cookie+';'))
        {
          return decodeURIComponent(match[1]);
        }
        return null;
      }
    }
  }
}

/**
 * @constructor
 * @extends SettingsBase
 */
var Settings = function(view_id, key_map, label_map, setting_map, template, group)
{
  this.init(view_id, key_map, label_map, setting_map, template, group);
}

Settings.get_setting_with_view_key_token = function(token)
{
  var arr = token.split('.'), setting = window.settings[arr[0]], key = arr[1];
  return setting && setting.exists(key) && {
      setting: setting,
      view: arr[0],
      key: key,
      value: setting.get(key),
      label: setting.label_map[key]
    } || null;
}

/**
 * Get all settings belonging to the group `group`
 */
Settings.get_settings_by_group = function(group)
{
    //var group_settings = {};
    var group_settings = [];
    var settings = window.settings;
    for (var setting in settings)
    {
        if (settings[setting].group == group)
        {
            //group_settings[setting] = settings[setting];
            group_settings.push(setting);
        }
    }
    return group_settings;
};

Settings.prototype = new SettingsBase();

/**
 * A group for settings.
 *
 * @param label The visible label for this group
 * @param group_name The name of the group that a Setting can be added to
 */
var SettingsGroup = function(label, group_name)
{
    this.label = label;
    this.group_name = group_name;

    SettingsGroup.groups.push(this);
}

SettingsGroup.groups = [];

<commit_msg>Check for object existance.<commit_after>/**
 * @fileoverview
 * Classes related to defining settings for views. Settings are dict-like
 * objects, providing get/set/exists methods. Settings are persisted by
 * calling out to the global storage ogject
 *
 * @see storage
 */

/**
 * @constructor
 */
var SettingsBase = function()
{
  /**
   * Update the view according to the new value of the setting key.
   * @private
   */
  var syncView = function(key, value)
  {
    var
    switches = document.getElementsByTagName('toolbar-switches'),
    _switch = null,
    butttons = null,
    button = null,
    i = 0,
    j = 0,
    key_id = this.view_id + '.' + key,
    force_reflow = false;

    for( ; _switch = switches[i]; i++)
    {
      force_reflow = false;
      buttons = _switch.getElementsByTagName('button');
      for( j = 0; button = buttons[j]; j++)
      {
        if( button.getAttribute('key') == key_id )
        {
          button.setAttribute('is-active' , value ? "true" : "false" );
          force_reflow = true;
        }
      }
      if( force_reflow )
      {
        _switch.innerHTML += "";
      }
    }
  }

  /**
   * Set the value of key.
   */
  this.set = function(key, value, sync_switches)
  {
    window.localStorage.setItem(key, JSON.stringify(this.map[key] = value))
    if( sync_switches && typeof value == 'boolean' )
    {
      syncView(key, value);
    }
  }

  /**
   * Returns the value assosciated with "key". If the key does not exist,
   * returns undefined
   * @argument {string} key whos value to get
   */
  this.get = function(key)
  {
    var val = "";
    return (
      this.map[key] ||
      (this.map[key] = ((val = window.localStorage.getItem(key)) ? JSON.parse(val) : null))
    );
  }

  /**
   * Check if a particular key exist in the settings object
   */
  this.exists = function(key)
  {
    return key in this.map;
  }

  this.init = function(view_id, key_map, label_map, setting_map, templates, group)
  {
    this.map = {};
    this.view_id = view_id;
    this.label_map = label_map;
    this.setting_map = setting_map;
    this.templates = templates || {};
    this.group = group;
    var stored_map = key_map, key = '', val = '';
    for( key in stored_map)
    {
      val = window.localStorage.getItem(key);
      this.map[key] = (val === undefined || val === null) ? key_map[key] : JSON.parse(val);
    }
    if(!window.settings)
    {
      window.settings = {};
    }
    window.settings[arguments[0]] = this;

    // Add a context menu
    var contextmenu = setting_map && setting_map.contextmenu;
    if (contextmenu)
    {
      var items = [];
      for (var i = 0, item; item = contextmenu[i]; i++)
      {
        items.push({
          label: label_map[item],
          id: item,
          setting: true,
          handler: function(event, target) {
            var item = target.getAttribute("data-handler-id");
            settings[view_id].set(item, !settings[view_id].get(item), true);
            views[view_id].update();
          }
        });
      }
      ContextMenu.register(view_id, items);
    }
  }

  if(!window.localStorage)
  {
    window.localStorage =
    {
      setItem: function(name, value)
      {
        document.cookie = name + "="+
          encodeURIComponent(value)+
          "; expires="+(new Date(new Date().getTime()+ (360 * 24 * 60 * 60 * 1000 ))).toGMTString()+
          "; path=/";
      },
      getItem: function(name)
      {
        var match = null;
        if (match = new RegExp(name+'\=([^;]*);','').exec(document.cookie+';'))
        {
          return decodeURIComponent(match[1]);
        }
        return null;
      }
    }
  }
}

/**
 * @constructor
 * @extends SettingsBase
 */
var Settings = function(view_id, key_map, label_map, setting_map, template, group)
{
  this.init(view_id, key_map, label_map, setting_map, template, group);
}

Settings.get_setting_with_view_key_token = function(token)
{
  var arr = token.split('.'), setting = window.settings[arr[0]], key = arr[1];
  return setting && setting.exists(key) && {
      setting: setting,
      view: arr[0],
      key: key,
      value: setting.get(key),
      label: setting.label_map[key]
    } || null;
}

/**
 * Get all settings belonging to the group `group`
 */
Settings.get_settings_by_group = function(group)
{
    //var group_settings = {};
    var group_settings = [];
    var settings = window.settings;
    for (var setting in settings)
    {
        if (settings[setting].group == group)
        {
            //group_settings[setting] = settings[setting];
            group_settings.push(setting);
        }
    }
    return group_settings;
};

Settings.prototype = new SettingsBase();

/**
 * A group for settings.
 *
 * @param label The visible label for this group
 * @param group_name The name of the group that a Setting can be added to
 */
var SettingsGroup = function(label, group_name)
{
    this.label = label;
    this.group_name = group_name;

    SettingsGroup.groups.push(this);
}

SettingsGroup.groups = [];

<|endoftext|>