<commit_before>function pageViewModel(gvm) {
    // Page specific i18n bindings
    gvm.title = ko.computed(function(){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("AdminPage");}, gvm);
    gvm.pageHeader = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserTitle");}, gvm);

    gvm.userName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserName");}, gvm);
    gvm.firstName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("Firstname");}, gvm);
    gvm.lastName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("Lastname");}, gvm);
    gvm.userStatus = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserStatus");}, gvm);
    gvm.userActions = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserActions");}, gvm);

    fetchUsersData();

    gvm.users = ko.observableArray([]);

    gvm.updateUsers = function(user)
    {
        gvm.users.push(user);
        console.log(user);
    }
}

function fetchUsersData()
{
    $.getJSON("/api/allusers/", function(data)
    {
        $.each(data, function(i, item){
            viewModel.updateUsers(new User(item.id, item.username, item.firstname, item.lastname, item.status));
        });
    });
}

function User(viewmodel, id, username, firstname, lastname, status) {
    return {
        id: ko.observable(id),
        userMail: ko.observable(username),
        firstname: ko.observable(firstname),
        lastname: ko.observable(lastname),
        status: ko.observable(status)
    };
}<commit_msg>Admin page: get All users<commit_after>function pageViewModel(gvm) {
    // Page specific i18n bindings
    gvm.title = ko.computed(function(){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("AdminPage");}, gvm);
    gvm.pageHeader = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserTitle");}, gvm);

    gvm.userName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserName");}, gvm);
    gvm.firstName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("Firstname");}, gvm);
    gvm.lastName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("Lastname");}, gvm);
    gvm.userStatus = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserStatus");}, gvm);
    gvm.userActions = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserActions");}, gvm);

    fetchUsersData();

    gvm.users = ko.observableArray([]);

    gvm.updateUsers = function(user)
    {
        gvm.users.push(user);
        console.log(user);
    }
}

function fetchUsersData()
{
    $.getJSON("/api/allusers/", function(data)
    {
        $.each(data, function(i, item){
            console.log(item.userName);
            viewModel.updateUsers(new User(item.id, item.userName, item.firstname, item.lastname, item.status));
        });
    });
}

function User(viewmodel, id, username, firstname, lastname, status) {
    return {
        id: ko.observable(id),
        userMail: ko.observable(username),
        firstname: ko.observable(firstname),
        lastname: ko.observable(lastname),
        status: ko.observable(status)
    };
}<|endoftext|>


<commit_before>function pageViewModel(gvm) {
    // Page specific i18n bindings
    gvm.title = ko.computed(function(){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("AdminPage");}, gvm);
    gvm.pageHeader = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserTitle");}, gvm);

    gvm.userName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserName");}, gvm);
    gvm.firstName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("Firstname");}, gvm);
    gvm.lastName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("Lastname");}, gvm);
    gvm.userStatus = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserStatus");}, gvm);
    gvm.userActions = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserActions");}, gvm);

    fetchUsersData();

    gvm.users = ko.observableArray([]);

    gvm.updateUsers = function(user)
    {
        gvm.users.push(user);
        console.log(user);
    }
}

function fetchUsersData()
{
    $.getJSON("/api/allusers/", function(data)
    {
        $.each(data, function(i, item){
            console.log(item.username);
            viewModel.updateUsers(new User(item.id, item.username, item.firstname, item.lastname, item.status));
        });
    });
}

function User(viewmodel, id, username, firstname, lastname, status) {
    return {
        id: ko.observable(id),
        username: ko.observable(username),
        firstname: ko.observable(firstname),
        lastname: ko.observable(lastname),
        status: ko.observable(status)
    };
}<commit_msg>Admin page: get All users<commit_after>function pageViewModel(gvm) {
    // Page specific i18n bindings
    gvm.title = ko.computed(function(){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("AdminPage");}, gvm);
    gvm.pageHeader = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserTitle");}, gvm);

    gvm.userName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserName");}, gvm);
    gvm.firstName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("Firstname");}, gvm);
    gvm.lastName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("Lastname");}, gvm);
    gvm.userStatus = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserStatus");}, gvm);
    gvm.userActions = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserActions");}, gvm);

    fetchUsersData();

    gvm.users = ko.observableArray([]);

    gvm.updateUsers = function(user)
    {
        gvm.users.push(user);
        console.log(user);
    }
}

function fetchUsersData()
{
    $.getJSON("/api/allusers/", function(data)
    {
        $.each(data, function(i, item){
            console.log(item.username);
            viewModel.updateUsers(new User(item.id, item.username, item.firstname, item.lastname, item.status));
        });
    });
}

function User(id, username, firstname, lastname, status) {
    return {
        id: ko.observable(id),
        username: ko.observable(username),
        firstname: ko.observable(firstname),
        lastname: ko.observable(lastname),
        status: ko.observable(status)
    };
}<|endoftext|>


<commit_before>/** kate-script
 * name: XML Style
 * license: LGPL
 * author: Milian Wolff <mail@milianw.de>
 * revision: 1
 * kate-version: 3.4
 * type: indentation
 *
 * This file is part of the Kate Project.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License version 2 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

var debugMode = true;

// specifies the characters which should trigger indent, beside the default '\n'
triggerCharacters = "/>";

function dbg(s) {
    if (debugMode)
        debug(s);
}

/**
 * Process a newline character.
 * This function is called whenever the user hits <return/enter>.
 *
 * It gets three arguments: line, indentwidth in spaces, typed character.
 */
