<commit_before>App.info({
  id: 'cc.newspring.newspringapp',
  name: 'NewSpring',
  description: 'App for NewSpring Church',
  author: 'NewSpring Church',
  email: 'web@newspring.cc',
  website: 'https://newspring.cc',
  version: '0.0.3',
  buildNumber: '137'
});

App.icons({
  // iOS
  'iphone_2x': 'assets/icons/ios/icon-60x60@2x.png',
  'iphone_3x': 'assets/icons/ios/icon-60x60@3x.png',
  'ipad': 'assets/icons/ios/icon-76x76.png',
  'ipad_2x': 'assets/icons/ios/icon-76x76@2x.png',
  'ipad_pro': 'assets/icons/ios/icon-83.5x83.5@2x.png',
  'ios_settings': 'assets/icons/ios/icon-29x29.png',
  'ios_settings_2x': 'assets/icons/ios/icon-29x29@2x.png',
  'ios_settings_3x': 'assets/icons/ios/icon-29x29@3x.png',
  'ios_spotlight': 'assets/icons/ios/icon-40x40.png',
  'ios_spotlight_2x': 'assets/icons/ios/icon-40x40@2x.png',

  // Android
  'android_mdpi': 'assets/icons/android/icon-48x48-mdpi.png',
  'android_hdpi': 'assets/icons/android/icon-72x72-hdpi.png',
  'android_xhdpi': 'assets/icons/android/icon-96x96-xhdpi.png',
  'android_xxhdpi': 'assets/icons/android/icon-144x144-xxhdpi.png',
  'android_xxxhdpi': 'assets/icons/android/icon-192x192-xxxhpdi.png'
});

App.launchScreens({
  // iOS
  'iphone_2x': 'assets/splash/ios/splash-320x480@2x.png',
  'iphone5': 'assets/splash/ios/splash-320x568@2x.png',
  'iphone6': 'assets/splash/ios/splash-667h.png',
  'iphone6p_portrait': 'assets/splash/ios/splash-1242h-portrait.png',
  'iphone6p_landscape': 'assets/splash/ios/splash-1242h-landscape.png',
  'ipad_portrait': 'assets/splash/ios/splash-768x1024.png',
  'ipad_portrait_2x': 'assets/splash/ios/splash-768x1024@2x.png',
  'ipad_landscape': 'assets/splash/ios/splash-1024x768.png',
  'ipad_landscape_2x': 'assets/splash/ios/splash-1024x768@2x.png',

  // Android
  'android_mdpi_portrait': 'assets/splash/android/splash-320x470.png',
  'android_mdpi_landscape': 'assets/splash/android/splash-470x320.png',
  'android_hdpi_portrait': 'assets/splash/android/splash-480x640.png',
  'android_hdpi_landscape': 'assets/splash/android/splash-640x480.png',
  'android_xhdpi_portrait': 'assets/splash/android/splash-720x960.png',
  'android_xhdpi_landscape': 'assets/splash/android/splash-960x720.png',
  'android_xxhdpi_portrait': 'assets/splash/android/splash-1080x1440.png',
  'android_xxhdpi_landscape': 'assets/splash/android/splash-1440x1080.png'
});

App.accessRule('*');

App.setPreference('EnableBitcode', false);
App.setPreference('ShowSplashScreenSpinner', false);
App.setPreference('SplashMaintainAspectRatio', true);

App.setPreference("StatusBarBackgroundColor", "#202020");
App.setPreference("StatusBarStyle", "lightcontent");

App.setPreference("Orientation", "portrait");
<commit_msg>build bump<commit_after>App.info({
  id: 'cc.newspring.newspringapp',
  name: 'NewSpring',
  description: 'App for NewSpring Church',
  author: 'NewSpring Church',
  email: 'web@newspring.cc',
  website: 'https://newspring.cc',
  version: '0.0.3',
  buildNumber: '138'
});

App.icons({
  // iOS
  'iphone_2x': 'assets/icons/ios/icon-60x60@2x.png',
  'iphone_3x': 'assets/icons/ios/icon-60x60@3x.png',
  'ipad': 'assets/icons/ios/icon-76x76.png',
  'ipad_2x': 'assets/icons/ios/icon-76x76@2x.png',
  'ipad_pro': 'assets/icons/ios/icon-83.5x83.5@2x.png',
  'ios_settings': 'assets/icons/ios/icon-29x29.png',
  'ios_settings_2x': 'assets/icons/ios/icon-29x29@2x.png',
  'ios_settings_3x': 'assets/icons/ios/icon-29x29@3x.png',
  'ios_spotlight': 'assets/icons/ios/icon-40x40.png',
  'ios_spotlight_2x': 'assets/icons/ios/icon-40x40@2x.png',

  // Android
  'android_mdpi': 'assets/icons/android/icon-48x48-mdpi.png',
  'android_hdpi': 'assets/icons/android/icon-72x72-hdpi.png',
  'android_xhdpi': 'assets/icons/android/icon-96x96-xhdpi.png',
  'android_xxhdpi': 'assets/icons/android/icon-144x144-xxhdpi.png',
  'android_xxxhdpi': 'assets/icons/android/icon-192x192-xxxhpdi.png'
});

App.launchScreens({
  // iOS
  'iphone_2x': 'assets/splash/ios/splash-320x480@2x.png',
  'iphone5': 'assets/splash/ios/splash-320x568@2x.png',
  'iphone6': 'assets/splash/ios/splash-667h.png',
  'iphone6p_portrait': 'assets/splash/ios/splash-1242h-portrait.png',
  'iphone6p_landscape': 'assets/splash/ios/splash-1242h-landscape.png',
  'ipad_portrait': 'assets/splash/ios/splash-768x1024.png',
  'ipad_portrait_2x': 'assets/splash/ios/splash-768x1024@2x.png',
  'ipad_landscape': 'assets/splash/ios/splash-1024x768.png',
  'ipad_landscape_2x': 'assets/splash/ios/splash-1024x768@2x.png',

  // Android
  'android_mdpi_portrait': 'assets/splash/android/splash-320x470.png',
  'android_mdpi_landscape': 'assets/splash/android/splash-470x320.png',
  'android_hdpi_portrait': 'assets/splash/android/splash-480x640.png',
  'android_hdpi_landscape': 'assets/splash/android/splash-640x480.png',
  'android_xhdpi_portrait': 'assets/splash/android/splash-720x960.png',
  'android_xhdpi_landscape': 'assets/splash/android/splash-960x720.png',
  'android_xxhdpi_portrait': 'assets/splash/android/splash-1080x1440.png',
  'android_xxhdpi_landscape': 'assets/splash/android/splash-1440x1080.png'
});

App.accessRule('*');

App.setPreference('EnableBitcode', false);
App.setPreference('ShowSplashScreenSpinner', false);
App.setPreference('SplashMaintainAspectRatio', true);

App.setPreference("StatusBarBackgroundColor", "#202020");
App.setPreference("StatusBarStyle", "lightcontent");

App.setPreference("Orientation", "portrait");
<|endoftext|>


<commit_before>var factory = function($resource) {

  var Post = $resource("/noo/post/:id/:action", {
    id: '@id',
    projectId: '@projectId'
  }, {
    comment: {
      method: "POST",
      params: {
        action: "comment"
      }
    },
    addFollowers: {
      method: 'POST',
      params: {
        action: 'followers'
      }
    },
    follow: {
      method: 'POST',
      params: {
        action: 'follow'
      }
    },
    unfollow: {
      method: 'POST',
      params: {
        action: 'follow',
        force: 'unfollow'
      }
    },
    queryForCommunity: {
      url: '/noo/community/:communityId/posts'
    },
    queryForUser: {
      url: '/noo/user/:userId/posts'
    },
    queryForNetwork: {
      url: '/noo/network/:id/posts'
    },
    fulfill: {
      method: 'POST',
      url: '/noo/post/:id/fulfill'
    },
    vote: {
      method: 'POST',
      url: '/noo/post/:id/vote'
    },
    findComments: {
      url: '/noo/post/:id/comments',
      isArray: true
    },
    saveInProject: {
      url: '/noo/project/:projectId/posts',
      method: 'POST'
    },
    complain: {
      method: 'POST',
      url: '/noo/post/:id/complain'
    }
  });

  // let's make things a bit more OO around here
  _.extend(Post.prototype, {
    update: function(params, success, error) {
      return Post.save(_.extend({id: this.id}, params), success, error);
    },
    fulfill: function(params, success, error) {
      return Post.fulfill(_.extend({id: this.id}, params), success, error);
    },
    vote: function(params, success, error) {
      return Post.vote(_.extend({id: this.id}, params), success, error);
    },
    findComments: function(params, success, error) {
      return Post.findComments(_.extend({id: this.id}, params), success, error);
    },
    unfollow: function(params, success, error) {
      return Post.unfollow(_.extend({id: this.id}, params), success, error);
    },
    complain: function(params, success, error) {
      return Post.complain(_.extend({id: this.id}, params), success, error);
    }
  });

  return Post;
};

module.exports = function(angularModule) {
  angularModule.factory('Post', factory);
};
<commit_msg>remove unused method<commit_after>var factory = function($resource) {

  var Post = $resource("/noo/post/:id/:action", {
    id: '@id',
    projectId: '@projectId'
  }, {
    comment: {
      method: "POST",
      params: {
        action: "comment"
      }
    },
    addFollowers: {
      method: 'POST',
      params: {
        action: 'followers'
      }
    },
    follow: {
      method: 'POST',
      params: {
        action: 'follow'
      }
    },
    unfollow: {
      method: 'POST',
      params: {
        action: 'follow',
        force: 'unfollow'
      }
    },
    queryForCommunity: {
      url: '/noo/community/:communityId/posts'
    },
    queryForUser: {
      url: '/noo/user/:userId/posts'
    },
    queryForNetwork: {
      url: '/noo/network/:id/posts'
    },
    fulfill: {
      method: 'POST',
      url: '/noo/post/:id/fulfill'
    },
    vote: {
      method: 'POST',
      url: '/noo/post/:id/vote'
    },
    findComments: {
      url: '/noo/post/:id/comments',
      isArray: true
    },
    saveInProject: {
      url: '/noo/project/:projectId/posts',
      method: 'POST'
    },
    complain: {
      method: 'POST',
      url: '/noo/post/:id/complain'
    }
  });

  // let's make things a bit more OO around here
  _.extend(Post.prototype, {
    update: function(params, success, error) {
      return Post.save(_.extend({id: this.id}, params), success, error);
    },
    fulfill: function(params, success, error) {
      return Post.fulfill(_.extend({id: this.id}, params), success, error);
    },
    vote: function(params, success, error) {
      return Post.vote(_.extend({id: this.id}, params), success, error);
    },
    findComments: function(params, success, error) {
      return Post.findComments(_.extend({id: this.id}, params), success, error);
    },
    unfollow: function(params, success, error) {
      return Post.unfollow(_.extend({id: this.id}, params), success, error);
    }
  });

  return Post;
};

module.exports = function(angularModule) {
  angularModule.factory('Post', factory);
};
<|endoftext|>