function indent(line, indentWidth, char)
{
    var prefLineString = line ? document.line(line-1) : "";
    var prefIndent = document.firstVirtualColumn(line-1);
    var lineString = document.line(line);

    var alignOnly = (char == "");
    if (alignOnly) {
        // XML might be all in one line, in which case we
        // want to break that up.
        var tokens = lineString.split(/>\s*</);
        if (tokens.length > 1) {
            var oldLine = line;
            var oldPrefIndent = prefIndent;
            for (var l in tokens) {
                var newLine = tokens[l];
                if (l > 0) {
                    newLine = '<' + newLine;
                }
                if (l < tokens.length - 1) {
                    newLine += '>';
                }

                if (newLine.match(/^\s*<\//)) {
                    char = '/';
                } else if (newLine.match(/\>[^<>]*$/)) {
                    char = '>';
                } else {
                    char = '\n';
                }
                var indentation = processChar(line, newLine, prefLineString, prefIndent, char, indentWidth);
                prefIndent = indentation;
                while (indentation > 0) {
                    //TODO: what about tabs
                    newLine = " " + newLine;
                    --indentation;
                }
                ++line;
                prefLineString = newLine;
                tokens[l] = newLine;
            }
            dbg(tokens.join('\n'));
            document.editBegin();
            document.removeLine(oldLine);
            document.insertText(oldLine, 0, tokens.join('\n'));
            document.editEnd();
            return oldPrefIndent;
        } else {
            if (lineString.match(/^\s*<\//)) {
                char = '/';
            } else if (lineString.match(/\>[^<>]*$/)) {
                char = '>';
            }
        }
    }

    return processChar(line, lineString, prefLineString, prefIndent, char, indentWidth);
}

function processChar(line, lineString, prefLineString, prefIndent, char, indentWidth)
{
    if (char == '/') {
        if (lineString.match(/^\s*<\//) && !prefLineString.match(/<[^/>]+>[^<>]*$/)) {
            // decrease indent when we write </ and prior line did not start a tag
            return prefIndent - indentWidth;
        }
    } else if (char == '>') {
        // increase indent width when we write <...> or <.../> but not </...>
        // and the prior line didn't close a tag
        if (line == 0) {
            return 0;
        } else if (prefLineString.match(/^<\?xml/)) {
            return 0;
        } else if (lineString.match(/^\s*<\//)) {
            // closing tag, decrease indentation when previous didn't open tag
            if (prefLineString.match(/<[^\/>]+>[^<>]*$/)) {
                // keep indent when pref line closed a tag
                return prefIndent;
            } else {
                return prefIndent - indentWidth;
            }
        } else if (prefLineString.match(/<\/[^\/>]+>\s*$/)) {
            // keep indent when pref line closed a tag
            return prefIndent;
        }
        return prefIndent + indentWidth;
    } else if (char == '\n') {
        if (prefLineString.match(/<[^\/>]+>[^<>]*$/)) {
            // increase indent when pref line opened a tag
            return prefIndent + indentWidth;
        }
    }

    return prefIndent;
}

// kate: space-indent on; indent-width 4; replace-tabs on;<commit_msg>skip empty lines when looking for previous one<commit_after>/** kate-script
 * name: XML Style
 * license: LGPL
 * author: Milian Wolff <mail@milianw.de>
 * revision: 1
 * kate-version: 3.4
 * type: indentation
 *
 * This file is part of the Kate Project.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License version 2 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

var debugMode = true;

// specifies the characters which should trigger indent, beside the default '\n'
triggerCharacters = "/>";

function dbg(s) {
    if (debugMode)
        debug(s);
}

/**
 * Process a newline character.
 * This function is called whenever the user hits <return/enter>.
 *
 * It gets three arguments: line, indentwidth in spaces, typed character.
 */
function indent(line, indentWidth, char)
{
    var prefLine = line - 1;
    var prefLineString = " ";
    while (prefLine > 0 && prefLineString.match(/^\s+$/)) {
        prefLine--;
        prefLineString = document.line(prefLine);
    }
    var prefIndent = document.firstVirtualColumn(prefLine);
    var lineString = document.line(line);

    var alignOnly = (char == "");
    if (alignOnly) {
        // XML might be all in one line, in which case we
        // want to break that up.
        var tokens = lineString.split(/>\s*</);
        if (tokens.length > 1) {
            var oldLine = line;
            var oldPrefIndent = prefIndent;
            for (var l in tokens) {
                var newLine = tokens[l];
                if (l > 0) {
                    newLine = '<' + newLine;
                }
                if (l < tokens.length - 1) {
                    newLine += '>';
                }

                if (newLine.match(/^\s*<\//)) {
                    char = '/';
                } else if (newLine.match(/\>[^<>]*$/)) {
                    char = '>';
                } else {
                    char = '\n';
                }
                var indentation = processChar(line, newLine, prefLineString, prefIndent, char, indentWidth);
                prefIndent = indentation;
                while (indentation > 0) {
                    //TODO: what about tabs
                    newLine = " " + newLine;
                    --indentation;
                }
                ++line;
                prefLineString = newLine;
                tokens[l] = newLine;
            }
            dbg(tokens.join('\n'));
            document.editBegin();
            document.removeLine(oldLine);
            document.insertText(oldLine, 0, tokens.join('\n'));
            document.editEnd();
            return oldPrefIndent;
        } else {
            if (lineString.match(/^\s*<\//)) {
                char = '/';
            } else if (lineString.match(/\>[^<>]*$/)) {
                char = '>';
            }
        }
    }

    return processChar(line, lineString, prefLineString, prefIndent, char, indentWidth);
}

function processChar(line, lineString, prefLineString, prefIndent, char, indentWidth)
{
    if (char == '/') {
        if (lineString.match(/^\s*<\//) && !prefLineString.match(/<[^/>]+>[^<>]*$/)) {
            // decrease indent when we write </ and prior line did not start a tag
            return prefIndent - indentWidth;
        }
    } else if (char == '>') {
        // increase indent width when we write <...> or <.../> but not </...>
        // and the prior line didn't close a tag
        if (line == 0) {
            return 0;
        } else if (prefLineString.match(/^<\?xml/)) {
            return 0;
        } else if (lineString.match(/^\s*<\//)) {
            // closing tag, decrease indentation when previous didn't open tag
            if (prefLineString.match(/<[^\/>]+>[^<>]*$/)) {
                // keep indent when pref line closed a tag
                return prefIndent;
            } else {
                return prefIndent - indentWidth;
            }
        } else if (prefLineString.match(/<\/[^\/>]+>\s*$/)) {
            // keep indent when pref line closed a tag
            return prefIndent;
        }
        return prefIndent + indentWidth;
    } else if (char == '\n') {
        if (prefLineString.match(/<[^\/>]+>[^<>]*$/)) {
            // increase indent when pref line opened a tag
            return prefIndent + indentWidth;
        }
    }

    return prefIndent;
}

// kate: space-indent on; indent-width 4; replace-tabs on;<|endoftext|>


<commit_before>var _     = require('underscore'),
    Hours = require('cloud/models/hours');

module.exports = Parse.Object.extend('Facility', {
  // no gender restriction or gender == self.gender
  matchesGender: function(targetGender) {
    var g = this.get('gender');

    if ( !g || !targetGender )  {
      return true;
    } else {
      return g.toUpperCase() == targetGender.toUpperCase();
    }
  },

  presentJSON: function() {
    var asJSON = this.toJSON();
    asJSON.services = _.map(this.get("services"), function(service) {
      return service.toJSON();
    });
    asJSON.demographics = this.demographics();
    return asJSON;
  },

  matchesAges: function(ages) {
    var a = this.get('age');
    if ( !ages || !a ) {
      return true;
    }

    return _.any(_.compact(ages), function(targetAge) {
      return _.include(a, targetAge);
    });
  },

  matchesFilter: function(filter) {
    var match = true;

    if ( !filter ) {
      return true;
    }

    match &= this.matchesGender(filter.gender);
    match &= this.matchesAges(filter.age);
    match &= this.hasServiceInCategories(filter.categories);
    return match;
  },

  hasStatus: function() {
    return (this.status() !== 'UNKNOWN');
  },

  status: function() {
    var hours, open;

    if ( this._status ) return this._status;

    // wrap in try because we may not have data or the data may not parse
    try {
      hours = this.hours();
      open = hours.within(new Date());
    } catch (e) {
      console.log(e);
      this._status = 'UNKNOWN';
    }

    if ( open ) {
      this._status = 'OPEN';
    } else {
      this._status = 'CLOSED';
    }

    return this._status;
  },

  hours: function() {
    if(this._hours) { return this._hours; }
    this._hours = new Hours(this.get('hours'));
  },

  hasServiceInCategories: function(categories) {
    if ( !categories )  {
      return true;
    }

    var services = this.get('services');
    return _.any(_.compact(categories), function(targetCategory) {
      return _.any(services, function(facService) {
        return facService.get("category") === targetCategory;
      });
    });
  },

  age_as_string: function(input) {
    switch ( input.toUpperCase() ) {
      case "C":
        return "children";
      case "Y":
        return "teens";
      case "A":
        return "adults";
      case "S":
        return "seniors";
    }
  },

  demographics: function() {
    var g, a, output = "";
    if ( !this.get('age') && !this.get('gender') ) {
      output = "Anyone";
    } else {
      if ( (g = this.get('gender') ) ) {
        if ( this.get('age') ) {
          output = g.toUpperCase() == "F" ? "Female " : "Male ";
        } else {
          output = "Only " + g.toUpperCase() == "F" ? "women" : "men";
        }
      } else {
        output = "All ";
      }

      if ( (a = this.get('age') ) ) {
        // C-Y-A-S
        var translated = _(a).map(this.age_as_string);
        output += _(translated).join(", ");
      }
    }
    return output;
  }
});
<commit_msg>return _hours from Facility#hours<commit_after>var _     = require('underscore'),
    Hours = require('cloud/models/hours');

module.exports = Parse.Object.extend('Facility', {
  // no gender restriction or gender == self.gender
  matchesGender: function(targetGender) {
    var g = this.get('gender');

    if ( !g || !targetGender )  {
      return true;
    } else {
      return g.toUpperCase() == targetGender.toUpperCase();
    }
  },

  presentJSON: function() {
    var asJSON = this.toJSON();
    asJSON.services = _.map(this.get("services"), function(service) {
      return service.toJSON();
    });
    asJSON.demographics = this.demographics();
    return asJSON;
  },

  matchesAges: function(ages) {
    var a = this.get('age');
    if ( !ages || !a ) {
      return true;
    }

    return _.any(_.compact(ages), function(targetAge) {
      return _.include(a, targetAge);
    });
  },

  matchesFilter: function(filter) {
    var match = true;

    if ( !filter ) {
      return true;
    }

    match &= this.matchesGender(filter.gender);
    match &= this.matchesAges(filter.age);
    match &= this.hasServiceInCategories(filter.categories);
    return match;
  },

  hasStatus: function() {
    return (this.status() !== 'UNKNOWN');
  },

  status: function() {
    var hours, open;

    if ( this._status ) return this._status;

    // wrap in try because we may not have data or the data may not parse
    try {
      hours = this.hours();
      open = hours.within(new Date());
    } catch (e) {
      console.log(e);
      this._status = 'UNKNOWN';
    }

    if ( open ) {
      this._status = 'OPEN';
    } else {
      this._status = 'CLOSED';
    }

    return this._status;
  },

  hours: function() {
    if(this._hours) { return this._hours; }
    this._hours = new Hours(this.get('hours'));
    return this._hours;
  },

  hasServiceInCategories: function(categories) {
    if ( !categories )  {
      return true;
    }

    var services = this.get('services');
    return _.any(_.compact(categories), function(targetCategory) {
      return _.any(services, function(facService) {
        return facService.get("category") === targetCategory;
      });
    });
  },

  age_as_string: function(input) {
    switch ( input.toUpperCase() ) {
      case "C":
        return "children";
      case "Y":
        return "teens";
      case "A":
        return "adults";
      case "S":
        return "seniors";
    }
  },

  demographics: function() {
    var g, a, output = "";
    if ( !this.get('age') && !this.get('gender') ) {
      output = "Anyone";
    } else {
      if ( (g = this.get('gender') ) ) {
        if ( this.get('age') ) {
          output = g.toUpperCase() == "F" ? "Female " : "Male ";
        } else {
          output = "Only " + g.toUpperCase() == "F" ? "women" : "men";
        }
      } else {
        output = "All ";
      }

      if ( (a = this.get('age') ) ) {
        // C-Y-A-S
        var translated = _(a).map(this.age_as_string);
        output += _(translated).join(", ");
      }
    }
    return output;
  }
});
<|endoftext|>


<commit_before>var Backbone = require('backbone'),
    $ = require('jquery'),
    Query = require('lib/query'),
    facilities = require('collections/facilities').instance;

var ListView = Backbone.View.extend({
  el: $("#linksf"),
  template: require('templates/list'),

  submitQuery: function() {
    // serialize the form
    var params = $('#query').serializeObject(),
        self = this;

    // submit query

    Query.submit(params).done(function(results) {
      // populate with results
      facilities.reset(results.data);
    });

    // prevent default form submission
    return false;
  },

  render: function() {
    var jsonFacilities = this.collection.toJSON();

    // replace with template
    $(this.el).html(this.template({ facilities: jsonFacilities }));

    // bind to form submission
    $('#query').submit($.proxy(this.submitQuery, this));
    return this;
  }
});

module.exports = ListView;
<commit_msg>introducing... technical debt<commit_after>var Backbone = require('backbone'),
    $ = require('jquery'),
    Query = require('lib/query'),
    _ = require('underscore'),
    facilities = require('collections/facilities').instance;

var ListView = Backbone.View.extend({
  el: $("#linksf"),
  template: require('templates/list'),

  submitQuery: function() {
    // serialize the form
    var params = $('#query').serializeObject(),
        self = this;

    // submit query

    Query.submit(params).done(function(results) {
      // populate with results
      facilities.reset(results.data);
    });

    // prevent default form submission
    return false;
  },

  render: function() {
    // console.log(this.collection);
    // var jsonFacilities = this.collection.toJSON();
    var deepJson = this.deepToJson(this.collection);
    var templateJson = this.flattenServices(deepJson);

    console.log(templateJson);
    // replace with template
    $(this.el).html(this.template({ facilities: templateJson }));
    $('#query').hide();

    // bind to form submission
    $('#query').submit($.proxy(this.submitQuery, this));
    return this;
  },

  deepToJson: function(collection) {
    var json = [],
        modelJson;

    json = _.map(collection.models, function(model) {
      modelJson = model.toJSON();
      modelJson.services = [];
      _.each(model.attributes.services, function(service) {
        modelJson.services.push(service.toJSON());
      });
      return modelJson;
    });

    return json;
  },

  flattenServices: function(jsonArray) {
    var serviceCategories,
        allNotes,
        flattened = [];

    _.each(jsonArray, function(jsonModel) {
      serviceCategories = [];
      allNotes = [];
      _.each(jsonModel.services, function(jsonService) {
        serviceCategories.push(jsonService.category);
        allNotes.push(jsonService.notes);
      });
      jsonModel.serviceCategories = serviceCategories.join(', ');
      jsonModel.allNotes = allNotes.join();
      flattened.push(jsonModel);
    });

    return flattened;
  }
});

module.exports = ListView;
<|endoftext|>


<commit_before>/* =============================================================
 * bootstrap-typeahead.js v2.0.0
 * http://twitter.github.com/bootstrap/javascript.html#collapsible
 * =============================================================
 * Copyright 2012 Twitter, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ============================================================ */

!function( $ ){

  "use strict"

  var Typeahead = function ( element, options ) {
    this.$element = $(element)
    this.options = $.extend({}, $.fn.typeahead.defaults, options)
    this.$menu = $(this.options.menu).appendTo('body')
    this.data = this.options.data
    this.shown = false
    this.listen()
  }

  Typeahead.prototype = {

    constructor: Typeahead

  , matcher: function (item, query) {
      // ;_; http://jsperf.com/asdfdfasdfa
      return ~item.toLowerCase().indexOf(query.toLowerCase())
    }

  , select: function () {
      var val = this.$menu.find('.active').attr('data-value')
      this.$element.val(val)
      return this.hide()
    }

  , show: function () {
      var pos = $.extend({}, this.$element.offset(), {
        height: this.$element[0].offsetHeight
      })

      this.$menu.css({
        top: pos.top + pos.height
      , left: pos.left
      })

      this.$menu.show()
      this.shown = true
      return this
    }

  , hide: function () {
      this.$menu.hide()
      this.shown = false
      return this
    }

  , lookup: function (event) {
      var query = this.$element.val()
        , that = this
        , items

      if (!query) {
        return this.shown ? this.hide() : this
      }

      items = this.data.filter(function (item) {
        if (that.matcher(item, query)) return item
      })

      if (!items.length) {
        return this.shown ? this.hide() : this
      }

      return this.render(items.slice(0, this.options.items)).show()
    }

  , render: function (items) {
      var that = this

      items = $(items).map(function (i, item) {
        i = $(that.options.item).attr('data-value', item)
        i.find('a').text(item)
        return i[0]
      })

      items.first().addClass('active')
      this.$menu.html(items)
      return this
    }

  , next: function (event) {
      var active = this.$menu.find('.active').removeClass('active')
        , next = active.next()

      if (!next.length) {
        next = $(this.$menu.find('li')[0])
      }

      next.addClass('active')
    }

  , prev: function (event) {
      var active = this.$menu.find('.active').removeClass('active')
        , prev = active.prev()

      if (!prev.length) {
        prev = this.$menu.find('li').last()
      }

      prev.addClass('active')
    }

  , keyup: function (e) {
      e.stopPropagation()
      e.preventDefault()

      switch(e.keyCode) {
        case 40: // down arrow
        case 38: // up arrow
          break

        case 9: // tab
        case 13: // enter
          this.select()
          break

        case 27: // escape
          this.hide()
          break

        default:
          this.lookup()
      }

  }

  , keypress: function (e) {
      e.stopPropagation()

      switch(e.keyCode) {
        case 9: // tab
        case 13: // enter
        case 27: // escape
          e.preventDefault()
          break

        case 38: // up arrow
          if (!this.shown) return
          e.preventDefault()
          this.prev()
          break

        case 40: // down arrow
          if (!this.shown) return
          e.preventDefault()
          this.next()
          break
      }
    }

  , blur: function (e) {
      var that = this
      e.stopPropagation()
      e.preventDefault()
      setTimeout(function () { that.hide() }, 150)
    }

  , click: function (e) {
      e.stopPropagation()
      e.preventDefault()
      this.select()
    }

  , mouseenter: function (e) {
      this.$menu.find('.active').removeClass('active')
      $(e.currentTarget).addClass('active')
    }

  , listen: function () {
      this.$element
        .on('blur',     $.proxy(this.blur, this))
        .on('keypress', $.proxy(this.keypress, this))
        .on('keyup',    $.proxy(this.keyup, this))

      if ($.browser.webkit || $.browser.msie) {
        this.$element.on('keydown', $.proxy(this.keypress, this))
      }

      this.$menu
        .on('click', $.proxy(this.click, this))
        .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
    }
  }

  /* TYPEAHEAD PLUGIN DEFINITION
   * =========================== */

  $.fn.typeahead = function ( option ) {
    return this.each(function () {
      var $this = $(this)
        , data = $this.data('typeahead')
        , options = typeof option == 'object' && option
      if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  $.fn.typeahead.defaults = {
    items: 8
  , menu: '

'
  , item: '

'
  }

  $.fn.typeahead.Constructor = Typeahead

 /* TYPEAHEAD DATA-API
  * ================== */

  $(function () {
    $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
      var $this = $(this)
      if ($this.data('typeahead')) return
      e.preventDefault()
      $this.typeahead($this.data())
    })
  })

}( window.jQuery )<commit_msg>more efficient matcher + bold matched query in item<commit_after>/* =============================================================
 * bootstrap-typeahead.js v2.0.0
 * http://twitter.github.com/bootstrap/javascript.html#collapsible
 * =============================================================
 * Copyright 2012 Twitter, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ============================================================ */

!function( $ ){

  "use strict"

  var Typeahead = function ( element, options ) {
    this.$element = $(element)
    this.options = $.extend({}, $.fn.typeahead.defaults, options)
    this.$menu = $(this.options.menu).appendTo('body')
    this.data = this.options.data
    this.shown = false
    this.listen()
  }

  Typeahead.prototype = {

    constructor: Typeahead

  , matcher: function (item, query) {
      // ;_; http://jsperf.com/asdfdfasdfa
      return ~item.toLowerCase().indexOf(query)
    }

  , select: function () {
      var val = this.$menu.find('.active').attr('data-value')
      this.$element.val(val)
      return this.hide()
    }

  , show: function () {
      var pos = $.extend({}, this.$element.offset(), {
        height: this.$element[0].offsetHeight
      })

      this.$menu.css({
        top: pos.top + pos.height
      , left: pos.left
      })

      this.$menu.show()
      this.shown = true
      return this
    }

  , hide: function () {
      this.$menu.hide()
      this.shown = false
      return this
    }

  , lookup: function (event) {
      var that = this
        , items
        , q

      this.query = this.$element.val()

      if (!this.query) {
        return this.shown ? this.hide() : this
      }

      q = this.query.toLowerCase()

      items = this.data.filter(function (item) {
        if (that.matcher(item, q)) return item
      })

      if (!items.length) {
        return this.shown ? this.hide() : this
      }

      return this.render(items.slice(0, this.options.items)).show()
    }

  , render: function (items) {
      var that = this
        , QUERY = new RegExp('(' + this.query + ')', 'ig')

      items = $(items).map(function (i, item) {
        i = $(that.options.item).attr('data-value', item)

        i.find('a').html(item.replace(QUERY, function ($1, match) {
          return '' + match + ''
        }))

        return i[0]
      })

      items.first().addClass('active')
      this.$menu.html(items)
      return this
    }

  , next: function (event) {
      var active = this.$menu.find('.active').removeClass('active')
        , next = active.next()

      if (!next.length) {
        next = $(this.$menu.find('li')[0])
      }

      next.addClass('active')
    }

  , prev: function (event) {
      var active = this.$menu.find('.active').removeClass('active')
        , prev = active.prev()

      if (!prev.length) {
        prev = this.$menu.find('li').last()
      }

      prev.addClass('active')
    }

  , listen: function () {
      this.$element
        .on('blur',     $.proxy(this.blur, this))
        .on('keypress', $.proxy(this.keypress, this))
        .on('keyup',    $.proxy(this.keyup, this))

      if ($.browser.webkit || $.browser.msie) {
        this.$element.on('keydown', $.proxy(this.keypress, this))
      }

      this.$menu
        .on('click', $.proxy(this.click, this))
        .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
    }

  , keyup: function (e) {
      e.stopPropagation()
      e.preventDefault()

      switch(e.keyCode) {
        case 40: // down arrow
        case 38: // up arrow
          break

        case 9: // tab
        case 13: // enter
          this.select()
          break

        case 27: // escape
          this.hide()
          break

        default:
          this.lookup()
      }

  }

  , keypress: function (e) {
      e.stopPropagation()

      switch(e.keyCode) {
        case 9: // tab
        case 13: // enter
        case 27: // escape
          e.preventDefault()
          break

        case 38: // up arrow
          if (!this.shown) return
          e.preventDefault()
          this.prev()
          break

        case 40: // down arrow
          if (!this.shown) return
          e.preventDefault()
          this.next()
          break
      }
    }

  , blur: function (e) {
      var that = this
      e.stopPropagation()
      e.preventDefault()
      setTimeout(function () { that.hide() }, 150)
    }

  , click: function (e) {
      e.stopPropagation()
      e.preventDefault()
      this.select()
    }

  , mouseenter: function (e) {
      this.$menu.find('.active').removeClass('active')
      $(e.currentTarget).addClass('active')
    }

  }

  /* TYPEAHEAD PLUGIN DEFINITION
   * =========================== */

  $.fn.typeahead = function ( option ) {
    return this.each(function () {
      var $this = $(this)
        , data = $this.data('typeahead')
        , options = typeof option == 'object' && option
      if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  $.fn.typeahead.defaults = {
    items: 8
  , menu: '

'
  , item: '

'
  }

  $.fn.typeahead.Constructor = Typeahead

 /* TYPEAHEAD DATA-API
  * ================== */

  $(function () {
    $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
      var $this = $(this)
      if ($this.data('typeahead')) return
      e.preventDefault()
      $this.typeahead($this.data())
    })
  })

}( window.jQuery )<|endoftext|>


<commit_before>// Copyright 2014-2020, University of Colorado Boulder

/**
 * Base class for button models, which describe the behavior of buttons when users interact with them.  Property values
 * are set by an associated listener, see PressListener for details.
 */

import BooleanProperty from '../../../axon/js/BooleanProperty.js';
import Emitter from '../../../axon/js/Emitter.js';
import Property from '../../../axon/js/Property.js';
import merge from '../../../phet-core/js/merge.js';
import PressListener from '../../../scenery/js/listeners/PressListener.js';
import PhetioObject from '../../../tandem/js/PhetioObject.js';
import Tandem from '../../../tandem/js/Tandem.js';
import EnabledComponent from '../EnabledComponent.js';
import sun from '../sun.js';

//TODO CREATE NEW ISSUE to talk about linkedElement and how to handle this button model business
class ButtonModel {

  /**
   * @mixes EnabledComponent
   * @param {Object} [options]
   */
  constructor( options ) {

    options = merge( {
      // {function()} called on pointer down
      startCallback: _.noop,
      // {function(over:boolean)} called on pointer up, @param {boolean} over - indicates whether the pointer was released over the button
      endCallback: _.noop,

      // phet-io
      tandem: Tandem.REQUIRED,
      phetioState: PhetioObject.DEFAULT_OPTIONS.phetioState, // to support properly passing this to children, see https://github.com/phetsims/tandem/issues/60
      phetioReadOnly: PhetioObject.DEFAULT_OPTIONS.phetioReadOnly, // to support properly passing this to children, see https://github.com/phetsims/tandem/issues/60
      phetioFeatured: PhetioObject.DEFAULT_OPTIONS.phetioFeatured // to support properly passing this to children, see https://github.com/phetsims/tandem/issues/60
    }, options );

    // Set up enabledPropertyOptions for the enabledProperty that the mixin might create
    options.enabledPropertyOptions = merge( {

      // phet-io
      phetioState: options.phetioState,
      phetioReadOnly: options.phetioReadOnly,
      phetioDocumentation: 'When disabled, the button is grayed out and cannot be pressed',
      phetioFeatured: true
    }, options.enabledPropertyOptions );

    // Initialize the mixin, which defines this.enabledProperty.
    this.initializeEnabledComponent( options );

    // model Properties
    this.overProperty = new BooleanProperty( false ); // @public - Is the pointer over the button?
    this.downProperty = new BooleanProperty( false, { reentrant: true } ); // @public - Is the pointer down?

    // @protected (read-only) - Is the button being clicked because of accessibility (like keyboard)?
    // See PressListener.a11yClickingProperty
    this.a11yClickingProperty = new BooleanProperty( false );

    // @public - This Property was added for a11y. It tracks whether or not the button should "look" down. This
    // will be true if downProperty is true or if an a11y click is in progress. For an a11y click, the listeners
    // are fired right away but the button will look down for as long as PressListener.a11yLooksPressedInterval.
    // See PressListener.click for more details.
    this.looksPressedProperty = new BooleanProperty( false );

    // @public (read-only by users, read-write in subclasses) - emitter that is fired when sound should be produced
    this.produceSoundEmitter = new Emitter();

    // @public (read-only) - indicates that interaction was interrupted during a press. Valid until next press.
    this.interrupted = false;

    // @private - keep track of and store all listeners this model creates
    this.listeners = [];

    // @private {Multilink|null} - Links all of the looksPressedProperties from the listeners that were created
    // by this ButtonModel, and updates the looksPressedProperty accordingly. First Multilink is added when the
    // first listener is created. See this.createListener.
    this.looksPressedMultilink = null;

    // startCallback on pointer down, endCallback on pointer up. lazyLink so they aren't called immediately.
    this.downProperty.lazyLink( down => {
      if ( down ) {
        options.startCallback();
      }
      else {
        options.endCallback( this.overProperty.get() );
      }
    } );

    // interrupt listeners when enabled is set to false
    this.enabledProperty.link( enabled => {
      if ( !enabled ) {
        for ( let i = 0; i < this.listeners.length; i++ ) {
          const listener = this.listeners[ i ];
          listener.interrupt && listener.interrupt();
        }
      }
    } );

    // @private
    this.disposeButtonModel = () => {

      // This will unlink all listeners, causing potential issues if listeners try to unlink Properties afterwards
      this.overProperty.dispose();
      this.downProperty.dispose();
      this.produceSoundEmitter.dispose();

      this.looksPressedMultilink && this.looksPressedMultilink.dispose();

      this.listeners = [];
    };
  }

  /**
   * @public
   */
  dispose() {
    this.disposeButtonModel();
    this.disposeEnabledComponent();
  }

  /**
   * is the button currently firing because of accessibility?
   * @public (a11y)
   * @returns {boolean}
   */
  isA11yClicking() {
    return this.a11yClickingProperty.get();
  }

  /**
   * Creates a standard button listener that can be added to a node and that will trigger the changes to this model.
   * @param {Object} [options]
   * @returns {PressListener}
   * @public
   */
  createListener( options ) {

    options = merge( {
      phetioDocumentation: 'Indicates when the button has been pressed or released',
      canStartPress: () => this.enabledProperty.value
    }, options );

    const pressListener = new PressListener( options );
    this.listeners.push( pressListener );

    // link lazily in case client externally sets downProperty - don't update until the next press
    pressListener.isPressedProperty.lazyLink( isPressed => {

      // determine interrupted first so listeners on downProperty have access
      this.interrupted = pressListener.interrupted;
      this.downProperty.set( isPressed );
    } );
    pressListener.isOverProperty.lazyLink( this.overProperty.set.bind( this.overProperty ) );
    pressListener.a11yClickingProperty.link( this.a11yClickingProperty.set.bind( this.a11yClickingProperty ) );

    // dispose the previous multilink in case we already created a PressListener with this model
    this.looksPressedMultilink && this.looksPressedMultilink.dispose();

    const looksPressedProperties = this.listeners.map( function( listener ) { return listener.looksPressedProperty; } );
    looksPressedProperties.push( this.downProperty );

    // assign a new Multilink (for disposal), and make sure that the button looks pressed when any of the
    // PressListeners created by this ButtonModel look pressed. Note that this cannot be an arrow function
    // because its implementation relies on arguments.
    const self = this;
    this.looksPressedMultilink = Property.multilink( looksPressedProperties, function() {
      self.looksPressedProperty.value = _.reduce( arguments, ( sum, newValue ) => sum || newValue, false );
    } );

    return pressListener;
  }
}

EnabledComponent.mixInto( ButtonModel );

sun.register( 'ButtonModel', ButtonModel );
export default ButtonModel;<commit_msg>delete TODO that is discussed in https://github.com/phetsims/sun/issues/515#issuecomment-709625808<commit_after>// Copyright 2014-2020, University of Colorado Boulder

/**
 * Base class for button models, which describe the behavior of buttons when users interact with them.  Property values
 * are set by an associated listener, see PressListener for details.
 */

import BooleanProperty from '../../../axon/js/BooleanProperty.js';
import Emitter from '../../../axon/js/Emitter.js';
import Property from '../../../axon/js/Property.js';
import merge from '../../../phet-core/js/merge.js';
import PressListener from '../../../scenery/js/listeners/PressListener.js';
import PhetioObject from '../../../tandem/js/PhetioObject.js';
import Tandem from '../../../tandem/js/Tandem.js';
import EnabledComponent from '../EnabledComponent.js';
import sun from '../sun.js';

class ButtonModel {

  /**
   * @mixes EnabledComponent
   * @param {Object} [options]
   */
  constructor( options ) {

    options = merge( {
      // {function()} called on pointer down
      startCallback: _.noop,
      // {function(over:boolean)} called on pointer up, @param {boolean} over - indicates whether the pointer was released over the button
      endCallback: _.noop,

      // phet-io
      tandem: Tandem.REQUIRED,
      phetioState: PhetioObject.DEFAULT_OPTIONS.phetioState, // to support properly passing this to children, see https://github.com/phetsims/tandem/issues/60
      phetioReadOnly: PhetioObject.DEFAULT_OPTIONS.phetioReadOnly, // to support properly passing this to children, see https://github.com/phetsims/tandem/issues/60
      phetioFeatured: PhetioObject.DEFAULT_OPTIONS.phetioFeatured // to support properly passing this to children, see https://github.com/phetsims/tandem/issues/60
    }, options );

    // Set up enabledPropertyOptions for the enabledProperty that the mixin might create
    options.enabledPropertyOptions = merge( {

      // phet-io
      phetioState: options.phetioState,
      phetioReadOnly: options.phetioReadOnly,
      phetioDocumentation: 'When disabled, the button is grayed out and cannot be pressed',
      phetioFeatured: true
    }, options.enabledPropertyOptions );

    // Initialize the mixin, which defines this.enabledProperty.
    this.initializeEnabledComponent( options );

    // model Properties
    this.overProperty = new BooleanProperty( false ); // @public - Is the pointer over the button?
    this.downProperty = new BooleanProperty( false, { reentrant: true } ); // @public - Is the pointer down?

    // @protected (read-only) - Is the button being clicked because of accessibility (like keyboard)?
    // See PressListener.a11yClickingProperty
    this.a11yClickingProperty = new BooleanProperty( false );

    // @public - This Property was added for a11y. It tracks whether or not the button should "look" down. This
    // will be true if downProperty is true or if an a11y click is in progress. For an a11y click, the listeners
    // are fired right away but the button will look down for as long as PressListener.a11yLooksPressedInterval.
    // See PressListener.click for more details.
    this.looksPressedProperty = new BooleanProperty( false );

    // @public (read-only by users, read-write in subclasses) - emitter that is fired when sound should be produced
    this.produceSoundEmitter = new Emitter();

    // @public (read-only) - indicates that interaction was interrupted during a press. Valid until next press.
    this.interrupted = false;

    // @private - keep track of and store all listeners this model creates
    this.listeners = [];

    // @private {Multilink|null} - Links all of the looksPressedProperties from the listeners that were created
    // by this ButtonModel, and updates the looksPressedProperty accordingly. First Multilink is added when the
    // first listener is created. See this.createListener.
    this.looksPressedMultilink = null;

    // startCallback on pointer down, endCallback on pointer up. lazyLink so they aren't called immediately.
    this.downProperty.lazyLink( down => {
      if ( down ) {
        options.startCallback();
      }
      else {
        options.endCallback( this.overProperty.get() );
      }
    } );

    // interrupt listeners when enabled is set to false
    this.enabledProperty.link( enabled => {
      if ( !enabled ) {
        for ( let i = 0; i < this.listeners.length; i++ ) {
          const listener = this.listeners[ i ];
          listener.interrupt && listener.interrupt();
        }
      }
    } );

    // @private
    this.disposeButtonModel = () => {

      // This will unlink all listeners, causing potential issues if listeners try to unlink Properties afterwards
      this.overProperty.dispose();
      this.downProperty.dispose();
      this.produceSoundEmitter.dispose();

      this.looksPressedMultilink && this.looksPressedMultilink.dispose();

      this.listeners = [];
    };
  }

  /**
   * @public
   */
  dispose() {
    this.disposeButtonModel();
    this.disposeEnabledComponent();
  }

  /**
   * is the button currently firing because of accessibility?
   * @public (a11y)
   * @returns {boolean}
   */
  isA11yClicking() {
    return this.a11yClickingProperty.get();
  }

  /**
   * Creates a standard button listener that can be added to a node and that will trigger the changes to this model.
   * @param {Object} [options]
   * @returns {PressListener}
   * @public
   */
  createListener( options ) {

    options = merge( {
      phetioDocumentation: 'Indicates when the button has been pressed or released',
      canStartPress: () => this.enabledProperty.value
    }, options );

    const pressListener = new PressListener( options );
    this.listeners.push( pressListener );

    // link lazily in case client externally sets downProperty - don't update until the next press
    pressListener.isPressedProperty.lazyLink( isPressed => {

      // determine interrupted first so listeners on downProperty have access
      this.interrupted = pressListener.interrupted;
      this.downProperty.set( isPressed );
    } );
    pressListener.isOverProperty.lazyLink( this.overProperty.set.bind( this.overProperty ) );
    pressListener.a11yClickingProperty.link( this.a11yClickingProperty.set.bind( this.a11yClickingProperty ) );

    // dispose the previous multilink in case we already created a PressListener with this model
    this.looksPressedMultilink && this.looksPressedMultilink.dispose();

    const looksPressedProperties = this.listeners.map( function( listener ) { return listener.looksPressedProperty; } );
    looksPressedProperties.push( this.downProperty );

    // assign a new Multilink (for disposal), and make sure that the button looks pressed when any of the
    // PressListeners created by this ButtonModel look pressed. Note that this cannot be an arrow function
    // because its implementation relies on arguments.
    const self = this;
    this.looksPressedMultilink = Property.multilink( looksPressedProperties, function() {
      self.looksPressedProperty.value = _.reduce( arguments, ( sum, newValue ) => sum || newValue, false );
    } );

    return pressListener;
  }
}

EnabledComponent.mixInto( ButtonModel );

sun.register( 'ButtonModel', ButtonModel );
export default ButtonModel;<|endoftext|>


<commit_before>// todo: proper namespacing

var lollipopMenu;

var LollipopMenu = function(owner) {
  lollipopMenu = this;
  this.owner = owner;
  this.worldPlane = new THREE.Plane(new THREE.Vector3(0,1,0),-this.owner.spriteYpos);
  this.selectionChanged = new signals.Signal(); // User selected icon (1-4) or deselected (0)
  this.positionChanged = new signals.Signal(); // new position
  this.shown = new signals.Signal; // Menu was shown
  this.hidden = new signals.Signal; // Menu was hidden

  this.lollipopSprite = null;
  this.iconSprites = [];

  this.lollipopMat = owner.pinMaterialFocus; // todo: replace with proper material
  this.iconMats = [];
  var iconTexNames = [
    "data/2d/icon_photos.png",
    "data/2d/icon_properties.png",
    "data/2d/icon_services.png",
    "data/2d/icon_transportation.png"
  ];
  for (var i = 0; i < iconTexNames.length; ++i) {
    this.iconMats.push(
      new THREE.SpriteMaterial({
        map: THREE.ImageUtils.loadTexture(iconTexNames[i]),
        color: "rgb(255,255,255)",
        fog: true,
        depthWrite : false,
      })
    );
  }

  this.iconAngles = [];
  this.minAngle = -90;
  this.maxAngle = 90;
  var angleRange = this.maxAngle - this.minAngle;
  for (var i = 0; i < iconTexNames.length; ++i) {
    this.iconAngles.push((this.minAngle + i * angleRange/(iconTexNames.length-1)) * (Math.PI/180));
  }
  this.iconDist = 0.9;
  this.iconHeight = 0.35;
  this.lollipopScale = 40;
  this.iconScale = 30;
  this.iconSelectedScale = 35;
  this.newPosThreshold = 20; // Clicking closer than this (in world coords) will close instead of moving to new pos

  this.hoverTweenSpeed = 4.5;
  this.hoverSelection = 0;

  this.scaleTween = 0;
  this.scaleTweenDir = 0;
  this.scaleTweenSpeed = 1.0;

  this.mouseDownX = 0;
  this.mouseDownY = 0;

  this.avatarMoveDelay = 1.0;

  // Helsinki issue objects
  this.issueItems = {};
  this.issueIconMaterials = {};
  this.issueIconMaterials.open = new THREE.SpriteMaterial({
    map: THREE.ImageUtils.loadTexture(IssueItem.Icons.OPEN),
    color: "rgb(216,136,0)",
    fog: true,
    depthWrite : false
  });
  this.issueIconMaterials.inprogress = new THREE.SpriteMaterial({
    map: THREE.ImageUtils.loadTexture(IssueItem.Icons.INPROGRESS),
    color: "rgb(216,136,0)",
    fog: true,
    depthWrite : false
  });
  this.issueIconMaterials.closed = new THREE.SpriteMaterial({
    map: THREE.ImageUtils.loadTexture(IssueItem.Icons.CLOSED),
    color: "rgb(216,136,0)",
    fog: true,
    depthWrite : false
  });
  this.issueIconMaterials.unknown = new THREE.SpriteMaterial({
    map: THREE.ImageUtils.loadTexture(IssueItem.Icons.UNKNOWN),
    color: "rgb(216,136,0)",
    fog: true,
    depthWrite : false
  });
  this.issueInstances = [];

  this.selection = 0; // 0 = none, 1 = photos, 2 = properties etc.
};

LollipopMenu.prototype = {
  onMouseDown : function(x, y) {
    this.mouseDownX = x;
    this.mouseDownY = y;
  },

  onMouseUp : function(x, y) {
    // Only show/hide menu if this is not a drag
    if (x == this.mouseDownX && y == this.mouseDownY) {
      if (!this.isShowing()) {
        var pos = this.planeRaycast(x, y);
        if (pos) {
          this.createMenu(pos);
		 this.sendPositionChanged(pos);
		}
      }
      else {
        // Raycast to icons and perform selection, hide/reopen menu if none hit
        if (!this.doSelectionRaycast(x, y) &&
            !this.doIconRaycast(x, y)) {
          var pos = this.planeRaycast(x, y);
          var distVec = new THREE.Vector3();
          distVec.subVectors(pos, this.lastShowPos);
          var dist = distVec.length();
          if (dist > this.newPosThreshold) {
            this.createMenu(pos);
			this.sendPositionChanged(pos);
          }
          else {
            this.startHideMenu();
          }
        }
      }
    }
  },

  sendPositionChanged: function(pos)
  {
	var point = new VIZI.Point(pos.x, pos.y);
	var w = this.owner.options.globalData.world;
	var latLong = w.unproject(point, w.zoom);
	this.positionChanged.dispatch(latLong);
  },

  onMouseMove : function(x, y) {
    if (this.isShowing()){
      this.updateIconPositions();
      this.doHoverRaycast(x, y);
    }
  },

  planeRaycast : function(x, y) {
    var vector = new THREE.Vector3(x, y, 1);
    vector.unproject(this.owner.world.camera.camera);
    var pLocal = new THREE.Vector3(0, 0, -1);
    var pWorld = pLocal.applyMatrix4(this.owner.world.camera.camera.matrixWorld);
    var ray = new THREE.Ray(pWorld, vector.sub(pWorld).normalize());
    var intersectPoint = ray.intersectPlane(this.worldPlane, null);
    return intersectPoint;
  },

  getIssueMaterial: function(status){
      if (status == "open")
          return this.issueIconMaterials.open;
      else if(status == "inprogress")
          return this.issueIconMaterials.inprogress;
      else if(status == "closed")
          return this.issueIconMaterials.closed;
      else
          return this.issueIconMaterials.unknown;
  },

  doIconRaycast : function(x, y) {
    var intersections = this.owner.doRaycast(x, y, this.issueInstances);
    for (var i = 0; i < intersections.length; ++i)
    {
      //console.log(intersections[i]);
      this.openDialog(intersections[i].object.item);
    }
    return intersections.length > 0;
  },

  openDialog: function(item) {
    var self = this;
    $("body").append("
" +
                     "
" + item.description + "
" +
                     "
");
    this.currentDialog = $("#" + item.id).dialog({
          width: 500,
          height: "auto",
          close: function(ev, ui) {
            self.closeDialog();
          }
        });
  },

  closeDialog: function() {
    this.currentDialog = null;
  },

  createIssueIcons: function() {
      var world = this.owner.options.globalData.world;
      var item = null;
      var sprite = null;

      for (var i in this.issueItems)
      {
          item = this.issueItems[i];
          sprite = new THREE.Sprite(this.getIssueMaterial(item.status));
          sprite.scale.set(20, 20, 20);
          sprite.position.copy(world.project(item.latLon, world.zoom));
          sprite.position.set(sprite.position.x, 60, sprite.position.y);
          sprite.item = item;
          this.issueInstances.push(sprite);
          this.owner.add(sprite);
      }
  },

  removeIssueIcons: function() {
      this.issueItems = {};
      for(var i in this.issueInstances)
          this.owner.remove(this.issueInstances[i]);
      this.issueInstances = [];
  },

  createMenu : function(pos) {
    if (this.isShowing()) {
        this.hideMenu(); // If already showing, hide the old first
    }

    if (this.owner.options.globalData != null && this.owner.options.globalData.world != null)
    {
        var point = new VIZI.Point(pos.x, pos.z);
        var w = this.owner.options.globalData.world;
        var latLong = w.unproject(point, w.zoom);
        var that = this;
        this.removeIssueIcons();

        var callback = function(result) {
            var item = null;
            for(var i in result) {
                item = result[i];
                if (item instanceof IssueItem) {
                    that.issueItems[item.id] = item;
                }
            }
            that.createIssueIcons();

            that = null;
        };
        HelsinkiIssues.RequestIssues(latLong.lat, latLong.lon, 300, callback);
    }

    // Animate the circle to new position
    if (this.owner.options.globalData != null && this.owner.options.globalData.animator != null)
	{
		this.owner.options.globalData.animator.SetPosition(pos);
	}

    this.lastShowPos = pos;

    // Move user's avatar to the position where the menu is going to open
    if (userPresence)
      userPresence.sendPositionUpdate(pos, this.avatarMoveDelay);

    // The central "lollipop" is actually redundant if user has an avatar
    // in that case create just a bare scene node
    var spr;
    if (userPresence && userPresence.entity)
        spr = new THREE.Object3D();
    else
        spr = new THREE.Sprite(this.lollipopMat);

    spr.position.x = pos.x;
    spr.position.y = this.owner.spriteYpos;
    spr.position.z = pos.z;
    this.owner.add(spr);
    this.lollipopSprite = spr;

    for (var i = 0; i < this.iconMats.length; ++i) {
      var spr2 = new THREE.Sprite(this.iconMats[i]);
      spr2.scale.set(this.iconScale,this.iconScale,this.iconScale);
      spr2.position.copy(this.calculateIconPosition(i));
      spr2.selectionNumber = i+1;
      spr2.hoverTween = 0.0;

      this.lollipopSprite.add(spr2);
      this.iconSprites.push(spr2);
    }
    this.setSelection(0);
    this.scaleTween = 0.1;
    this.scaleTweenDir = 1;
    this.updateScale(); // Set initial lollipop scale

    this.shown.dispatch();
  },

  startHideMenu : function() {
    this.scaleTweenDir = -1;
  },

  hideMenu : function() {
    if (!this.isShowing()) {
      return; // Not showing
    }

    for (var i = 0; i < this.iconSprites.length; ++i)
      this.lollipopSprite.remove(this.iconSprites[i]);
    this.iconSprites = [];
    this.owner.remove(this.lollipopSprite);
    this.lollipopSprite = null;
    this.setSelection(0);

    this.hidden.dispatch();
  },

  isShowing : function() {
    return this.lollipopSprite != null;
  },

  calculateIconPosition : function(i) {
    var iconPos = new THREE.Vector3(Math.sin(this.iconAngles[i])*this.iconDist,
      this.iconHeight + Math.cos(this.iconAngles[i])*this.iconDist, 0);
    iconPos.applyQuaternion(this.owner.world.camera.camera.getWorldQuaternion());
    return iconPos;
  },

  updateIconPositions : function() {
    for (var i = 0; i < this.iconSprites.length; ++i) {
      this.iconSprites[i].position.copy(this.calculateIconPosition(i));
    }
  },

  doHoverRaycast : function(x, y) {
    var intersections = this.owner.doRaycast(x, y, this.iconSprites);
    this.hoverSelection = -1;
    if(intersections.length != 0){
      this.hoverSelection = intersections[0].object.selectionNumber - 1;
    }
    return this.hoverSelection;
  },

  doSelectionRaycast : function(x, y) {
    var intersections = this.owner.doRaycast(x, y, this.iconSprites);
    for (var i = 0; i < intersections.length; ++i)
    {
      // If we raycast to already hit object, rather use the one that is unselected
      if (this.selection != intersections[i].object.selectionNumber) {
        this.setSelection(intersections[i].object.selectionNumber);
        break;
      }
    }
    return intersections.length > 0;
  },

  onTick : function(delta) {
    this.updateTween(delta);
    this.updateScale();
  },

  updateTween : function(delta) {
    if (this.lollipopSprite){
      if (this.lollipopSprite && this.scaleTweenDir != 0) {
        this.scaleTween += this.scaleTweenDir * this.scaleTweenSpeed * delta;
        if (this.scaleTween > 1) {
          this.scaleTween = 1;
          this.scaleTweenDir = 0;
        }
        if (this.scaleTween < 0) {
          this.scaleTween = 0;
          this.scaleTweenDir = 0;
          this.hideMenu();
        }
      }

      for (var i = 0; i < this.iconSprites.length; ++i) {
        var dir = i == this.hoverSelection ? 1.0 : -1.0;
        this.iconSprites[i].hoverTween = Math.max(0, Math.min(1.0, this.iconSprites[i].hoverTween + dir * this.hoverTweenSpeed * delta));
      }
    }
  },

  easeInOutQuad : function (t) {
    return t<.5 ? 2*t*t : -1+(4-2*t)*t
  },

  updateScale : function() {
    if (this.lollipopSprite) {
      //center icon
      var t = Math.min(0.33333, this.scaleTween)*3.0;
      var icontween = this.easeInOutQuad(t);
      var iconscl = (6.5) - (5.5* icontween);
      var s = iconscl*this.lollipopScale * icontween;
      this.lollipopSprite.scale.set(s, s, s);

      //Update icon scales
      var num = this.iconSprites.length;
      var step = 1.0 / num;
      var start = 0.5;
      var totaldelay = start + 0.25*num*step;
      var delaystep = (1.0 / (1.0 - totaldelay));

      for (var i = 0; i < this.iconSprites.length; ++i) {

        var delay = start + 0.25*i*step;
        t = (this.scaleTween - delay) * delaystep;
        t = Math.max( 0.0, Math.min( 1.0, t) );

        var tween = this.easeInOutQuad(t);
        var hoverscl = this.easeInOutQuad(this.iconSprites[i].hoverTween) * 0.5;
        var sclfactor = hoverscl + ((4.5) - (3.5* tween));

        if (this.iconSprites[i].selectionNumber == this.selection) {
          s = sclfactor * this.iconSelectedScale * tween;
        }
        else {
          s = sclfactor * this.iconScale * tween;
        }
        this.iconSprites[i].scale.set(s, s, s);
      }
    }
  },

  setSelection : function(newSel) {
    if (newSel != this.selection) {
      this.selection = newSel;
      this.updateScale();
      this.selectionChanged.dispatch(this.selection);
    }
  },

  getSelection : function() {
    return this.selection;
  }
}
<commit_msg>Added image support to Helsinki issues dialog if defined in issue report.<commit_after>// todo: proper namespacing

var lollipopMenu;

var LollipopMenu = function(owner) {
  lollipopMenu = this;
  this.owner = owner;
  this.worldPlane = new THREE.Plane(new THREE.Vector3(0,1,0),-this.owner.spriteYpos);
  this.selectionChanged = new signals.Signal(); // User selected icon (1-4) or deselected (0)
  this.positionChanged = new signals.Signal(); // new position
  this.shown = new signals.Signal; // Menu was shown
  this.hidden = new signals.Signal; // Menu was hidden

  this.lollipopSprite = null;
  this.iconSprites = [];

  this.lollipopMat = owner.pinMaterialFocus; // todo: replace with proper material
  this.iconMats = [];
  var iconTexNames = [
    "data/2d/icon_photos.png",
    "data/2d/icon_properties.png",
    "data/2d/icon_services.png",
    "data/2d/icon_transportation.png"
  ];
  for (var i = 0; i < iconTexNames.length; ++i) {
    this.iconMats.push(
      new THREE.SpriteMaterial({
        map: THREE.ImageUtils.loadTexture(iconTexNames[i]),
        color: "rgb(255,255,255)",
        fog: true,
        depthWrite : false,
      })
    );
  }

  this.iconAngles = [];
  this.minAngle = -90;
  this.maxAngle = 90;
  var angleRange = this.maxAngle - this.minAngle;
  for (var i = 0; i < iconTexNames.length; ++i) {
    this.iconAngles.push((this.minAngle + i * angleRange/(iconTexNames.length-1)) * (Math.PI/180));
  }
  this.iconDist = 0.9;
  this.iconHeight = 0.35;
  this.lollipopScale = 40;
  this.iconScale = 30;
  this.iconSelectedScale = 35;
  this.newPosThreshold = 20; // Clicking closer than this (in world coords) will close instead of moving to new pos

  this.hoverTweenSpeed = 4.5;
  this.hoverSelection = 0;

  this.scaleTween = 0;
  this.scaleTweenDir = 0;
  this.scaleTweenSpeed = 1.0;

  this.mouseDownX = 0;
  this.mouseDownY = 0;

  this.avatarMoveDelay = 1.0;

  // Helsinki issue objects
  this.issueItems = {};
  this.issueIconMaterials = {};
  this.issueIconMaterials.open = new THREE.SpriteMaterial({
    map: THREE.ImageUtils.loadTexture(IssueItem.Icons.OPEN),
    color: "rgb(216,136,0)",
    fog: true,
    depthWrite : false
  });
  this.issueIconMaterials.inprogress = new THREE.SpriteMaterial({
    map: THREE.ImageUtils.loadTexture(IssueItem.Icons.INPROGRESS),
    color: "rgb(216,136,0)",
    fog: true,
    depthWrite : false
  });
  this.issueIconMaterials.closed = new THREE.SpriteMaterial({
    map: THREE.ImageUtils.loadTexture(IssueItem.Icons.CLOSED),
    color: "rgb(216,136,0)",
    fog: true,
    depthWrite : false
  });
  this.issueIconMaterials.unknown = new THREE.SpriteMaterial({
    map: THREE.ImageUtils.loadTexture(IssueItem.Icons.UNKNOWN),
    color: "rgb(216,136,0)",
    fog: true,
    depthWrite : false
  });
  this.issueInstances = [];

  this.selection = 0; // 0 = none, 1 = photos, 2 = properties etc.
};

LollipopMenu.prototype = {
  onMouseDown : function(x, y) {
    this.mouseDownX = x;
    this.mouseDownY = y;
  },

  onMouseUp : function(x, y) {
    // Only show/hide menu if this is not a drag
    if (x == this.mouseDownX && y == this.mouseDownY) {
      if (!this.isShowing()) {
        var pos = this.planeRaycast(x, y);
        if (pos) {
          this.createMenu(pos);
		 this.sendPositionChanged(pos);
		}
      }
      else {
        // Raycast to icons and perform selection, hide/reopen menu if none hit
        if (!this.doSelectionRaycast(x, y) &&
            !this.doIconRaycast(x, y)) {
          var pos = this.planeRaycast(x, y);
          var distVec = new THREE.Vector3();
          distVec.subVectors(pos, this.lastShowPos);
          var dist = distVec.length();
          if (dist > this.newPosThreshold) {
            this.createMenu(pos);
			this.sendPositionChanged(pos);
          }
          else {
            this.startHideMenu();
          }
        }
      }
    }
  },

  sendPositionChanged: function(pos)
  {
	var point = new VIZI.Point(pos.x, pos.y);
	var w = this.owner.options.globalData.world;
	var latLong = w.unproject(point, w.zoom);
	this.positionChanged.dispatch(latLong);
  },

  onMouseMove : function(x, y) {
    if (this.isShowing()){
      this.updateIconPositions();
      this.doHoverRaycast(x, y);
    }
  },

  planeRaycast : function(x, y) {
    var vector = new THREE.Vector3(x, y, 1);
    vector.unproject(this.owner.world.camera.camera);
    var pLocal = new THREE.Vector3(0, 0, -1);
    var pWorld = pLocal.applyMatrix4(this.owner.world.camera.camera.matrixWorld);
    var ray = new THREE.Ray(pWorld, vector.sub(pWorld).normalize());
    var intersectPoint = ray.intersectPlane(this.worldPlane, null);
    return intersectPoint;
  },

  getIssueMaterial: function(status){
      if (status == "open")
          return this.issueIconMaterials.open;
      else if(status == "inprogress")
          return this.issueIconMaterials.inprogress;
      else if(status == "closed")
          return this.issueIconMaterials.closed;
      else
          return this.issueIconMaterials.unknown;
  },

  doIconRaycast : function(x, y) {
    var intersections = this.owner.doRaycast(x, y, this.issueInstances);
    for (var i = 0; i < intersections.length; ++i)
    {
      //console.log(intersections[i]);
      this.openDialog(intersections[i].object.item);
    }
    return intersections.length > 0;
  },

  openDialog: function(item) {
    var self = this;
    var image_str = "";
    if (item.media != null)
        image_str = "";
    $("body").append("
" +
                     "
" + item.description + "
" + image_str +
                     "
");

    this.currentDialog = $("#" + item.id).dialog({
          width: 500,
          height: "auto",
          close: function(ev, ui) {
            self.closeDialog();
          }
        });
  },

  closeDialog: function() {
    this.currentDialog = null;
  },

  createIssueIcons: function() {
      var world = this.owner.options.globalData.world;
      var item = null;
      var sprite = null;

      for (var i in this.issueItems)
      {
          item = this.issueItems[i];
          sprite = new THREE.Sprite(this.getIssueMaterial(item.status));
          sprite.scale.set(20, 20, 20);
          sprite.position.copy(world.project(item.latLon, world.zoom));
          sprite.position.set(sprite.position.x, 60, sprite.position.y);
          sprite.item = item;
          this.issueInstances.push(sprite);
          this.owner.add(sprite);
      }
  },

  removeIssueIcons: function() {
      this.issueItems = {};
      for(var i in this.issueInstances)
          this.owner.remove(this.issueInstances[i]);
      this.issueInstances = [];
  },

  createMenu : function(pos) {
    if (this.isShowing()) {
        this.hideMenu(); // If already showing, hide the old first
    }

    if (this.owner.options.globalData != null && this.owner.options.globalData.world != null)
    {
        var point = new VIZI.Point(pos.x, pos.z);
        var w = this.owner.options.globalData.world;
        var latLong = w.unproject(point, w.zoom);
        var that = this;
        this.removeIssueIcons();

        var callback = function(result) {
            var item = null;
            for(var i in result) {
                item = result[i];
                if (item instanceof IssueItem) {
                    that.issueItems[item.id] = item;
                }
            }
            that.createIssueIcons();

            that = null;
        };
        HelsinkiIssues.RequestIssues(latLong.lat, latLong.lon, 300, callback);
    }

    // Animate the circle to new position
    if (this.owner.options.globalData != null && this.owner.options.globalData.animator != null)
	{
		this.owner.options.globalData.animator.SetPosition(pos);
	}

    this.lastShowPos = pos;

    // Move user's avatar to the position where the menu is going to open
    if (userPresence)
      userPresence.sendPositionUpdate(pos, this.avatarMoveDelay);

    // The central "lollipop" is actually redundant if user has an avatar
    // in that case create just a bare scene node
    var spr;
    if (userPresence && userPresence.entity)
        spr = new THREE.Object3D();
    else
        spr = new THREE.Sprite(this.lollipopMat);

    spr.position.x = pos.x;
    spr.position.y = this.owner.spriteYpos;
    spr.position.z = pos.z;
    this.owner.add(spr);
    this.lollipopSprite = spr;

    for (var i = 0; i < this.iconMats.length; ++i) {
      var spr2 = new THREE.Sprite(this.iconMats[i]);
      spr2.scale.set(this.iconScale,this.iconScale,this.iconScale);
      spr2.position.copy(this.calculateIconPosition(i));
      spr2.selectionNumber = i+1;
      spr2.hoverTween = 0.0;

      this.lollipopSprite.add(spr2);
      this.iconSprites.push(spr2);
    }
    this.setSelection(0);
    this.scaleTween = 0.1;
    this.scaleTweenDir = 1;
    this.updateScale(); // Set initial lollipop scale

    this.shown.dispatch();
  },

  startHideMenu : function() {
    this.scaleTweenDir = -1;
  },

  hideMenu : function() {
    if (!this.isShowing()) {
      return; // Not showing
    }

    for (var i = 0; i < this.iconSprites.length; ++i)
      this.lollipopSprite.remove(this.iconSprites[i]);
    this.iconSprites = [];
    this.owner.remove(this.lollipopSprite);
    this.lollipopSprite = null;
    this.setSelection(0);

    this.hidden.dispatch();
  },

  isShowing : function() {
    return this.lollipopSprite != null;
  },

  calculateIconPosition : function(i) {
    var iconPos = new THREE.Vector3(Math.sin(this.iconAngles[i])*this.iconDist,
      this.iconHeight + Math.cos(this.iconAngles[i])*this.iconDist, 0);
    iconPos.applyQuaternion(this.owner.world.camera.camera.getWorldQuaternion());
    return iconPos;
  },

  updateIconPositions : function() {
    for (var i = 0; i < this.iconSprites.length; ++i) {
      this.iconSprites[i].position.copy(this.calculateIconPosition(i));
    }
  },

  doHoverRaycast : function(x, y) {
    var intersections = this.owner.doRaycast(x, y, this.iconSprites);
    this.hoverSelection = -1;
    if(intersections.length != 0){
      this.hoverSelection = intersections[0].object.selectionNumber - 1;
    }
    return this.hoverSelection;
  },

  doSelectionRaycast : function(x, y) {
    var intersections = this.owner.doRaycast(x, y, this.iconSprites);
    for (var i = 0; i < intersections.length; ++i)
    {
      // If we raycast to already hit object, rather use the one that is unselected
      if (this.selection != intersections[i].object.selectionNumber) {
        this.setSelection(intersections[i].object.selectionNumber);
        break;
      }
    }
    return intersections.length > 0;
  },

  onTick : function(delta) {
    this.updateTween(delta);
    this.updateScale();
  },

  updateTween : function(delta) {
    if (this.lollipopSprite){
      if (this.lollipopSprite && this.scaleTweenDir != 0) {
        this.scaleTween += this.scaleTweenDir * this.scaleTweenSpeed * delta;
        if (this.scaleTween > 1) {
          this.scaleTween = 1;
          this.scaleTweenDir = 0;
        }
        if (this.scaleTween < 0) {
          this.scaleTween = 0;
          this.scaleTweenDir = 0;
          this.hideMenu();
        }
      }

      for (var i = 0; i < this.iconSprites.length; ++i) {
        var dir = i == this.hoverSelection ? 1.0 : -1.0;
        this.iconSprites[i].hoverTween = Math.max(0, Math.min(1.0, this.iconSprites[i].hoverTween + dir * this.hoverTweenSpeed * delta));
      }
    }
  },

  easeInOutQuad : function (t) {
    return t<.5 ? 2*t*t : -1+(4-2*t)*t
  },

  updateScale : function() {
    if (this.lollipopSprite) {
      //center icon
      var t = Math.min(0.33333, this.scaleTween)*3.0;
      var icontween = this.easeInOutQuad(t);
      var iconscl = (6.5) - (5.5* icontween);
      var s = iconscl*this.lollipopScale * icontween;
      this.lollipopSprite.scale.set(s, s, s);

      //Update icon scales
      var num = this.iconSprites.length;
      var step = 1.0 / num;
      var start = 0.5;
      var totaldelay = start + 0.25*num*step;
      var delaystep = (1.0 / (1.0 - totaldelay));

      for (var i = 0; i < this.iconSprites.length; ++i) {

        var delay = start + 0.25*i*step;
        t = (this.scaleTween - delay) * delaystep;
        t = Math.max( 0.0, Math.min( 1.0, t) );

        var tween = this.easeInOutQuad(t);
        var hoverscl = this.easeInOutQuad(this.iconSprites[i].hoverTween) * 0.5;
        var sclfactor = hoverscl + ((4.5) - (3.5* tween));

        if (this.iconSprites[i].selectionNumber == this.selection) {
          s = sclfactor * this.iconSelectedScale * tween;
        }
        else {
          s = sclfactor * this.iconScale * tween;
        }
        this.iconSprites[i].scale.set(s, s, s);
      }
    }
  },

  setSelection : function(newSel) {
    if (newSel != this.selection) {
      this.selection = newSel;
      this.updateScale();
      this.selectionChanged.dispatch(this.selection);
    }
  },

  getSelection : function() {
    return this.selection;
  }
}
<|endoftext|>


<commit_before>function Division(input1, input2) {
    this.input1 = input1;
    this.input2 = input2;

    this.setChildren([this.input1, this.input2]);

    this.getName = function() {
        return this.input1.getName() + "_" + this.input2.getName();
    };
    this.setName = null;

    this.validate = function() {
        if (this.input1.getColumns() == null || this.input2.getColumns() == null) {
            throw "Es fehlt mindestens eine Eingaberelation.";
        }
    };

    this.getColumns = function() {
        this.validate();
        var result = [];
        var columns = this.input1.getColumns().clone();
        var dividerColumns = this.input2.getColumns().clone();
        var isInColumns = function(name, columnArray) {
          for (var i = 0; i<columnArray.length;i++) {
            if (columnArray[i] === name) {
              return true;
            }
          }
          return false;
        };
        for (var i = 0; i<columns.length;i++) {
          if (!isInColumns(columns[i], dividerColumns)) {
            result.push(columns[i]);
          }
        }
        return result;
    };
    this.setColumns = null;

    this.getResult = function() {

        this.validate();

        var cols = this.getColumns();

        console.log(cols);

        var pro = new Projection(cols.toString(), this.input1);

        var cp = new Crossproduct(pro, this.input2);

        var namingCommand = "";

        var cpColumns = cp.getColumns();

        for (var i=0;i<cpColumns.length;i++) {
          namingCommand += cpColumns[i].split(".")[1] + "<-" + cpColumns[i] + ",";
        }

        var newNames = new Rename(namingCommand,cp);

        var min = new Minus(newNames, this.input1);

        var proj = new Projection(cols.toString(), min);

        min = new Minus(pro, proj);

        return min.getResult();
    };

    this.copy = function() {
        return new Division(this.input1.copy(), this.input2.copy());
    };

    this.toHTML = function(options) {
        var display = '';
        display += '(' + this.input1.toHTML(options) + " " + latex("\\div") +  " " + this.input2.toHTML(options) + ")";
        return display;
    };

    this.toLatex = function(options) {
        return "(" + this.input1.toLatex(options) + "\\div " + this.input2.toLatex(options) + ")";
    };
}
Division.prototype = new Relation();
<commit_msg>cleanup and add more validation to relational division<commit_after>function Division(input1, input2) {
    this.input1 = input1;
    this.input2 = input2;

    this.setChildren([this.input1, this.input2]);

    this.getName = function() {
        return this.input1.getName() + "_" + this.input2.getName();
    };
    this.setName = null;

    this.validate = function() {
      var leftInputColumns = this.input1.getColumns();
      var rightInputColumns = this.input2.getColumns()
      if (leftInputColumns == null || rightInputColumns == null) {
          throw "Es fehlt mindestens eine Eingaberelation.";
      }

      var commonColumnCount = 0;
      for (var i = 0; i<rightInputColumns.length;i++) {
        if (leftInputColumns.indexOf(rightInputColumns[i]) === -1) {
          throw "Das Schema der rechten Eingaberelation ist keine (echte) Teilmenge des Schemas der linken Eingaberelation.";
        } else {
          commonColumnCount++;
        }
      }
      if (commonColumnCount === leftInputColumns.length) {
        throw "Das Schema der rechten Eingaberelation ist keine (echte) Teilmenge des Schemas der linken Eingaberelation.";
      }
    };

    this.getColumns = function() {
        this.validate();
        var result = [];
        var columns = this.input1.getColumns().clone();
        var dividerColumns = this.input2.getColumns().clone();
        var isInColumns = function(name, columnArray) {
          for (var i = 0; i<columnArray.length;i++) {
            if (columnArray[i] === name) {
              return true;
            }
          }
          return false;
        };
        for (var i = 0; i<columns.length;i++) {
          if (!isInColumns(columns[i], dividerColumns)) {
            result.push(columns[i]);
          }
        }
        return result;
    };
    this.setColumns = null;

    this.getResult = function() {
        this.validate();
        var cols = this.getColumns();
        var pro = new Projection(cols.toString(), this.input1);
        var cp = new Crossproduct(pro, this.input2);
        var namingCommand = "";
        var cpColumns = cp.getColumns();
        for (var i=0;i<cpColumns.length;i++) {
          namingCommand += cpColumns[i].split(".")[1] + "<-" + cpColumns[i] + ",";
        }
        var newNames = new Rename(namingCommand,cp);
        var min = new Minus(newNames, this.input1);
        var proj = new Projection(cols.toString(), min);
        min = new Minus(pro, proj);
        return min.getResult();
    };

    this.copy = function() {
        return new Division(this.input1.copy(), this.input2.copy());
    };

    this.toHTML = function(options) {
        var display = '';
        display += '(' + this.input1.toHTML(options) + " " + latex("\\div") +  " " + this.input2.toHTML(options) + ")";
        return display;
    };

    this.toLatex = function(options) {
        return "(" + this.input1.toLatex(options) + "\\div " + this.input2.toLatex(options) + ")";
    };
}
Division.prototype = new Relation();
<|endoftext|>


<commit_before>/*!
 * FileInput German Translations
 *
 * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
 * any HTML markup tags in the messages must not be converted or translated.
 *
 * @see http://github.com/kartik-v/bootstrap-fileinput
 */
(function ($) {
    "use strict";

    $.fn.fileinput.locales.de = {
        fileSingle: 'Datei',
        filePlural: 'Dateien',
        browseLabel: 'Blättern …',
        removeLabel: 'Entfernen',
        removeTitle: 'Klar ausgewählten Dateien',
        cancelLabel: 'Laden',
        cancelTitle: 'Abbruch laufenden Hochladen',
        uploadLabel: 'Hochladen',
        uploadTitle: 'Hochladen ausgewählten Dateien',
        msgSizeTooLarge: 'Datei "{name}" ({size} KB) überschreitet maximal zulässige Upload-Größe von {maxSize} KB. Bitte versuchen Sie Ihr Hochladen!',
        msgFilesTooLess: 'Sie müssen mindestens {n} {files} zum Hochladen auswählen. Bitte versuchen Sie Ihr Hochladen!',
        msgFilesTooMany: 'Anzahl der Dateien für den Upload ausgewählt ({n}) überschreitet maximal zulässige Grenze von {m}. Bitte versuchen Sie Ihr Hochladen!',
        msgFileNotFound: 'Datei "{name}" wurde nicht gefunden!',
        msgFileSecured: 'Sicherheitseinschränkungen verhindern Lesen der Datei "{name}".',
        msgFileNotReadable: 'Datei "{name}" ist nicht lesbar.',
        msgFilePreviewAborted: 'Dateivorschau abgebrochen für "{name}".',
        msgFilePreviewError: 'Beim Lesen der Datei "{name}" ein Fehler aufgetreten.',
        msgInvalidFileType: 'Ungültiger Typ für Datei "{name}". Nur "{types}" Dateien werden unterstützt.',
        msgInvalidFileExtension: 'Ungültige Erweiterung für Datei "{name}". Nur "{extensions}" Dateien werden unterstützt.',
        msgValidationError: 'Dateifehler hochladen',
        msgLoading: 'Wird Geladen Datei {index} von {files} …',
        msgProgress: 'Wird Geladen Datei {index} von {files} - {name} - {percent}% fertiggestellt.',
        msgSelected: '{n} Dateien ausgewählt',
        msgFoldersNotAllowed: 'Drag & Drop Dateien nur! Sprungen {n} gesunken Ordner.',
        dropZoneTitle: 'Drag & Drop Dateien hier …'
    };

    $.extend($.fn.fileinput.defaults, $.fn.fileinput.locales.de);
})(window.jQuery);<commit_msg>Correct German translation.<commit_after>/*!
 * FileInput German Translations
 *
 * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
 * any HTML markup tags in the messages must not be converted or translated.
 *
 * @see http://github.com/kartik-v/bootstrap-fileinput
 */
(function ($) {
    "use strict";

    $.fn.fileinput.locales.de = {
        fileSingle: 'Datei',
        filePlural: 'Dateien',
        browseLabel: 'Auswählen …',
        removeLabel: 'Löschen',
        removeTitle: 'Ausgewählte löschen',
        cancelLabel: 'Laden',
        cancelTitle: 'Hochladen abbrechen',
        uploadLabel: 'Hochladen',
        uploadTitle: 'Hochladen der ausgewählten Dateien',
        msgSizeTooLarge: 'Datei "{name}" ({size} KB) überschreitet maximal zulässige Upload-Größe von {maxSize} KB.',
        msgFilesTooLess: 'Sie müssen mindestens {n} {files} zum Hochladen auswählen. Bitte versuchen es erneut!',
        msgFilesTooMany: 'Anzahl der Dateien für den Upload ausgewählt ({n}) überschreitet maximal zulässige Grenze von {m} Stück.',
        msgFileNotFound: 'Datei "{name}" wurde nicht gefunden!',
        msgFileSecured: 'Sicherheitseinstellungen verhindern das Lesen der Datei "{name}".',
        msgFileNotReadable: 'Die Datei "{name}" ist nicht lesbar.',
        msgFilePreviewAborted: 'Dateivorschau abgebrochen für "{name}".',
        msgFilePreviewError: 'Beim Lesen der Datei "{name}" ein Fehler aufgetreten.',
        msgInvalidFileType: 'Ungültiger Typ für Datei "{name}". Nur Dateien der Typen "{types}" werden unterstützt.',
        msgInvalidFileExtension: 'Ungültige Erweiterung für Datei "{name}". Nur Dateien mit der Endung "{extensions}" werden unterstützt.',
        msgValidationError: 'Fehler beim Hochladen',
        msgLoading: 'Lade Datei {index} von {files} hoch…',
        msgProgress: 'Datei {index} von {files} - {name} - zu {percent}% fertiggestellt.',
        msgSelected: '{n} Dateien ausgewählt',
        msgFoldersNotAllowed: 'Drag & Drop funktioniert nur bei Dateien! {n} Ordner übersprungen.',
        dropZoneTitle: 'Dateien hierher ziehen …'
    };

    $.extend($.fn.fileinput.defaults, $.fn.fileinput.locales.de);
})(window.jQuery);
<|endoftext|>


<commit_before>'use strict';

class Terrain extends GameObject {
	constructor(transform) {
		super(transform);
		// setup GLSL program
		this.programInfo = twgl.createProgramInfo(gl, ["3d-vertex-shader", "3d-fragment-shader"])

		let height_data = this.getHeightData(images.heightmap, images.heightmap.width, images.heightmap.height);

		// first transform everything into vectors
		let vertices = [];
		let len_rows = images.heightmap.height;
		let len_cols = images.heightmap.width;
		let center_coord = {
			row: len_rows / 2,
			col: len_cols / 2
		};
		for (let row = 0; row < len_rows; ++row) {
			for (let col = 0; col < len_cols; ++col) {
				let v = v3.create(
					row - center_coord.row,
					height_data[row*len_rows + col],
					col - center_coord.col
				);
				vertices.push(v);
			}
		}
		console.log(vertices.length);
		let normals = [];
		/*
		// fuck this I'll solve it later
		let aux = v3.create();
		for (let row = 0; row < len_rows; ++row) {
			for (let col = 0; col < len_cols; ++col) {
				let current = vertices[row*len_rows + col];
				if (row > 0 && row < len_rows - 1 && col > 0 && col < len_cols - 1) {
					let adjacent = [
						vertices[(row-1)*len_rows + col-1],
						vertices[(row-1)*len_rows + col],
						vertices[(row-1)*len_rows + col+1],
						vertices[(row)*len_rows + col-1],
						vertices[(row)*len_rows + col+1],
						vertices[(row+1)*len_rows + col-1],
						vertices[(row+1)*len_rows + col],
						vertices[(row+1)*len_rows + col+1]
					];
					let normal = v3.create();
					for (let ii = 0, len = adjacent.length; ii < len; ++ii) {
						v3.subtract(current, adjacent[ii], aux);
						v3.add(normal, aux, normal);
					}
					v3.normalize(normal, normal);
					normals.push(normal);
				} else {
					// brute force edge cases because I don't want to think
					normals.push(v3.create(0, 1, 0));
				}
			}
		}
		*/

		let geometry = [];

		for (let row = 0; row < len_rows; ++row) {
			for (let col = 0; col < len_cols; ++col) {
				let current = vertices[row*len_rows + col];
				if ((row < (len_rows - 1)) && (col < (len_cols - 1))) {
					let current = vertices[row*len_rows + col];
					let right = vertices[row*len_rows + col + 1];
					let below = vertices[(row + 1)*len_rows + col];
					let diag = vertices[(row + 1)*len_rows + col + 1];
					geometry.push(
						below[0], below[1], below[2],
						current[0], current[1], current[2],
						right[0], right[1], right[2],

						diag[0], diag[1], diag[2],
						below[0], below[1], below[2],
						right[0], right[1], right[2],
					)
				}
			}
		}
		geometry = new Float32Array(geometry);

		for (let ii = 0, len = geometry.length / 3; ii < len; ++ii) {
			normals.push(0, 1, 0);
		}
		// normals = new Float32Array(
		// 	[].concat.apply([], normals.map(
		// 		normal =>  [normal[0], normal[1], normal[2]]
		// 	))
		// );
		normals = new Float32Array(normals);

		let colors = [];
		for (let ii = 0, len = geometry.length; ii < len; ++ii) {
			// colors.push(Math.floor(Math.random() * 255));
			colors.push(128);
		}
		colors = new Uint8Array(colors);

		let texcoords = [];
		for (let ii = 0, len = geometry.length/2; ii < len; ++ii) {
			texcoords.push(0, 0);
		}
		texcoords = new Float32Array(texcoords);

		console.log(geometry.length);
		console.log(normals.length);
		console.log(colors.length);

		console.log(geometry);

		let arrays = {
			// Estos nombres dependen de las variables de los shaders
			a_position: {numComponents: 3, data: geometry},
			a_color: {numComponents: 3, data: colors},
			a_normal: {numComponents: 3, data: normals},
			a_texcoord: {numComponents: 2, data: texcoords}
		};

		this.bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);

	};

	getHeightData(img, img_width, img_height) {
		let canvas = document.createElement('canvas');
		canvas.width = img_width;
		canvas.height = img_height;
		let context = canvas.getContext('2d');

		let size = img_width * img_height;
		let data = new Float32Array(size);

		context.drawImage(img, 0, 0);

		for ( let ii = 0; ii < size; ++ii ) {
			data[ii] = 0
		}

		let imgd = context.getImageData(0, 0, img_width, img_height);
		let pix = imgd.data;

		let jj = 0;
		for (let ii = 0, len = pix.length; ii < len; ii += (4)) {
			let all = pix[ii] + pix[ii+1] + pix[ii+2];
			data[jj++] = all / (3 * 255);
			// data[jj++] = pix[ii];
		}

		return data;
	}
}
<commit_msg>habemus normales<commit_after>'use strict';

class Terrain extends GameObject {
	constructor(transform) {
		super(transform);
		// setup GLSL program
		this.programInfo = twgl.createProgramInfo(gl, ["3d-vertex-shader", "3d-fragment-shader"])

		let height_data = this.getHeightData(images.heightmap, images.heightmap.width, images.heightmap.height);

		// first transform everything into vectors
		let position_buffer = [];
		let len_rows = images.heightmap.height;
		let len_cols = images.heightmap.width;
		let center_coord = {
			row: len_rows / 2,
			col: len_cols / 2
		};
		for (let row = 0; row < len_rows; ++row) {
			for (let col = 0; col < len_cols; ++col) {
				let v = v3.create(
					row - center_coord.row,
					height_data[row*len_rows + col],
					col - center_coord.col
				);
				position_buffer.push(v);
			}
		}

		let index_buffer = [];
		for (let row = 0; row < len_rows; ++row) {
			for (let col = 0; col < len_cols; ++col) {
				let current = position_buffer[row*len_rows + col];
				if ((row < (len_rows - 1)) && (col < (len_cols - 1))) {
					let current = row*len_rows + col;
					let right = row*len_rows + col + 1;
					let below = (row + 1)*len_rows + col;
					let diag = (row + 1)*len_rows + col + 1;
					index_buffer.push(
						below, current, right,
						diag, below, right,
					)
				}
			}
		}

		let geometry = [];
		for (let ii = 0, len = index_buffer.length; ii < len; ++ii) {
			let current = position_buffer[index_buffer[ii]];
			geometry.push(
				current[0], current[1], current[2]
			)
		}
		geometry = new Float32Array(geometry);

		let normal_buffer = [];
		for (let ii = 0, len = index_buffer.length; ii < len; ii += 3) {
			normal_buffer.push(v3.create());
		}
		let normal = v3.create();
		let aux1 = v3.create();
		let aux2 = v3.create();
		for (let ii = 0, len = index_buffer.length; ii < len; ii += 3) {
			let v0 = position_buffer[index_buffer[ii]];
			let v1 = position_buffer[index_buffer[ii + 1]];
			let v2 = position_buffer[index_buffer[ii + 2]];

			v3.subtract(v1, v0, aux1);
			v3.subtract(v2, v0, aux2);
			v3.cross(aux1, aux2, normal);
			v3.normalize(normal, normal);

			v3.add(normal, normal_buffer[index_buffer[ii]],
				   normal_buffer[index_buffer[ii]]);
			v3.add(normal, normal_buffer[index_buffer[ii + 1]],
				   normal_buffer[index_buffer[ii + 1]]);
			v3.add(normal, normal_buffer[index_buffer[ii + 2]],
				   normal_buffer[index_buffer[ii + 2]]);
		}
		for (let ii = 0, len = index_buffer.length; ii < len; ii += 1) {
			v3.normalize(normal_buffer[index_buffer[ii]],
				 		 normal_buffer[index_buffer[ii]]);
		}

		let normals = [];
		for (let ii = 0, len = index_buffer.length; ii < len; ++ii) {
			let current = normal_buffer[index_buffer[ii]];
			normals.push(
				current[0], current[1], current[2]
			)
		}
		normals = new Float32Array(normals);

		let colors = [];
		for (let ii = 0, len = geometry.length; ii < len; ++ii) {
			// colors.push(Math.floor(Math.random() * 255));
			colors.push(128);
		}
		colors = new Uint8Array(colors);

		let texcoords = [];
		for (let ii = 0, len = geometry.length/2; ii < len; ++ii) {
			texcoords.push(0, 0);
		}
		texcoords = new Float32Array(texcoords);

		console.log(geometry.length);
		console.log(normals.length);
		console.log(colors.length);

		console.log(geometry);

		let arrays = {
			// Estos nombres dependen de las variables de los shaders
			a_position: {numComponents: 3, data: geometry},
			a_color: {numComponents: 3, data: colors},
			a_normal: {numComponents: 3, data: normals},
			a_texcoord: {numComponents: 2, data: texcoords}
		};

		this.bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);

	};

	getHeightData(img, img_width, img_height) {
		let canvas = document.createElement('canvas');
		canvas.width = img_width;
		canvas.height = img_height;
		let context = canvas.getContext('2d');

		let size = img_width * img_height;
		let data = new Float32Array(size);

		context.drawImage(img, 0, 0);

		for ( let ii = 0; ii < size; ++ii ) {
			data[ii] = 0
		}

		let imgd = context.getImageData(0, 0, img_width, img_height);
		let pix = imgd.data;

		let jj = 0;
		for (let ii = 0, len = pix.length; ii < len; ii += (4)) {
			let all = pix[ii] + pix[ii+1] + pix[ii+2];
			data[jj++] = all / (3 * 255);
			// data[jj++] = pix[ii];
		}

		return data;
	}
}
<|endoftext|>