<commit_before>$(function(){
    var last_input = "";
    var input = "";
    var output = "";

    setInterval(function(){
        if ($(".home .input:last .cursor").text() == ""){
            $(".home .input:last .cursor").text(" ")
        }
        if ($(".home .input:last .cursor").css("background-color") == "transparent"){
            $(".home .input:last .cursor").css("background-color", "rgba(0, 0, 0, 0.6)");
        } else {
            $(".home .input:last .cursor").css("background-color", "rgba(0, 0, 0, 0)");
        };

    }, 500);

    var update_scroller = function() {
        $("#screen").scrollTop($("#screen")[0].scrollHeight);
    };
    var write_it = function(token){
        var curr = $(".home .input:last .cursor");
        curr.text(token);
        curr.removeClass('cursor');
        if (curr.next().length == 0){
            curr.after("")
        }
        curr.next().addClass('cursor');
        input += token;
    };
    var send_it = function(string){
        $.ajax({
            type: "POST",
            url: "/",
            data: {input: input}
        }).done(function(response){
            output = response.output;
            $(".home .output:last").text(output);
        }).fail(function(){
            $(".home .output:last").text("Something Went Wrong");
        }).always(function(){
            $(".cursor").removeClass("cursor");
            $(".home").append("

");
            $(".home").append("

");
        });
    };

    $("#buttons button").click(function(event) {
        $(".home .input ins:not(.input:last .cursor)").css("background-color", "rgba(0, 0, 0, 0)");
        switch ($(this).attr('id')) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                {
                    write_it(this.id);
                }
                break;
            case '+':
            case '-':
            case '/':
            case '*':
            case '\u00B2':
                {
                    if (input == ""){
                        write_it('Ans');
                    };
                    write_it(this.id);
                }
                break;
            case 'right':
                {
                    if ($(".cursor").next().length == 0){
                        break;
                    }
                    var cur = $(".cursor");
                    cur.removeClass("cursor");
                    cur.next().addClass("cursor");
                }
                break;
            case 'left':
                {
                    if ($(".cursor").prev().length == 0){
                        break;
                    }
                    var cur = $(".cursor");
                    cur.removeClass("cursor");
                    cur.prev().addClass("cursor");
                }
                break;
            case 'up':
                {
                    var cur = $(".cursor");
                    cur.removeClass("cursor");
                    if($(cur.prevAll()[35]).length == 0){
                        $(".input:last ins:first").addClass("cursor");
                        break;
                    };
                    $(cur.prevAll()[35]).addClass("cursor");
                }
                break;
            case 'down':
                {
                    var cur = $(".cursor");
                    cur.removeClass("cursor");
                    if($(cur.nextAll()[35]).length == 0){
                        $(".input:last ins:last").addClass("cursor");
                        break;
                    };
                    $(cur.nextAll()[35]).addClass("cursor");
                }
                break;
            case 'delete':
                {
                    var cur = $(".cursor");
                    if (cur.next().length != 0){
                        cur.next().addClass('cursor');
                        cur.remove()
                    };
                }
                break;
            case 'clear':
                {
                    $(".home").empty();
                    $(".home").append("

");
                    $(".home").append("

");
                    input = "";
                }
                break;
            case 'ENTER':
                {
                    if (input == ""){
                        input = last_input;
                    };
                    last_input = input;
                    input = input.split("Ans").join(output);
                    send_it(input);
                    input = "";
                }
                break;
            default: break;
        };
        update_scroller();
    });
});<commit_msg>added more button functionality<commit_after>$(function(){
    var last_input = "";
    var input = "";
    var output = "";

    setInterval(function(){
        if ($(".home .input:last .cursor").text() == ""){
            $(".home .input:last .cursor").text(" ")
        }
        if ($(".home .input:last .cursor").css("background-color") == "transparent"){
            $(".home .input:last .cursor").css("background-color", "rgba(0, 0, 0, 0.6)");
        } else {
            $(".home .input:last .cursor").css("background-color", "rgba(0, 0, 0, 0)");
        };

    }, 500);

    var update_scroller = function() {
        $("#screen").scrollTop($("#screen")[0].scrollHeight);
    };
    var write_it = function(token){
        var curr = $(".home .input:last .cursor");
        curr.text(token);
        curr.removeClass('cursor');
        if (curr.next().length == 0){
            curr.after("")
        }
        curr.next().addClass('cursor');
        input += token;
    };
    var send_it = function(string){
        $.ajax({
            type: "POST",
            url: "/",
            data: {input: input}
        }).done(function(response){
            output = response.output;
            $(".home .output:last").text(output);
        }).fail(function(){
            $(".home .output:last").text("Something Went Wrong");
        }).always(function(){
            $(".cursor").removeClass("cursor");
            $(".home").append("

");
            $(".home").append("

");
        });
    };

    $("#buttons button").click(function(event) {
        $(".home .input ins:not(.input:last .cursor)").css("background-color", "rgba(0, 0, 0, 0)");
        switch ($(this).attr('id')) {
            case '+':
            case '-':
            case '/':
            case '*':
            case '^':
            case '\u00B2':
                {
                    if (input == ""){
                        write_it('Ans');
                    };
                }
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case '10^(':
            case 'log(':
            case 'sin(':
            case 'tan(':
            case 'cos(':
            case 'sin^-1(':
            case 'tan^-1(':
            case 'cos^-1(':
            case '(':
            case '(':
            case ')':
            case '{':
            case '}':
            case ',':
                {
                    write_it(this.id);
                }
                break;
            case 'right':
                {
                    if ($(".cursor").next().length == 0){
                        break;
                    }
                    var cur = $(".cursor");
                    cur.removeClass("cursor");
                    cur.next().addClass("cursor");
                }
                break;
            case 'left':
                {
                    if ($(".cursor").prev().length == 0){
                        break;
                    }
                    var cur = $(".cursor");
                    cur.removeClass("cursor");
                    cur.prev().addClass("cursor");
                }
                break;
            case 'up':
                {
                    var cur = $(".cursor");
                    cur.removeClass("cursor");
                    if($(cur.prevAll()[35]).length == 0){
                        $(".input:last ins:first").addClass("cursor");
                        break;
                    };
                    $(cur.prevAll()[35]).addClass("cursor");
                }
                break;
            case 'down':
                {
                    var cur = $(".cursor");
                    cur.removeClass("cursor");
                    if($(cur.nextAll()[35]).length == 0){
                        $(".input:last ins:last").addClass("cursor");
                        break;
                    };
                    $(cur.nextAll()[35]).addClass("cursor");
                }
                break;
            case 'delete':
                {
                    var cur = $(".cursor");
                    if (cur.next().length != 0){
                        cur.next().addClass('cursor');
                        cur.remove()
                    };
                }
                break;
            case 'clear':
                {
                    $(".home").empty();
                    $(".home").append("

");
                    $(".home").append("

");
                    input = "";
                }
                break;
            case 'ENTER':
                {
                    if (input == ""){
                        input = last_input;
                    };
                    last_input = input;
                    input = input.split("Ans").join(output);
                    send_it(input);
                    input = "";
                }
                break;
            default: break;
        };
        update_scroller();
    });
});<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 *
 * 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.
 */

foam.CLASS({
  package: 'foam.comics',
  name: 'DAOController',

  requires: [
    'foam.comics.SearchMode',
    'foam.u2.borders.NullBorder',
    'foam.nanos.export.CSVDriver'
  ],

  topics: [
    'finished'
  ],

  properties: [
    {
      name: 'data',
      hidden: true
    },
    {
      name: 'predicate'
    },
    {
      name: 'csvDriver',
      factory: function() {
        return this.CSVDriver.create();
      }
    },
    {
      name: 'filteredDAO',
      view: { class: 'foam.u2.view.ScrollTableView' },
      expression: function(data, predicate) {
        return predicate ? data.where(predicate) : data;
      }
    },
    {
      name: 'relationship'
    },
    {
      name: 'selection',
      hidden: true
    },
    {
      class: 'Boolean',
      name: 'createEnabled',
      documentation: 'True to enable the create button.',
      value: true
    },
    {
      class: 'Boolean',
      name: 'editEnabled',
      documentation: 'True to enable the edit button',
      value: true
    },
    {
      class: 'Boolean',
      name: 'selectEnabled',
      documentation: 'True to enable the select button.',
      value: false
    },
    {
      class: 'Boolean',
      name: 'addEnabled',
      documentation: 'True to enable the Add button for adding to a relationship',
      value: false
    },
    {
      class: 'Boolean',
      name: 'exportEnabled',
      documentation: 'True to enable the export button.',
      value: true
    },
    {
      class: 'Boolean',
      name: 'exportCSVEnabled',
      documentation: 'True to enable export csv button'
    },
    {
      class: 'Boolean',
      name: 'toggleEnabled',
      documentation: 'True to enable the toggle filters button.',
      value: true
    },
    {
      name: 'border',
      documentation: `
        If you want the DAO controller to be the content of a border view, set
        the border here.
      `,
      factory: function() { return this.NullBorder.create(); }
    },
    {
      class: 'Boolean',
      name: 'searchHidden',
      documentation: `Used internally to keep track of whether the search panel
        is currently hidden or not.`,
      value: false
    },
    {
      class: 'Enum',
      of: 'foam.comics.SearchMode',
      name: 'searchMode',
      documentation: `
        The level of search capabilities that the controller should have.
      `,
      factory: function() {
        return this.SearchMode.FULL;
      }
    },
    {
      name: 'searchColumns',
      documentation: `
        Lets you pick which properties on the model should be used as search
        filters. You should set the search columns on the model itself and only
        set this property when you want to override the ones set on the model.
      `
    },
    {
      class: 'String',
      name: 'title',
      expression: function(data$data$of) {
        return 'Browse ' + data$data$of.model_.plural;
      }
    },
    {
      class: 'String',
      name: 'subtitle'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.core.Action',
      name: 'primaryAction',
      documentation: `
        The most important action on the page. The view for this controller may
        choose to display this action prominently.
      `
    },
    {
      class: 'foam.u2.ViewSpec',
      name: 'summaryView',
      documentation: `
        Subclasses can set this to override the default summaryView.
      `
    },
    {
      class: 'String',
      name: 'createLabel',
      documentation: `
        Set this to override the label of the create button, which is the
        default primary action.
      `
    },
    {
      class: 'String',
      name: 'detailView',
      value: 'foam.u2.DetailView'
    }
  ],

  actions: [
    {
      name: 'toggleFilters',
      isAvailable: function(toggleEnabled) { return toggleEnabled; },
      code: function() {
        this.searchHidden = ! this.searchHidden;
      }
    },
    {
      name: 'create',
      isAvailable: function(createEnabled) { return createEnabled; },
      code: function() { }
    },
    {
      name: 'edit',
      isEnabled: function(selection) { return !! selection; },
      isAvailable: function(editEnabled) { return editEnabled; },
      code: function() {
        this.pub('edit', this.selection.id);
      }
    },
    {
      name: 'findRelatedObject',
      label: 'Add',
      isAvailable: function(relationship, addEnabled) {
        // Only enable the Add button if we're not already trying to choose a selected item for a relationship.
        return !! ( relationship && relationship.junctionDAO ) && ! addEnabled;
      },
      code: function() { }
    },
    {
      name: 'addSelection',
      label: 'Add',
      isAvailable: function(addEnabled) { return addEnabled; },
      code: function() {
        var self = this;
        this.relationship.add(this.selection).then(function() {
          self.finished.pub();
        });
      }
    },
    {
      name: 'select',
      isAvailable: function(selectEnabled) { return selectEnabled; },
      isEnabled: function(selection) { return !! selection; },
      code: function() {
        this.pub('select', this.selection.id);
        this.finished.pub();
      }
    },
    {
      name: 'export',
      isAvailable: function(exportEnabled) { return exportEnabled; },
      code: function() {
        this.pub('export', this.filteredDAO);
      }
    },
    {
      name: 'exportCSV',
      label: 'Export as CSV',
      icon: 'images/export-icon-resting.svg',
      isAvailable: function(exportCSVEnabled) { return exportCSVEnabled; },
      code: function() {
        this.downloadCSV(this.filteredDAO);
      }
    }
  ],

  listeners: [
    function downloadCSV(data) {
      this.csvDriver.exportDAO(this.__context__, data)
      .then(function(result) {
        result = 'data:text/csv;charset=utf-8,' + result;
        var encodedUri = encodeURI(result);
        var link = document.createElement('a');
        link.setAttribute('href', encodedUri);
        link.setAttribute('download', 'data.csv');
        document.body.appendChild(link);
        link.click();
      });
    }
  ]
});
<commit_msg>Fix incorrect property access<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 *
 * 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.
 */

foam.CLASS({
  package: 'foam.comics',
  name: 'DAOController',

  requires: [
    'foam.comics.SearchMode',
    'foam.u2.borders.NullBorder',
    'foam.nanos.export.CSVDriver'
  ],

  topics: [
    'finished'
  ],

  properties: [
    {
      name: 'data',
      hidden: true
    },
    {
      name: 'predicate'
    },
    {
      name: 'csvDriver',
      factory: function() {
        return this.CSVDriver.create();
      }
    },
    {
      name: 'filteredDAO',
      view: { class: 'foam.u2.view.ScrollTableView' },
      expression: function(data, predicate) {
        return predicate ? data.where(predicate) : data;
      }
    },
    {
      name: 'relationship'
    },
    {
      name: 'selection',
      hidden: true
    },
    {
      class: 'Boolean',
      name: 'createEnabled',
      documentation: 'True to enable the create button.',
      value: true
    },
    {
      class: 'Boolean',
      name: 'editEnabled',
      documentation: 'True to enable the edit button',
      value: true
    },
    {
      class: 'Boolean',
      name: 'selectEnabled',
      documentation: 'True to enable the select button.',
      value: false
    },
    {
      class: 'Boolean',
      name: 'addEnabled',
      documentation: 'True to enable the Add button for adding to a relationship',
      value: false
    },
    {
      class: 'Boolean',
      name: 'exportEnabled',
      documentation: 'True to enable the export button.',
      value: true
    },
    {
      class: 'Boolean',
      name: 'exportCSVEnabled',
      documentation: 'True to enable export csv button'
    },
    {
      class: 'Boolean',
      name: 'toggleEnabled',
      documentation: 'True to enable the toggle filters button.',
      value: true
    },
    {
      name: 'border',
      documentation: `
        If you want the DAO controller to be the content of a border view, set
        the border here.
      `,
      factory: function() { return this.NullBorder.create(); }
    },
    {
      class: 'Boolean',
      name: 'searchHidden',
      documentation: `Used internally to keep track of whether the search panel
        is currently hidden or not.`,
      value: false
    },
    {
      class: 'Enum',
      of: 'foam.comics.SearchMode',
      name: 'searchMode',
      documentation: `
        The level of search capabilities that the controller should have.
      `,
      factory: function() {
        return this.SearchMode.FULL;
      }
    },
    {
      name: 'searchColumns',
      documentation: `
        Lets you pick which properties on the model should be used as search
        filters. You should set the search columns on the model itself and only
        set this property when you want to override the ones set on the model.
      `
    },
    {
      class: 'String',
      name: 'title',
      expression: function(data$of) {
        return 'Browse ' + data$of.model_.plural;
      }
    },
    {
      class: 'String',
      name: 'subtitle'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.core.Action',
      name: 'primaryAction',
      documentation: `
        The most important action on the page. The view for this controller may
        choose to display this action prominently.
      `
    },
    {
      class: 'foam.u2.ViewSpec',
      name: 'summaryView',
      documentation: `
        Subclasses can set this to override the default summaryView.
      `
    },
    {
      class: 'String',
      name: 'createLabel',
      documentation: `
        Set this to override the label of the create button, which is the
        default primary action.
      `
    },
    {
      class: 'String',
      name: 'detailView',
      value: 'foam.u2.DetailView'
    }
  ],

  actions: [
    {
      name: 'toggleFilters',
      isAvailable: function(toggleEnabled) { return toggleEnabled; },
      code: function() {
        this.searchHidden = ! this.searchHidden;
      }
    },
    {
      name: 'create',
      isAvailable: function(createEnabled) { return createEnabled; },
      code: function() { }
    },
    {
      name: 'edit',
      isEnabled: function(selection) { return !! selection; },
      isAvailable: function(editEnabled) { return editEnabled; },
      code: function() {
        this.pub('edit', this.selection.id);
      }
    },
    {
      name: 'findRelatedObject',
      label: 'Add',
      isAvailable: function(relationship, addEnabled) {
        // Only enable the Add button if we're not already trying to choose a selected item for a relationship.
        return !! ( relationship && relationship.junctionDAO ) && ! addEnabled;
      },
      code: function() { }
    },
    {
      name: 'addSelection',
      label: 'Add',
      isAvailable: function(addEnabled) { return addEnabled; },
      code: function() {
        var self = this;
        this.relationship.add(this.selection).then(function() {
          self.finished.pub();
        });
      }
    },
    {
      name: 'select',
      isAvailable: function(selectEnabled) { return selectEnabled; },
      isEnabled: function(selection) { return !! selection; },
      code: function() {
        this.pub('select', this.selection.id);
        this.finished.pub();
      }
    },
    {
      name: 'export',
      isAvailable: function(exportEnabled) { return exportEnabled; },
      code: function() {
        this.pub('export', this.filteredDAO);
      }
    },
    {
      name: 'exportCSV',
      label: 'Export as CSV',
      icon: 'images/export-icon-resting.svg',
      isAvailable: function(exportCSVEnabled) { return exportCSVEnabled; },
      code: function() {
        this.downloadCSV(this.filteredDAO);
      }
    }
  ],

  listeners: [
    function downloadCSV(data) {
      this.csvDriver.exportDAO(this.__context__, data)
      .then(function(result) {
        result = 'data:text/csv;charset=utf-8,' + result;
        var encodedUri = encodeURI(result);
        var link = document.createElement('a');
        link.setAttribute('href', encodedUri);
        link.setAttribute('download', 'data.csv');
        document.body.appendChild(link);
        link.click();
      });
    }
  ]
});
<|endoftext|>


<commit_before>'use strict'
require('./jq-list');var IDE=require('./ide').IDE,prefs=require('./prefs'),esc=require('./util').esc
var $sel=$(),sel,$d // sel:selected item, sel: .data('cn') of the selected item (only if it's unique), $d:dialog
var MIN_VER=[14,1] // minimum supported version
var DFLT_NAME='[New Connection]',TMP_NAME='[Temporary Connection]'
function cmpVer(x,y){return x[0]-y[0]||x[1]-y[1]||0} // compare two versions of the form [major,minor]
function save(){prefs.favs($('#cn-favs>*').map(function(){var h=$(this).data('cn');return h.tmp?null:h}).toArray())}
function favText(x){return x.tmp?TMP_NAME:x.name||DFLT_NAME}
function favDOM(x){return $('
'+esc(favText(x))+'
').data('cn',x)}
function updateFormDetail(){$('#cn-detail>*').hide();$('#cn-'+$('#cn-type').val()).show()} // the part below "Type"
function fmtKey(e){return[e.modKey?'Cmd-':'',e.ctrlKey?'Ctrl-':'',e.altKey?'Alt-':'',e.shiftKey?'Shift-':'',
                          CodeMirror.keyNames[e.which]||''].join('')}
module.exports=function(){
  $('#cn-page').show();document.title='RIDE - Connect'
  $('#cn-fav-cb').change(function(){
    var c=this.checked;c?delete sel.tmp:(sel.tmp=1);$sel.find('a').text(favText(sel))
    $('#cn-fav-name-wr').toggle(c);c&&$('#cn-fav-name').focus();save()
  })
  $('#cn-fav-name').prop('placeholder',DFLT_NAME).on('change keyup',function(e){
    if(sel.name!==this.value){sel.name=this.value;$sel.find('a').text(favText(sel));save()}
  })
  $('#cn-type').change(function(){sel.type=this.value;updateFormDetail();save()})
  updateFormDetail()
  $('#cn-ssh [name=user]').prop('placeholder',process.env.USER||'')
  $('#cn-exe').on('change keyup',function(){$('#cn-exes').val()||prefs.otherExe($(this).val())})
  $('#cn-exes').change(function(){
    var v=$(this).val(),$e=$('#cn-exe').val(v||prefs.otherExe()).prop('readonly',!!v).change();v||$e.focus()
    prefs.selectedExe(v) // todo: do we still need this pref?
  })
  $('#cn-env-add').on('click','a',function(e){
    var t=$(this).text(), e=$('#cn-env')[0], k=t.split('=')[0], s=e.value, m=RegExp('^'+k+'=(.*)$','m').exec(s)
    if(m){e.setSelectionRange(m.index+k.length+1,m.index+m[0].length)}
    else{e.value=s=s.replace(/([^\n])$/,'$1\n')+t+'\n';e.setSelectionRange(s.length-t.length+k.length,s.length-1)}
    return!1
  })
  prefs.favs().forEach(function(x){$('#cn-favs').append(favDOM(x))})
  $('#cn-favs').list().sortable({cursor:'move',revert:true,axis:'y',stop:save})
    .on('click','.go',function(e){$('#cn-go').click()}) // todo: setTimeout?
    .keydown(function(e){switch(fmtKey(e)){
      case'Enter':$('#cn-go:visible').click();return!1
      case'Insert':case'Ctrl-N':$('#cn-new').click();return!1
      case'Delete':$('#cn-del').click();return!1
      case'Ctrl-D':$('#cn-clone').click();return!1
    }})
    .on('list-selection-changed',function(){
      $sel=$('#cn-favs .list-selection')
      var u=$sel.length===1 // is selection unique?
      $('#cn-clone').attr('disabled',!u);$('#cn-del').attr('disabled',!$sel.length);$('#cn-rhs').toggle(u)
      sel=u?$sel.data('cn'):null
      if(u){
        $('#cn-type').val(sel.type||'tcp');updateFormDetail()
        $('#cn-fav-cb').prop('checked',!sel.tmp);$('#cn-fav-name').val(sel.name);$('#cn-fav-name-wr').toggle(!sel.tmp)
        $('#cn-rhs [name]').each(function(){$(this).val(sel[this.name])})
        $('#cn-exes').val(sel.exe).val()||$('#cn-exes').val('') // use sel.exe if available, otherwise use "Other..."
        $('#cn-rhs :text').elastic()
      }
    })
    .list('select',0).find('a').eq(0).focus()
  $('#cn-new').click(function(){
    var $e=favDOM({});$('#cn-favs').append($e).list('select',$e.index());$('#cn-fav-name').focus()
  })
  $('#cn-clone').click(function(){
    if(sel){favDOM($.extend({},sel)).insertBefore($sel);$sel.find('a').focus();save();$('#cn-fav-name').focus()}
  })
  $('#cn-del').click(function(){
    var n=$sel.length
    n&&$.confirm('Are you sure you want to delete the selected connection'+(n>1?'s':'')+'?','Confirmation',
      function(r){if(r){var i=$sel.eq(0).index();$sel.remove();$('#cn-favs').list('select',i,1);save()}})
  })
  $('#cn-go').click(go)
  $('#cn-lhs').resizable({handles:'e',resize:function(e,ui){$('#cn-rhs').css({left:ui.size.width+10})}})
  $('#cn-rhs :text').elastic()
    .on('keyup change',function(){if(sel[this.name]!==this.value){sel[this.name]=this.value;save()}})
  D.socket
    .on('*proxyInfo',function(x){
      $('#cn-exes').html(
        x.interpreters
         .sort(function(a,b){
            return cmpVer(b.version,a.version)||+b.bits-+a.bits||(b.edition==='unicode')-(a.edition==='unicode')
         })
         .map(function(x){
           var s='v'+x.version.join('.')+', '+x.bits+'-bit, '+x.edition[0].toUpperCase()+x.edition.slice(1)
           var supported=cmpVer(x.version,MIN_VER)>=0;supported||(s+=' (unsupported)')
           return'<option value="'+esc(x.exe)+'"'+(supported?'':' disabled')+'>'+esc(s)
         }).join('')+'<option value="">Other...'
      ).val($('#cn-exe').val()).val()||$('#cn-exes').val('')
    })
    .on('*connected',function(x){if($d){$d.dialog('close');$d=null};new IDE().setHostAndPort(x.host,x.port)})
    .on('*connectError',function(x){if($d){$d.dialog('close');$d=null};$.alert(x.err,'Error')})
    .on('*spawned',function(x){D.lastSpawnedExe=x.exe})
    .on('*spawnedError',function(x){$.alert(x.message,'Error')})
    .on('*spawnedExited',function(x){$.alert(x.code!=null?'exited with code '+x.code:'received '+x.signal)})
    .on('*listenError',function(x){if($d){$d.dialog('close');$d=null};$.alert(x.err,'Error')})
    .emit('*getProxyInfo')
  return{
    listen:function(port){port&&$listenPort.val(port);$listen.click()},
    connect:function(s){hp=parseFav(s);D.socket.emit('*connect',{host:hp.host,port:hp.port||4502})}
  }
}
function go(){
  $d&&$d.dialog('close');var t=$('#cn-type').val()
  try{
    if(t==='spawn'){
      D.socket.emit('*spawn',{exe:sel.exe})
    }else if(t==='tcp'){
      $d=$('

')
        .dialog({modal:1,width:350,title:'Connecting...',buttons:{Cancel:function(){$(this).dialog('close')}}})
      D.socket.emit('*connect',{host:sel.host,port:+sel.port})
    }else if(t==='listen'){
      if(sel.port&&(!/^\d+$/.test(sel.port)||+sel.port<1||+sel.port>0xffff)){
        $.alert('Invalid port','Error',function(){$('[name=port]:visible').select()});return
      }
      var port=sel.port||4502
      $d=$(
        '
'+
          '

'+
          'Please start the remote interpreter with'+
          '
RIDE_INIT=\'CONNECT:host:'+port+'\'
'+
          ' in its environment, so it connects here.'+
        '
'
      ).dialog({modal:1,width:450,title:'Waiting for connection...',
                buttons:{Cancel:function(){$d.dialog('close')}},
                close:function(){D.socket.emit('*listenCancel')}})
      D.socket.emit('*listen',{host:sel.host,port:port})
    }else{
      $.alert('nyi')
    }
  }catch(e){
    $.alert(e,'Error')
  }
  return!1
}
<commit_msg>when a fav link is clicked, make sure to select current item before pressing "Go"<commit_after>'use strict'
require('./jq-list');var IDE=require('./ide').IDE,prefs=require('./prefs'),esc=require('./util').esc
var $sel=$(),sel,$d // sel:selected item, sel: .data('cn') of the selected item (only if it's unique), $d:dialog
var MIN_VER=[14,1] // minimum supported version
var DFLT_NAME='[New Connection]',TMP_NAME='[Temporary Connection]'
function cmpVer(x,y){return x[0]-y[0]||x[1]-y[1]||0} // compare two versions of the form [major,minor]
function save(){prefs.favs($('#cn-favs>*').map(function(){var h=$(this).data('cn');return h.tmp?null:h}).toArray())}
function favText(x){return x.tmp?TMP_NAME:x.name||DFLT_NAME}
function favDOM(x){return $('
'+esc(favText(x))+'
').data('cn',x)}
function updateFormDetail(){$('#cn-detail>*').hide();$('#cn-'+$('#cn-type').val()).show()} // the part below "Type"
function fmtKey(e){return[e.modKey?'Cmd-':'',e.ctrlKey?'Ctrl-':'',e.altKey?'Alt-':'',e.shiftKey?'Shift-':'',
                          CodeMirror.keyNames[e.which]||''].join('')}
module.exports=function(){
  $('#cn-page').show();document.title='RIDE - Connect'
  $('#cn-fav-cb').change(function(){
    var c=this.checked;c?delete sel.tmp:(sel.tmp=1);$sel.find('a').text(favText(sel))
    $('#cn-fav-name-wr').toggle(c);c&&$('#cn-fav-name').focus();save()
  })
  $('#cn-fav-name').prop('placeholder',DFLT_NAME).on('change keyup',function(e){
    if(sel.name!==this.value){sel.name=this.value;$sel.find('a').text(favText(sel));save()}
  })
  $('#cn-type').change(function(){sel.type=this.value;updateFormDetail();save()})
  updateFormDetail()
  $('#cn-ssh [name=user]').prop('placeholder',process.env.USER||'')
  $('#cn-exe').on('change keyup',function(){$('#cn-exes').val()||prefs.otherExe($(this).val())})
  $('#cn-exes').change(function(){
    var v=$(this).val(),$e=$('#cn-exe').val(v||prefs.otherExe()).prop('readonly',!!v).change();v||$e.focus()
    prefs.selectedExe(v) // todo: do we still need this pref?
  })
  $('#cn-env-add').on('click','a',function(e){
    var t=$(this).text(), e=$('#cn-env')[0], k=t.split('=')[0], s=e.value, m=RegExp('^'+k+'=(.*)$','m').exec(s)
    if(m){e.setSelectionRange(m.index+k.length+1,m.index+m[0].length)}
    else{e.value=s=s.replace(/([^\n])$/,'$1\n')+t+'\n';e.setSelectionRange(s.length-t.length+k.length,s.length-1)}
    return!1
  })
  prefs.favs().forEach(function(x){$('#cn-favs').append(favDOM(x))})
  $('#cn-favs').list().sortable({cursor:'move',revert:true,axis:'y',stop:save})
    .on('click','.go',function(e){
      $('#conn-favs').list('select',$(this).parentsUntil('#conn-favs').last().index())
      setTimeout(function(){$('#cn-go').click()},1) // todo
    })
    .keydown(function(e){switch(fmtKey(e)){
      case'Enter':$('#cn-go:visible').click();return!1
      case'Insert':case'Ctrl-N':$('#cn-new').click();return!1
      case'Delete':$('#cn-del').click();return!1
      case'Ctrl-D':$('#cn-clone').click();return!1
    }})
    .on('list-selection-changed',function(){
      $sel=$('#cn-favs .list-selection')
      var u=$sel.length===1 // is selection unique?
      $('#cn-clone').attr('disabled',!u);$('#cn-del').attr('disabled',!$sel.length);$('#cn-rhs').toggle(u)
      sel=u?$sel.data('cn'):null
      if(u){
        $('#cn-type').val(sel.type||'tcp');updateFormDetail()
        $('#cn-fav-cb').prop('checked',!sel.tmp);$('#cn-fav-name').val(sel.name);$('#cn-fav-name-wr').toggle(!sel.tmp)
        $('#cn-rhs [name]').each(function(){$(this).val(sel[this.name])})
        $('#cn-exes').val(sel.exe).val()||$('#cn-exes').val('') // use sel.exe if available, otherwise use "Other..."
        $('#cn-rhs :text').elastic()
      }
    })
    .list('select',0).find('a').eq(0).focus()
  $('#cn-new').click(function(){
    var $e=favDOM({});$('#cn-favs').append($e).list('select',$e.index());$('#cn-fav-name').focus()
  })
  $('#cn-clone').click(function(){
    if(sel){favDOM($.extend({},sel)).insertBefore($sel);$sel.find('a').focus();save();$('#cn-fav-name').focus()}
  })
  $('#cn-del').click(function(){
    var n=$sel.length
    n&&$.confirm('Are you sure you want to delete the selected connection'+(n>1?'s':'')+'?','Confirmation',
      function(r){if(r){var i=$sel.eq(0).index();$sel.remove();$('#cn-favs').list('select',i,1);save()}})
  })
  $('#cn-go').click(go)
  $('#cn-lhs').resizable({handles:'e',resize:function(e,ui){$('#cn-rhs').css({left:ui.size.width+10})}})
  $('#cn-rhs :text').elastic()
    .on('keyup change',function(){if(sel[this.name]!==this.value){sel[this.name]=this.value;save()}})
  D.socket
    .on('*proxyInfo',function(x){
      $('#cn-exes').html(
        x.interpreters
         .sort(function(a,b){
            return cmpVer(b.version,a.version)||+b.bits-+a.bits||(b.edition==='unicode')-(a.edition==='unicode')
         })
         .map(function(x){
           var s='v'+x.version.join('.')+', '+x.bits+'-bit, '+x.edition[0].toUpperCase()+x.edition.slice(1)
           var supported=cmpVer(x.version,MIN_VER)>=0;supported||(s+=' (unsupported)')
           return'<option value="'+esc(x.exe)+'"'+(supported?'':' disabled')+'>'+esc(s)
         }).join('')+'<option value="">Other...'
      ).val($('#cn-exe').val()).val()||$('#cn-exes').val('')
    })
    .on('*connected',function(x){if($d){$d.dialog('close');$d=null};new IDE().setHostAndPort(x.host,x.port)})
    .on('*connectError',function(x){if($d){$d.dialog('close');$d=null};$.alert(x.err,'Error')})
    .on('*spawned',function(x){D.lastSpawnedExe=x.exe})
    .on('*spawnedError',function(x){$.alert(x.message,'Error')})
    .on('*spawnedExited',function(x){$.alert(x.code!=null?'exited with code '+x.code:'received '+x.signal)})
    .on('*listenError',function(x){if($d){$d.dialog('close');$d=null};$.alert(x.err,'Error')})
    .emit('*getProxyInfo')
  return{
    listen:function(port){port&&$listenPort.val(port);$listen.click()},
    connect:function(s){hp=parseFav(s);D.socket.emit('*connect',{host:hp.host,port:hp.port||4502})}
  }
}
function go(){
  $d&&$d.dialog('close');var t=$('#cn-type').val()
  try{
    if(t==='spawn'){
      D.socket.emit('*spawn',{exe:sel.exe})
    }else if(t==='tcp'){
      $d=$('

')
        .dialog({modal:1,width:350,title:'Connecting...',buttons:{Cancel:function(){$(this).dialog('close')}}})
      D.socket.emit('*connect',{host:sel.host,port:+sel.port})
    }else if(t==='listen'){
      if(sel.port&&(!/^\d+$/.test(sel.port)||+sel.port<1||+sel.port>0xffff)){
        $.alert('Invalid port','Error',function(){$('[name=port]:visible').select()});return
      }
      var port=sel.port||4502
      $d=$(
        '
'+
          '

'+
          'Please start the remote interpreter with'+
          '
RIDE_INIT=\'CONNECT:host:'+port+'\'
'+
          ' in its environment, so it connects here.'+
        '
'
      ).dialog({modal:1,width:450,title:'Waiting for connection...',
                buttons:{Cancel:function(){$d.dialog('close')}},
                close:function(){D.socket.emit('*listenCancel')}})
      D.socket.emit('*listen',{host:sel.host,port:port})
    }else{
      $.alert('nyi')
    }
  }catch(e){
    $.alert(e,'Error')
  }
  return!1
}
<|endoftext|>


<commit_before>// Karma configuration
// Generated on Sat Sep 26 2015 21:17:15 GMT+0200 (CEST)

module.exports = function (config) {
    config.set({

        // base path that will be used to resolve all patterns (eg. files, exclude)
        basePath: '',

        // frameworks to use
        // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
        frameworks: ['jasmine'],

        // list of files / patterns to load in the browser
        files: [

            // include dependencies
            'node_modules/angular/angular.js',
            'node_modules/angular-mocks/angular-mocks.js',
            'node_modules/angular-ui-router/release/angular-ui-router.js',
            'node_modules/angular-ui-bootstrap/dist/ui-bootstrap.js',

            // include navbar directive
            'src/navbar.js',

            // include directive templates
            'template/navbar-ul.html.js',
            'template/navbar-li.html.js',

            // include test folder
            'test/*.spec.js'
        ],

        // list of files to exclude
        exclude: [],

        // preprocess matching files before serving them to the browser
        // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
        preprocessors: {},

        // test results reporter to use
        // possible values: 'dots', 'progress'
        // available reporters: https://npmjs.org/browse/keyword/karma-reporter
        reporters: ['progress'],

        // web server port
        port: 9876,

        // enable / disable colors in the output (reporters and logs)
        colors: true,

        // level of logging
        // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
        logLevel: config.LOG_INFO,

        // enable / disable watching file and executing tests whenever any file changes
        autoWatch: true,

        // start these browsers
        // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
        browsers: ['Chrome'],

        // Continuous Integration mode
        // if true, Karma captures browsers, runs the tests and exits
        singleRun: false,

        // Travis configuration to run Chrome
        customLaunchers: {
            Chrome_travis_ci: {
                base: 'Chrome',
                flags: ['--no-sandbox']
            }
        }
    });

    if(process.env.TRAVIS) {
        configuration.browsers = ['Chrome_travis_ci'];
    }
};
<commit_msg>Fix missing configuration on travis build.<commit_after>// Karma configuration
// Generated on Sat Sep 26 2015 21:17:15 GMT+0200 (CEST)

module.exports = function (config) {

    var configuration = {

        // base path that will be used to resolve all patterns (eg. files, exclude)
        basePath: '',

        // frameworks to use
        // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
        frameworks: ['jasmine'],

        // list of files / patterns to load in the browser
        files: [

            // include dependencies
            'node_modules/angular/angular.js',
            'node_modules/angular-mocks/angular-mocks.js',
            'node_modules/angular-ui-router/release/angular-ui-router.js',
            'node_modules/angular-ui-bootstrap/dist/ui-bootstrap.js',

            // include navbar directive
            'src/navbar.js',

            // include directive templates
            'template/navbar-ul.html.js',
            'template/navbar-li.html.js',

            // include test folder
            'test/*.spec.js'
        ],

        // list of files to exclude
        exclude: [],

        // preprocess matching files before serving them to the browser
        // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
        preprocessors: {},

        // test results reporter to use
        // possible values: 'dots', 'progress'
        // available reporters: https://npmjs.org/browse/keyword/karma-reporter
        reporters: ['progress'],

        // web server port
        port: 9876,

        // enable / disable colors in the output (reporters and logs)
        colors: true,

        // level of logging
        // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
        logLevel: config.LOG_INFO,

        // enable / disable watching file and executing tests whenever any file changes
        autoWatch: true,

        // start these browsers
        // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
        browsers: ['Chrome'],

        // Continuous Integration mode
        // if true, Karma captures browsers, runs the tests and exits
        singleRun: false,

        // Travis configuration to run Chrome
        customLaunchers: {
            Chrome_travis_ci: {
                base: 'Chrome',
                flags: ['--no-sandbox']
            }
        }
    };

    if (process.env.TRAVIS) {
        configuration.browsers = ['Chrome_travis_ci'];
    }

    config.set(configuration);
};
<|endoftext|>


<commit_before>import assert from 'assert';
import PanelsActionCreator from '../actions/panels-action-creator';
import SculptureActionCreator from '../actions/sculpture-action-creator';
import MoleGameActionCreator from '../actions/mole-game-action-creator';
import TrackedPanels from '../utils/tracked-panels';
import COLORS from '../constants/colors';
import PanelAnimation from '../animation/panel-animation';
import Frame from '../animation/frame';

/**
 * Handles both the Minimal State (transitions) and the Mole Game Logic
 */
export default class MoleGameLogic {
  // These are automatically added to the sculpture store
  static trackedProperties = {
    panelCount: 0, // Game progress (0..30)
    panels: new TrackedPanels(),  // panel -> state
    complete: false,
  };

  /*
   * The constructor manages transition _into_ the Minimal State
   */
  constructor(store, config) {
    this.store = store;
    this.config = config;
    this.gameConfig = config.MOLE_GAME;
    this.sculptureActionCreator = new SculptureActionCreator(this.store.dispatcher);
    this.moleGameActionCreator = new MoleGameActionCreator(this.store.dispatcher);

    // Unique panel objects: panelKey -> panel
    this._panels = {};
    this.config.GAME_STRIPS.forEach(stripId => {
      this._lights.get(stripId).panelIds.forEach((panelId) => {
        const key = this._hash(stripId, panelId);
        this._panels[key] = { stripId, panelId, key };
      });
    });

    // Force RGB strips to black.
    // FIXME: This is a temporary fix for a firmware bug not respecting intensity
    this._lights.setColor(this.config.LIGHTS.RGB_STRIPS, null, COLORS.BLACK);
  }

  get data() {
    return this.store.data.get('mole');
  }

  /*!
   * Starts the game logic. Only the master should call this method.
   */
  start() {
    this._initRemainingPanels();
    this.data.set('complete', false);
    this.data.set('panelCount', 0);
    this.data.get('panels').clear();
    this._registerMoveDelay(0); // Request a new active panel immediately
  }

  /**
   * _remainingPanels is a set of panel keys for panels with STATE_OFF,
   * and are used to select random panels.
   */
  _initRemainingPanels() {
    this._remainingPanels = new Set();
    Object.keys(this._panels).forEach((panelkey) => this._remainingPanels.add(panelkey));
  }

  end() {
    this.config.GAME_STRIPS.forEach(stripId => this._lights.deactivateAll(stripId));
  }

  /**
   * handleActionPayload must _synchronously_ change tracked data in sculpture store.
   * Any asynchronous behavior must happen by dispatching actions.
   * We're _not_ allowed to dispatch actions synchronously.
   */
  handleActionPayload(payload) {
    if (this.data.get("complete")) return;

    const actionHandlers = {
      [PanelsActionCreator.PANEL_PRESSED]: this._actionPanelPressed.bind(this),
      [MoleGameActionCreator.AVAIL_PANEL]: this._actionAvailPanel.bind(this),
      [MoleGameActionCreator.DEAVAIL_PANEL]: this._actionDeavailPanel.bind(this),
      [SculptureActionCreator.MERGE_STATE]: this._actionMergeState.bind(this),
    };

    const actionHandler = actionHandlers[payload.actionType];
    if (actionHandler) actionHandler(payload);
  }

  /**
   * Asynchronous panel activation
   */
  _actionAvailPanel(panel) {
    this._availPanel(panel);
  }

  /**
   * Asynchronous panel deactivation
   */
  _actionDeavailPanel(panel) {
    this._deavailPanel(panel);
  }

  /**
   * Local action:
   *
   * If an active panel is pressed:
   * 1) Turn panel to location color
   * 2) Wait a short moment
   * 3) Avail the next panel
   * 4) increase/decrease # of simulaneously active panels
   */
  _actionPanelPressed(payload) {
    let {stripId, panelId, pressed} = payload;

    const state = this.data.get('panels').getPanelState(stripId, panelId);
    if (state === TrackedPanels.STATE_OFF) {
      if (pressed) {
        this._lights.setColor(stripId, panelId, this.store.locationColor);
        this._lights.setIntensity(stripId, panelId, this.config.PANEL_DEFAULTS.ACTIVE_INTENSITY);
      }
      else {
        this._lights.setToDefaultColor(stripId, panelId);
        this._lights.setIntensity(stripId, panelId, this.config.PANEL_DEFAULTS.INACTIVE_INTENSITY);
      }
      return;
    }

    // If an active panel was touched
    if (state === TrackedPanels.STATE_ON) {
      this._colorPanel(payload);

      // Only for master:
      if (this.store.isMaster()) {
        this._remainingPanels.delete(this._getPanelKey(payload));
        // If we have a timeout on this panel, kill the timeout
        this._removeTimeout(this._getPanelKey(payload));
        this._advanceGame();
      }
    }
  }

  /**
   * Trigger winning of game. Should only be called by master
   */
  _winGame() {
    // Count all panel colors
    const colorCount = { };
    this.config.GAME_STRIPS.forEach(stripId => {
      const panelIds = this._lights.get(stripId).panelIds;
      panelIds.forEach((panelId) => {
        const col = this._lights.getColor(stripId, panelId);
        colorCount[col] = (colorCount[col] || 0) + 1;
      });
    });

    // Determine winning color
    const winningColor = Object.entries(colorCount).reduce((a, b) => a[1] > b[1] ? a : b)[0];
    console.log(`Winning color: ${winningColor}`);

    // Transition animation:
    const transitionFrames = [
      // Disable interaction
      new Frame(() => {
        this._lights.deactivateAll();
      }, 0),
    // 1) Turn off non-winning colors
      new Frame(() => {
        this.config.GAME_STRIPS.forEach(stripId => {
          const panelIds = this._lights.get(stripId).panelIds;
          panelIds.forEach((panelId) => {
            if (this._lights.getColor(stripId, panelId) !== winningColor) {
              this._lights.setIntensity(stripId, panelId, 0);
            }
          });
        });
      }, 4500),
    // 2) Turn off all remaining colors and start next game
      new Frame(() => {
        this.config.GAME_STRIPS.forEach(stripId => {
          this._lights.setIntensity(stripId, null, 0);
        });
        this.data.set('complete', true);
        setTimeout(() => this.sculptureActionCreator.sendStartNextGame(), 3000);
      }, 5000),
    ];

    const transitionAnimation = new PanelAnimation(transitionFrames);
    this.store.playAnimation(transitionAnimation);
  }

  /**
   * Advance game. Should only be called by master
   */
  _advanceGame() {
    let panelCount = this.data.get("panelCount") + 1;
    this.data.set('panelCount', panelCount);
    if (panelCount === this.gameConfig.GAME_END) {
      this._winGame();
    }
    else {
      // Determine whether to add, remove of keep # of simultaneous panels
      const addPanels = 1 + (this.gameConfig.NUM_ACTIVE_PANELS[panelCount] ? this.gameConfig.NUM_ACTIVE_PANELS[panelCount] : 0);

      for (let i=0; i<addPanels; i++) {
        // Wait before next panel
        this._registerMoveDelay(this.gameConfig.PANEL_SUCCESS_DELAY);
      }
    }
  }

  /*!
   * Remote action
   * If we're master, we're responsible for correctly advancing the game
   */
  _actionMergeState(payload) {
    if (!payload.mole) return; // Only handle mole state

    const moleData = this.data;
    const moleChanges = payload.mole;
    const moleProps = payload.metadata.props.mole;

    // Master owns the panelCount field
    if (!this.store.isMaster()) {
      if (moleChanges.hasOwnProperty('panelCount')) {
        moleData.set('panelCount', moleChanges.panelCount, moleProps.panelCount);
      }
    }

    // Iterate over changed panel states
    if (moleChanges.hasOwnProperty('panels')) {
      const panels = moleData.get('panels');
      const changedPanels = moleChanges.panels;
      for (let panelKey of Object.keys(changedPanels)) {
        const newstate = changedPanels[panelKey];
        const oldstate = panels.getPanelStateByKey(panelKey);

        // Slaves just merge
        if (!this.store.isMaster()) {
          panels.setPanelStateByKey(panelKey, newstate, moleProps.panels[panelKey]);
        }
        else {
          // Master will only accept state modification to STATE_IGNORED
          if (newstate !== oldstate && newstate === TrackedPanels.STATE_IGNORED) {
            panels.setPanelStateByKey(panelKey, newstate);

            // oldstate was ON
            // Normal situation (no race condition): oldstate === ON
            if (this._hasTimeout(panelKey)) { // Normal situation (no race condition): oldstate === ON
              console.log('Merge normal (after new panel)');
              if (oldstate !== TrackedPanels.STATE_ON) {
                assert('state must be STATE_ON');
              }

              this._remainingPanels.delete(panelKey);
              this._removeTimeout(panelKey);
              this._advanceGame();
            }
            // oldstate was OFF
            else {
              if (oldstate !== TrackedPanels.STATE_OFF) {
                assert('state must be STATE_OFF');
              }

              // If we don't have a timeout any longer, we have a race condition.
              // We could be in a few states:
              // 1_ PANEL_SUCCESS_TIMER is active
              // Master already hit the panel => Master wins.
              // This is a NOP since master will pass on the correct colors automatically

              // 2) PANEL_MOVE_DELAY timer is active
              // Master just deactivated the panel, waiting to activate the next one
              // => Kill timer and advance the game
              if (oldstate === TrackedPanels.STATE_OFF && this._panels[panelKey].moveDelay) {
                console.log('merged during MOVE_DELAY');
                clearTimeout(this._panels[panelKey].moveDelay);
                delete this._panels[panelKey].moveDelay;
                this._remainingPanels.delete(panelKey);
                this._advanceGame();
              }

              // 3) The next panel was already made active
              // Accept the change. We also need to reset the intensity
              //
              // Note: This can never be the last move, as the last panel would then be
              // reactivated, not OFF.
              else if (oldstate === TrackedPanels.STATE_OFF && !this._panels[panelKey].moveDelay) {
                console.log('merged after new panel');
                this._remainingPanels.delete(panelKey);
                // FIXME: If this is the last panel, we may need to go to _winGame()
                this.data.set('panelCount', this.data.get("panelCount") + 1);
                this._lights.setIntensity(this._panels[panelKey].stripId, this._panels[panelKey].panelId, this.gameConfig.COLORED_PANEL_INTENSITY);
              }
              else {
                console.log('merge: Master wins - slave is late');
              }
            }
            // Make sure changes are merged by all slaves
            this.store.reassertChanges();
          }
        }
      }
    }
  }

  _hash(stripId, panelId) {
    return `${stripId},${panelId}`;
  }

  _getPanelKey({stripId, panelId}) {
    return this._hash(stripId, panelId);
  }

  /**
   * Request the next active panel, as the game progresses
   * Returns {panel, lifetime}
   */
  _nextActivePanel(count) {
    if (count < this.gameConfig.INITIAL_PANELS.length) {
      const panel = this.gameConfig.INITIAL_PANELS[count];
      const panelkey = this._getPanelKey(panel);
      return { panelkey, lifetime: this._getPanelLifetime(count) }; // No timeout
    }
    return { panelkey: this._getRandomPanel(), lifetime: this._getPanelLifetime(count)};
  }

  /**
   * Selects and returns a random panelkey from _remainingPanels
   */
  _getRandomPanel() {
    const idx = Math.floor(Math.random() * this._remainingPanels.size);
    const iter = this._remainingPanels.values();
    let curr = iter.next();
    for (let i=0; i<idx; i++) curr = iter.next();
    return curr.value;
  }

  _getPanelLifetime(count) {
    // find last and next lifetime values for interpolation
    let last, next;
    for (let elem of this.gameConfig.PANEL_LIFETIME) {
      if (!last || elem.count <= count) last = elem;
      next = elem;
      if (elem.count > count) break;
    }

    let min, max;
    if (last === next) {
      min = last.range[0];
      max = last.range[1];
    }
    else {
      min = last.range[0] + (next.range[0] - last.range[0]) * (count - last.count) / (next.count - last.count);
      max = last.range[1] + (next.range[1] - last.range[1]) * (count - last.count) / (next.count - last.count);
    }
    return 1000 * (Math.random() * (max - min) + min);
  }

  /**
   * Called if an active panel times out: We'll move the panel (deavail+pause+avail).
   */
  _panelTimeout(oldPanel) {
    const key = this._getPanelKey(oldPanel);
    delete this._panels[key].timeout;
    this.moleGameActionCreator.sendDeavailPanel(oldPanel);
    this._registerMoveDelay(this.gameConfig.PANEL_MOVE_DELAY, key);
  }

  _requestPanel(oldPanelKey = null) {
    if (oldPanelKey) delete this._panels[oldPanelKey].moveDelay;
    const {panelkey, lifetime} = this._nextActivePanel(this.data.get("panelCount"));
    if (!panelkey) {
      console.error('No panel key!');
    }
    this.moleGameActionCreator.sendAvailPanel(this._panels[panelkey]);
    this._panels[panelkey].timeout = setTimeout(this._panelTimeout.bind(this, this._panels[panelkey]), lifetime);
  }

  _registerMoveDelay(delay, panelKey = null) {
    const tid = setTimeout(this._requestPanel.bind(this, panelKey), delay);
    if (panelKey) this._panels[panelKey].moveDelay = tid;
  }

  /**
   * Check if we have a timeout registered on the given panel
   */
  _hasTimeout(key) {
    return this._panels[key].timeout !== undefined;
  }

  /**
   * Removes a timeout with the given panel key
   */
  _removeTimeout(key) {
    if (this._hasTimeout(key)) {
      clearTimeout(this._panels[key].timeout);
      delete this._panels[key].timeout;
    }
  }

  /**
   * Only called from a timeout
   */
  _availPanel(panel) {
    this._setPanelState(panel, TrackedPanels.STATE_ON);
    this._remainingPanels.delete(this._getPanelKey(panel));
    this._lights.setIntensity(panel.stripId, panel.panelId, this.gameConfig.ACTIVE_PANEL_INTENSITY);
  }

  /**
   * Only called from a timeout
   */
  _deavailPanel(panel) {
    this._remainingPanels.add(this._getPanelKey(panel));
    this._setPanelState(panel, TrackedPanels.STATE_OFF);
    this._lights.setIntensity(panel.stripId, panel.panelId, this.gameConfig.INACTIVE_PANEL_INTENSITY);
  }

  /**
   * Called by the sculpture first hitting an ON panel
   */
  _colorPanel(panel) {
    this._setPanelState(panel, TrackedPanels.STATE_IGNORED);
    this._lights.setIntensity(panel.stripId, panel.panelId, this.gameConfig.COLORED_PANEL_INTENSITY);
    this._lights.setColor(panel.stripId, panel.panelId, this.store.locationColor);
  }

  get _lights() {
    return this.store.data.get('lights');
  }

  _setPanelState({stripId, panelId}, state) {
    this.data.get('panels').setPanelState(stripId, panelId, state);
  }

}
<commit_msg>minor refactoring<commit_after>import assert from 'assert';
import PanelsActionCreator from '../actions/panels-action-creator';
import SculptureActionCreator from '../actions/sculpture-action-creator';
import MoleGameActionCreator from '../actions/mole-game-action-creator';
import TrackedPanels from '../utils/tracked-panels';
import COLORS from '../constants/colors';
import PanelAnimation from '../animation/panel-animation';
import Frame from '../animation/frame';

/**
 * Handles both the Minimal State (transitions) and the Mole Game Logic
 */
export default class MoleGameLogic {
  // These are automatically added to the sculpture store
  static trackedProperties = {
    panelCount: 0, // Game progress (0..30)
    panels: new TrackedPanels(),  // panel -> state
    complete: false,
  };

  /*
   * The constructor manages transition _into_ the Minimal State
   */
  constructor(store, config) {
    this.store = store;
    this.config = config;
    this.gameConfig = config.MOLE_GAME;
    this.sculptureActionCreator = new SculptureActionCreator(this.store.dispatcher);
    this.moleGameActionCreator = new MoleGameActionCreator(this.store.dispatcher);

    // Unique panel objects: panelKey -> panel
    this._panels = {};
    this.config.GAME_STRIPS.forEach(stripId => {
      this._lights.get(stripId).panelIds.forEach((panelId) => {
        const key = this._hash(stripId, panelId);
        this._panels[key] = { stripId, panelId, key };
      });
    });

    // Force RGB strips to black.
    // FIXME: This is a temporary fix for a firmware bug not respecting intensity
    this._lights.setColor(this.config.LIGHTS.RGB_STRIPS, null, COLORS.BLACK);
  }

  get data() {
    return this.store.data.get('mole');
  }

  /*!
   * Starts the game logic. Only the master should call this method.
   */
  start() {
    this._initRemainingPanels();
    this.data.set('complete', false);
    this.data.set('panelCount', 0);
    this.data.get('panels').clear();
    this._registerMoveDelay(0); // Request a new active panel immediately
  }

  /**

  _turnOffAllGameStrips() {
    this.config.GAME_STRIPS.forEach(stripId => this._lights.setIntensity(stripId, null, 0));
  }

   * _remainingPanels is a set of panel keys for panels with STATE_OFF,
   * and are used to select random panels.
   */
  _initRemainingPanels() {
    this._remainingPanels = new Set();
    Object.keys(this._panels).forEach((panelkey) => this._remainingPanels.add(panelkey));
  }

  end() {
    this.config.GAME_STRIPS.forEach(stripId => this._lights.deactivateAll(stripId));
  }

  /**
   * handleActionPayload must _synchronously_ change tracked data in sculpture store.
   * Any asynchronous behavior must happen by dispatching actions.
   * We're _not_ allowed to dispatch actions synchronously.
   */
  handleActionPayload(payload) {
    if (this.data.get("complete")) return;

    const actionHandlers = {
      [PanelsActionCreator.PANEL_PRESSED]: this._actionPanelPressed.bind(this),
      [MoleGameActionCreator.AVAIL_PANEL]: this._actionAvailPanel.bind(this),
      [MoleGameActionCreator.DEAVAIL_PANEL]: this._actionDeavailPanel.bind(this),
      [SculptureActionCreator.MERGE_STATE]: this._actionMergeState.bind(this),
    };

    const actionHandler = actionHandlers[payload.actionType];
    if (actionHandler) actionHandler(payload);
  }

  /**
   * Asynchronous panel activation
   */
  _actionAvailPanel(panel) {
    this._availPanel(panel);
  }

  /**
   * Asynchronous panel deactivation
   */
  _actionDeavailPanel(panel) {
    this._deavailPanel(panel);
  }

  /**
   * Local action:
   *
   * If an active panel is pressed:
   * 1) Turn panel to location color
   * 2) Wait a short moment
   * 3) Avail the next panel
   * 4) increase/decrease # of simulaneously active panels
   */
  _actionPanelPressed(payload) {
    let {stripId, panelId, pressed} = payload;

    const state = this.data.get('panels').getPanelState(stripId, panelId);
    if (state === TrackedPanels.STATE_OFF) {
      if (pressed) {
        this._lights.setColor(stripId, panelId, this.store.locationColor);
        this._lights.setIntensity(stripId, panelId, this.config.PANEL_DEFAULTS.ACTIVE_INTENSITY);
      }
      else {
        this._lights.setToDefaultColor(stripId, panelId);
        this._lights.setIntensity(stripId, panelId, this.config.PANEL_DEFAULTS.INACTIVE_INTENSITY);
      }
      return;
    }

    // If an active panel was touched
    if (state === TrackedPanels.STATE_ON) {
      this._colorPanel(payload);

      // Only for master:
      if (this.store.isMaster()) {
        this._remainingPanels.delete(this._getPanelKey(payload));
        // If we have a timeout on this panel, kill the timeout
        this._removeTimeout(this._getPanelKey(payload));
        this._advanceGame();
      }
    }
  }

  /**
   * Trigger winning of game. Should only be called by master
   */
  _winGame() {
    // Count all panel colors
    const colorCount = { };
    this.config.GAME_STRIPS.forEach(stripId => {
      const panelIds = this._lights.get(stripId).panelIds;
      panelIds.forEach((panelId) => {
        const col = this._lights.getColor(stripId, panelId);
        colorCount[col] = (colorCount[col] || 0) + 1;
      });
    });

    // Determine winning color
    const winningColor = Object.entries(colorCount).reduce((a, b) => a[1] > b[1] ? a : b)[0];
    console.log(`Winning color: ${winningColor}`);

    // Transition animation:
    const transitionFrames = [
      // Disable interaction
      new Frame(() => {
        this._lights.deactivateAll();
      }, 0),
    // 1) Turn off non-winning colors
      new Frame(() => {
        this.config.GAME_STRIPS.forEach(stripId => {
          const panelIds = this._lights.get(stripId).panelIds;
          panelIds.forEach((panelId) => {
            if (this._lights.getColor(stripId, panelId) !== winningColor) {
              this._lights.setIntensity(stripId, panelId, 0);
            }
          });
        });
      }, 4500),
    // 2) Turn off all remaining colors and start next game
      new Frame(() => {
        this._turnOffAllGameStrips();
        this.data.set('complete', true);
        setTimeout(() => this.sculptureActionCreator.sendStartNextGame(), 3000);
      }, 5000),
    ];

    const transitionAnimation = new PanelAnimation(transitionFrames);
    this.store.playAnimation(transitionAnimation);
  }

  /**
   * Advance game. Should only be called by master
   */
  _advanceGame() {
    let panelCount = this.data.get("panelCount") + 1;
    this.data.set('panelCount', panelCount);
    if (panelCount === this.gameConfig.GAME_END) {
      this._winGame();
    }
    else {
      // Determine whether to add, remove of keep # of simultaneous panels
      const addPanels = 1 + (this.gameConfig.NUM_ACTIVE_PANELS[panelCount] ? this.gameConfig.NUM_ACTIVE_PANELS[panelCount] : 0);

      for (let i=0; i<addPanels; i++) {
        // Wait before next panel
        this._registerMoveDelay(this.gameConfig.PANEL_SUCCESS_DELAY);
      }
    }
  }

  /*!
   * Remote action
   * If we're master, we're responsible for correctly advancing the game
   */
  _actionMergeState(payload) {
    if (!payload.mole) return; // Only handle mole state

    const moleData = this.data;
    const moleChanges = payload.mole;
    const moleProps = payload.metadata.props.mole;

    // Master owns the panelCount field
    if (!this.store.isMaster()) {
      if (moleChanges.hasOwnProperty('panelCount')) {
        moleData.set('panelCount', moleChanges.panelCount, moleProps.panelCount);
      }
    }

    // Iterate over changed panel states
    if (moleChanges.hasOwnProperty('panels')) {
      const panels = moleData.get('panels');
      const changedPanels = moleChanges.panels;
      for (let panelKey of Object.keys(changedPanels)) {
        const newstate = changedPanels[panelKey];
        const oldstate = panels.getPanelStateByKey(panelKey);

        // Slaves just merge
        if (!this.store.isMaster()) {
          panels.setPanelStateByKey(panelKey, newstate, moleProps.panels[panelKey]);
        }
        else {
          // Master will only accept state modification to STATE_IGNORED
          if (newstate !== oldstate && newstate === TrackedPanels.STATE_IGNORED) {
            panels.setPanelStateByKey(panelKey, newstate);

            // oldstate was ON
            // Normal situation (no race condition): oldstate === ON
            if (this._hasTimeout(panelKey)) { // Normal situation (no race condition): oldstate === ON
              console.log('Merge normal (after new panel)');
              if (oldstate !== TrackedPanels.STATE_ON) {
                assert('state must be STATE_ON');
              }

              this._remainingPanels.delete(panelKey);
              this._removeTimeout(panelKey);
              this._advanceGame();
            }
            // oldstate was OFF
            else {
              if (oldstate !== TrackedPanels.STATE_OFF) {
                assert('state must be STATE_OFF');
              }

              // If we don't have a timeout any longer, we have a race condition.
              // We could be in a few states:
              // 1_ PANEL_SUCCESS_TIMER is active
              // Master already hit the panel => Master wins.
              // This is a NOP since master will pass on the correct colors automatically

              // 2) PANEL_MOVE_DELAY timer is active
              // Master just deactivated the panel, waiting to activate the next one
              // => Kill timer and advance the game
              if (oldstate === TrackedPanels.STATE_OFF && this._panels[panelKey].moveDelay) {
                console.log('merged during MOVE_DELAY');
                clearTimeout(this._panels[panelKey].moveDelay);
                delete this._panels[panelKey].moveDelay;
                this._remainingPanels.delete(panelKey);
                this._advanceGame();
              }

              // 3) The next panel was already made active
              // Accept the change. We also need to reset the intensity
              //
              // Note: This can never be the last move, as the last panel would then be
              // reactivated, not OFF.
              else if (oldstate === TrackedPanels.STATE_OFF && !this._panels[panelKey].moveDelay) {
                console.log('merged after new panel');
                this._remainingPanels.delete(panelKey);
                // FIXME: If this is the last panel, we may need to go to _winGame()
                this.data.set('panelCount', this.data.get("panelCount") + 1);
                this._lights.setIntensity(this._panels[panelKey].stripId, this._panels[panelKey].panelId, this.gameConfig.COLORED_PANEL_INTENSITY);
              }
              else {
                console.log('merge: Master wins - slave is late');
              }
            }
            // Make sure changes are merged by all slaves
            this.store.reassertChanges();
          }
        }
      }
    }
  }

  _hash(stripId, panelId) {
    return `${stripId},${panelId}`;
  }

  _getPanelKey({stripId, panelId}) {
    return this._hash(stripId, panelId);
  }

  /**
   * Request the next active panel, as the game progresses
   * Returns {panel, lifetime}
   */
  _nextActivePanel(count) {
    if (count < this.gameConfig.INITIAL_PANELS.length) {
      const panel = this.gameConfig.INITIAL_PANELS[count];
      const panelkey = this._getPanelKey(panel);
      return { panelkey, lifetime: this._getPanelLifetime(count) }; // No timeout
    }
    return { panelkey: this._getRandomPanel(), lifetime: this._getPanelLifetime(count)};
  }

  /**
   * Selects and returns a random panelkey from _remainingPanels
   */
  _getRandomPanel() {
    const idx = Math.floor(Math.random() * this._remainingPanels.size);
    const iter = this._remainingPanels.values();
    let curr = iter.next();
    for (let i=0; i<idx; i++) curr = iter.next();
    return curr.value;
  }

  _getPanelLifetime(count) {
    // find last and next lifetime values for interpolation
    let last, next;
    for (let elem of this.gameConfig.PANEL_LIFETIME) {
      if (!last || elem.count <= count) last = elem;
      next = elem;
      if (elem.count > count) break;
    }

    let min, max;
    if (last === next) {
      min = last.range[0];
      max = last.range[1];
    }
    else {
      min = last.range[0] + (next.range[0] - last.range[0]) * (count - last.count) / (next.count - last.count);
      max = last.range[1] + (next.range[1] - last.range[1]) * (count - last.count) / (next.count - last.count);
    }
    return 1000 * (Math.random() * (max - min) + min);
  }

  /**
   * Called if an active panel times out: We'll move the panel (deavail+pause+avail).
   */
  _panelTimeout(oldPanel) {
    const key = this._getPanelKey(oldPanel);
    delete this._panels[key].timeout;
    this.moleGameActionCreator.sendDeavailPanel(oldPanel);
    this._registerMoveDelay(this.gameConfig.PANEL_MOVE_DELAY, key);
  }

  _requestPanel(oldPanelKey = null) {
    if (oldPanelKey) delete this._panels[oldPanelKey].moveDelay;
    const {panelkey, lifetime} = this._nextActivePanel(this.data.get("panelCount"));
    if (!panelkey) {
      console.error('No panel key!');
    }
    this.moleGameActionCreator.sendAvailPanel(this._panels[panelkey]);
    this._panels[panelkey].timeout = setTimeout(this._panelTimeout.bind(this, this._panels[panelkey]), lifetime);
  }

  _registerMoveDelay(delay, panelKey = null) {
    const tid = setTimeout(this._requestPanel.bind(this, panelKey), delay);
    if (panelKey) this._panels[panelKey].moveDelay = tid;
  }

  /**
   * Check if we have a timeout registered on the given panel
   */
  _hasTimeout(key) {
    return this._panels[key].timeout !== undefined;
  }

  /**
   * Removes a timeout with the given panel key
   */
  _removeTimeout(key) {
    if (this._hasTimeout(key)) {
      clearTimeout(this._panels[key].timeout);
      delete this._panels[key].timeout;
    }
  }

  /**
   * Only called from a timeout
   */
  _availPanel(panel) {
    this._setPanelState(panel, TrackedPanels.STATE_ON);
    this._remainingPanels.delete(this._getPanelKey(panel));
    this._lights.setIntensity(panel.stripId, panel.panelId, this.gameConfig.ACTIVE_PANEL_INTENSITY);
  }

  /**
   * Only called from a timeout
   */
  _deavailPanel(panel) {
    this._remainingPanels.add(this._getPanelKey(panel));
    this._setPanelState(panel, TrackedPanels.STATE_OFF);
    this._lights.setIntensity(panel.stripId, panel.panelId, this.gameConfig.INACTIVE_PANEL_INTENSITY);
  }

  /**
   * Called by the sculpture first hitting an ON panel
   */
  _colorPanel(panel) {
    this._setPanelState(panel, TrackedPanels.STATE_IGNORED);
    this._lights.setIntensity(panel.stripId, panel.panelId, this.gameConfig.COLORED_PANEL_INTENSITY);
    this._lights.setColor(panel.stripId, panel.panelId, this.store.locationColor);
  }

  get _lights() {
    return this.store.data.get('lights');
  }

  _setPanelState({stripId, panelId}, state) {
    this.data.get('panels').setPanelState(stripId, panelId, state);
  }

}
<|endoftext|>


<commit_before>"use strict";

define([
  "backbone",
  "underscore",
  "jquery",
  "handlebars",
  "models/file",
  "models/label",
  "models/session",
  "text!templates/nav-view.html"
], function(Backbone, _, $, Handlebars,
  fileModel, labelModel, session, headerTemplate) {

  session = session.getSession();

  var Header = Backbone.View.extend({

    el: "#main-nav",

    template: Handlebars.compile(headerTemplate),

    events: {
      "click .new-file": "newFile",
      "click .new-label": "newLabel",
      "click #create-label": "createLabel",
      "click .about": "updateAbout",
      "click .logout": "logout"
    },

    newFile: function() {
      var file = new fileModel();
      file.save({}, {
        success: function(model, response, options) {
          var modelId = model.get("id");
          window.open(session.getBaseUrl() + "/#file/" + modelId + "/edit", "_blank");
        }
      });
    },

    updateNav: function(current) {
      $(".navbar-nav li")
        .removeClass("active")
        .filter(function() {
          return $(this).children().attr("class") == current;
        })
        .addClass("active");
    },

    updateAbout: function() {
      this.updateNav("about");
    },

    newLabel: function() {
      $("#new-label-modal").modal("show");
    },

    createLabel: function() {
      // Grab info, validate
      var modalEl = $("#new-label-modal");
      var title = $("#new-label-modal input[type=text]").val();
      var description = $("#new-label-modal textarea").val();
      var newLabel = new labelModel();
      newLabel.save({
        name: title,
        description: description
      }, {
        success: function() {
          modalEl.modal("hide");
        },
        error: function() {
          alert("label not saved");
        }
      });
    },

    logout: function() {
      session.logout({
        success: function() {
          window.location = session.getBaseUrl();
        },
        failure: function() {
          alert("failed to logout");
        }
      });
    },

    render: function(currPage) {
      this.$el.html(this.template({
        baseUrl: session.getBaseUrl(),
        isAuthenticated: session.isAuthenticated(),
        isEditPage: currPage === "edit"
      }));
      return this;
    }
  });

  return Header;
});

<commit_msg>blank is superfluous<commit_after>"use strict";

define([
  "backbone",
  "underscore",
  "jquery",
  "handlebars",
  "models/file",
  "models/label",
  "models/session",
  "text!templates/nav-view.html"
], function(Backbone, _, $, Handlebars,
  fileModel, labelModel, session, headerTemplate) {

  session = session.getSession();

  var Header = Backbone.View.extend({

    el: "#main-nav",

    template: Handlebars.compile(headerTemplate),

    events: {
      "click .new-file": "newFile",
      "click .new-label": "newLabel",
      "click #create-label": "createLabel",
      "click .about": "updateAbout",
      "click .logout": "logout"
    },

    newFile: function() {
      var file = new fileModel();
      file.save({}, {
        success: function(model, response, options) {
          var modelId = model.get("id");
          window.open(session.getBaseUrl() + "/#file/" + modelId + "/edit");
        }
      });
    },

    updateNav: function(current) {
      $(".navbar-nav li")
        .removeClass("active")
        .filter(function() {
          return $(this).children().attr("class") == current;
        })
        .addClass("active");
    },

    updateAbout: function() {
      this.updateNav("about");
    },

    newLabel: function() {
      $("#new-label-modal").modal("show");
    },

    createLabel: function() {
      // Grab info, validate
      var modalEl = $("#new-label-modal");
      var title = $("#new-label-modal input[type=text]").val();
      var description = $("#new-label-modal textarea").val();
      var newLabel = new labelModel();
      newLabel.save({
        name: title,
        description: description
      }, {
        success: function() {
          modalEl.modal("hide");
        },
        error: function() {
          alert("label not saved");
        }
      });
    },

    logout: function() {
      session.logout({
        success: function() {
          window.location = session.getBaseUrl();
        },
        failure: function() {
          alert("failed to logout");
        }
      });
    },

    render: function(currPage) {
      this.$el.html(this.template({
        baseUrl: session.getBaseUrl(),
        isAuthenticated: session.isAuthenticated(),
        isEditPage: currPage === "edit"
      }));
      return this;
    }
  });

  return Header;
});

<|endoftext|>


<commit_before>(function() {
	/**
	 * input-password is an unstyled password field where user can show and hide the input value.
	 *
	 * Example:
	 *
	 *     <input-password visible toggleText="Show/Hide"></input-password>
	 *
	 * @element input-password
	 * @homepage http://jorgecasar.github.io/input-password/
	 * @demo
	 */
	Polymer({
		is: 'input-password',

		behaviors: [
			Polymer.IronFormElementBehavior,
			Polymer.PaperInputBehavior,
			Polymer.IronControlState
		],

		/**
		 * Fired when the value of is shown.
		 * @event showValue
		 */

		/**
		 * Fired when the value of is hidden.
		 * @event hideValue
		 */

		properties: {
			/**
			 * If true, the user can show the password.
			 *
			 * @attribute value
			 * @type boolean
			 * @default false
			 */
			visible: {
				type: Boolean,
				value: false
			},

			/**
			 * The button text to show and hide the input value.
			 *
			 * @attribute value
			 * @type string
			 * @default 'Toggle'
			 */
			toggleText: {
				type: String,
				value: 'Toggle'
			},

			/**
			 * This css class will be included
			 * when the password is visible.
			 *
			 * @attribute value
			 * @type string
			 * @default 'visible'
			 */
			visibleClass: {
				type: String,
				value: 'visible'
			}
		},

		observers: [
			'_visibleChanged(visible)',
			'_toggleTextChanged(toggleText)'
		],

		listeners: {
			'visibilityButton.tap': 'toggle'
		},

		attached: function() {
			this._toggleTextChanged();
			this._visibleChanged();
		},

		/**
		 * Observer for visible property
		 */
		_visibleChanged: function() {
			this[this.visible ? '_showValue' : '_hideValue']();
		},

		/**
		 * Observer for toggle-text property
		 */
		_toggleTextChanged: function() {
			this._showText = this.showText;
			this._hideText = this.hideText;
		},

		/**
		 * Get the text of the button for show action
		 */
		get showText() {
			return this.toggleText.split('/')[0];
		},

		/**
		 * Get the text of the button for hide action
		 */
		get hideText() {
			return this.toggleText.split('/')[1] || this._showText;
		},

		/**
		 * Action to show the input value
		 */
		_showValue: function() {
			if( this.visible ) this.visible = true;
			this.visible = true;
			this.$.input.type = 'text';
			if(this.visibleClass) {
				this.toggleClass(this.visibleClass, this.visible);
			}
			this.$.visibilityButton.innerHTML = this._hideText;
			this.fire('showValue');
		},

		/**
		 * Action to hide the input value
		 */
		_hideValue: function() {
			if( !this.visible ) this.visible = false;
			this.$.input.type = 'password';
			if(this.visibleClass) {
				this.toggleClass(this.visibleClass, this.visible);
			}
			this.$.visibilityButton.innerHTML = this._showText;
			this.fire('hideValue');
		},

		/**
		 * Toggle the visibily of the input value
		 */
		toggle: function() {
			this.visible = !this.visible;
		}
	});
})();
<commit_msg>bug fix showValue and hideValue methods<commit_after>(function() {
	/**
	 * input-password is an unstyled password field where user can show and hide the input value.
	 *
	 * Example:
	 *
	 *     <input-password visible toggleText="Show/Hide"></input-password>
	 *
	 * @element input-password
	 * @homepage http://jorgecasar.github.io/input-password/
	 * @demo
	 */
	Polymer({
		is: 'input-password',

		behaviors: [
			Polymer.IronFormElementBehavior,
			Polymer.PaperInputBehavior,
			Polymer.IronControlState
		],

		/**
		 * Fired when the value of is shown.
		 * @event showValue
		 */

		/**
		 * Fired when the value of is hidden.
		 * @event hideValue
		 */

		properties: {
			/**
			 * If true, the user can show the password.
			 *
			 * @attribute value
			 * @type boolean
			 * @default false
			 */
			visible: {
				type: Boolean,
				value: false
			},

			/**
			 * The button text to show and hide the input value.
			 *
			 * @attribute value
			 * @type string
			 * @default 'Toggle'
			 */
			toggleText: {
				type: String,
				value: 'Toggle'
			},

			/**
			 * This css class will be included
			 * when the password is visible.
			 *
			 * @attribute value
			 * @type string
			 * @default 'visible'
			 */
			visibleClass: {
				type: String,
				value: 'visible'
			}
		},

		observers: [
			'_visibleChanged(visible)',
			'_toggleTextChanged(toggleText)'
		],

		listeners: {
			'visibilityButton.tap': 'toggle'
		},

		attached: function() {
			this._toggleTextChanged();
			this._visibleChanged();
		},

		/**
		 * Observer for visible property
		 */
		_visibleChanged: function() {
			this[this.visible ? '_showValue' : '_hideValue']();
		},

		/**
		 * Observer for toggle-text property
		 */
		_toggleTextChanged: function() {
			this._showText = this.showText;
			this._hideText = this.hideText;
		},

		/**
		 * Get the text of the button for show action
		 */
		get showText() {
			return this.toggleText.split('/')[0];
		},

		/**
		 * Get the text of the button for hide action
		 */
		get hideText() {
			return this.toggleText.split('/')[1] || this._showText;
		},

		/**
		 * Action to show the input value
		 */
		_showValue: function() {
			if( this.visible ) { this.visible = true; }
			this.visible = true;
			this.$.input.type = 'text';
			if(this.visibleClass) {
				this.toggleClass(this.visibleClass, this.visible);
			}
			this.$.visibilityButton.innerHTML = this._hideText;
			this.fire('showValue');
		},

		/**
		 * Action to hide the input value
		 */
		_hideValue: function() {
			if( this.visible ) { this.visible = false; }
			this.$.input.type = 'password';
			if(this.visibleClass) {
				this.toggleClass(this.visibleClass, this.visible);
			}
			this.$.visibilityButton.innerHTML = this._showText;
			this.fire('hideValue');
		},

		/**
		 * Toggle the visibily of the input value
		 */
		toggle: function() {
			this.visible = !this.visible;
		}
	});
})();
<|endoftext|>