<commit_before>/* Copyright (C) 2011 Governo do Estado do Rio Grande do Sul
 *
 *   Author: Lincoln de Sousa <lincoln@gg.rs.gov.br>
 *   Author: Thiago Silva <thiago@metareload.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

$(function() {
    var moderatedBuzz = $("#buzz-moderated")
    var publicBuzz = $("#buzz-public");

    //max length of message a user can send
    var MAX_LENGTH = 300;

    $("a.filter").click(
        function() {
            $(this).toggleClass('off');
            moderatedBuzz.toggle();
            publicBuzz.toggle();

        });

    //initializing status bar
    $("#message-statusbar").status_bar("message-statusbar");

    /** Posts a new notice on the message buzz */
    function postNotice(message) {
        var close_msg = $.status_message({
            tag: 'message-statusbar',
            message: 'Enviando mensagem...aguarde',
            interval: 0
        });

        var params = { aid: $('#aid').val(), message: message };
        $.ajax({
            url: url_for('buzz.post'),
            data: params,
            type:'post',
            error: function(_,text,err) {
                close_msg();
                $.status_message({
                    tag: 'message-statusbar',
                    message: 'Houve um erro ao enviar a mensagem'
                });
            },
            success: function (data) {
                close_msg();
                var parsedData = $.parseJSON(data);
                if (parsedData.status !== 'ok') {
                    // For any reason, the user got loged out, so, as it's
                    // an async call, we have to request login credentials
                    // again. It's not actually usual to happen but better
                    // being safe than sorry.
                    if (parsedData.code === 'NobodyHome') {
                        auth.showLoginForm({
                            success: function (userData) {
                                postNotice(message);
                            }
                        });
                        return;
                    } else {
                        // Feedback the user. something wrong happened.
                        $.status_message({
                            tag: 'message-statusbar',
                            message: parsedData.msg
                        });
                    }
                } else {
                    // delivery ok
                    $.status_message({
                        tag: 'message-statusbar',
                        message: parsedData.msg
                    });
                    clear_message_area();
                }
            }});
    }

    function clear_message_area() {
        $(".current-msg-length").text(MAX_LENGTH);
        $("#message-area").val('');
    }

    $('#internal_chat').submit(function () {
        var $form = $(this);
        var $field = $('textarea', $form);
        var aid = $('#aid').val();
        var data = $.trim($field.val());

        // None received in the form. We cannot process this
        // request. Just feedback the user.
        if (data === '') {
            $field.addClass('error');
            return false;
        }

        // Just making sure that the user will not be confused by any
        // old error report.
        $field.removeClass('error');

        // Not authenticated users must access the login form
        if (!auth.isAuthenticated()) {
            auth.showLoginForm({
                success: function (userData) {
                    postNotice(data);
                }
            });
        } else {
            // Time to send the request to the server
            postNotice(data);
        }
        return false;
    });

    $('a.filter').tooltip({ opacity: 0.7 });

    // Starts a new instance of the buzz stream

    new Buzz(SIO_BASE, {
        new_buzz: function (msg) {
            var $el = $(tmpl("buzzTemplate", msg));
            $('#buzz-public').prepend($el);
        },

        buzz_accepted: function (msg) {
            var $el = $(tmpl("buzzTemplate", msg));
            $('#buzz-moderated').prepend($el);
        },

        buzz_published: function (msg) {
            var $el = $(tmpl("selectedNotice", msg));
            $('#beingAnswered').fadeOut(function () {
                $(this).html('');
                $(this).append($el);
                $(this).fadeIn();
            });
        }
    });

    // Initializing "how it works" stuff
    audience_how_it_works_spinning_gears_setup();

    $('div[rel]').overlay({
        oneInstance: false,
        speed: 'fast',
        fixed: false,
        mask: {
            color: '#111',
            opacity: 0.7
        },

        onLoad: function () {
            if ($(this.getOverlay()).data('firstRun') === undefined) {
                audience_show_how_it_works();
                $(this.getOverlay()).data('firstRun', 1);
            }
        }
    });

  var msg_area = $("textarea[name=message]");
  msg_area[0].onkeydown = msg_area[0].onkeyup = (function() {
    $(".current-msg-length").text(MAX_LENGTH-msg_area.val().length);
    if (msg_area.val().length > MAX_LENGTH) {
      $("#send_comment").enable(false)
      msg_area.addClass("error");
    } else {
      $("#send_comment").enable(true)
      msg_area.removeClass("error");
    }
  })
});

function audience_how_it_works_spinning_gears_setup() {
  var big_gear = $("#how-it-works-big-gear");
  var small_gear = $("#how-it-works-small-gear");
  var how_it_works = $("#how-it-works");

  big_gear.data("angle",0);
  small_gear.data("angle",0);

  var timer = new Timer();
  timer.then(function(timer,accel) {
    var big_current_angle = big_gear.data("angle");
    var small_current_angle = small_gear.data("angle");

    var big_angle = (big_current_angle + (Math.log(accel+2))) % 360;
    var small_angle = (small_current_angle - (Math.log(accel+2))) % 360;

    big_gear.data("angle",big_angle);
    small_gear.data("angle",small_angle);

    big_gear.rotate(big_angle);
    small_gear.rotate(small_angle);

    if (accel == 0) timer.stop();
  });

  var accel = 0;
  var max_accel = 100;
  how_it_works.hover(
    function() {
      timer.stepper(function() {
        if (accel < max_accel) accel++;
        return accel;
      });
      if (!timer.isRunning()) timer.run();
    },
    function() {
      timer.stepper(function() {
        if (accel > 0) accel--;
        return accel;
      });
    });
}

function audience_show_how_it_works() {
  var couple = $(".step-1-couple");
  var mail = $(".step-1-mail");
  var trail = $(".step-1-mail-trail");

  var big_gear = $(".step-2-big-gear");
  var small_gear = $(".step-2-small-gear");

  big_gear.data("angle",0);
  small_gear.data("angle",0);

  var step_2 = $(".step-2");

  var gentleman = $(".mustache-gentleman");
  var step_3_text = $(".step-3 p");
  var dialog = $(".mustache-gentleman-dialog");

  var spinning_gears = null;
  function spin_gears() {
      spinning_gears = setInterval(function() {
      var big_angle = (big_gear.data("angle")+1) % 360;
      var small_angle = (small_gear.data("angle")-1) % 360;

      big_gear.data("angle",big_angle);
      small_gear.data("angle",small_angle);

      big_gear.rotate(big_angle);
      small_gear.rotate(small_angle);
      }, 13);
  }

  // the animation...
  var timer = new Timer();

  timer.then(function(timer, step) {   /* step 1 */
    var opacity = step * 4 / 100.0;
    if (opacity > 1) opacity = 1;
    couple.css("opacity", opacity);
    if (opacity == 1) timer.next();

  }).then(function(timer,step) {
    var left = parseInt(mail.css("left"))+1;
    mail.css("left", left+"px");

    var opacity = parseFloat(mail.css("opacity")) + 0.05;
    if (opacity > 1) opacity = 1;

    mail.css("opacity", opacity);
    trail.css("opacity", opacity/2);

    if (left == 0) timer.next();
  }).then(function(timer) { /* step 2 */
    if (!spinning_gears) spin_gears();

    var opacity = parseFloat(step_2.css("opacity")) + 0.05;
    if (opacity > 1) opacity = 1;
    step_2.css("opacity", opacity);

    if (opacity == 1) timer.next();
  }).then(function(timer) { /* step 3 */
    var opacity = parseFloat(gentleman.css("opacity")) + 0.05;
    if (opacity > 1) opacity = 1;
    gentleman.css("opacity", opacity);
    step_3_text.css("opacity", opacity);
    dialog.css("opacity",opacity);
    if (opacity == 1) timer.next();
  }).then(function(timer) {
    if (!dialog.is(":visible")) {
      dialog.animate({
        width: 'toggle'
      }, 1000, 'swing');
    }
    timer.next();
  });

  timer.run();
}

function show_how_to() {
    $("#saiba-mais-link").hide();
    $("#saiba-mais-text").show();
}
<commit_msg>(audience) Detalhes cosméticos no js da audiência<commit_after>/* -*- Mode: js2; c-basic-offset:4; -*-
 *
 * Copyright (C) 2011 Governo do Estado do Rio Grande do Sul
 *
 *   Author: Lincoln de Sousa <lincoln@gg.rs.gov.br>
 *   Author: Thiago Silva <thiago@metareload.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

$(function() {
    var moderatedBuzz = $("#buzz-moderated");
    var publicBuzz = $("#buzz-public");

    //max length of message a user can send
    var MAX_LENGTH = 300;

    $("a.filter").click(
        function() {
            $(this).toggleClass('off');
            moderatedBuzz.toggle();
            publicBuzz.toggle();

        });

    // initializing status bar
    $("#message-statusbar").status_bar("message-statusbar");

    /** Posts a new notice on the message buzz */
    function postNotice(message) {
        var close_msg = $.status_message({
            tag: 'message-statusbar',
            message: 'Enviando mensagem...aguarde',
            interval: 0
        });

        var params = { aid: $('#aid').val(), message: message };
        $.ajax({
            url: url_for('buzz.post'),
            data: params,
            type:'post',
            error: function(_,text,err) {
                close_msg();
                $.status_message({
                    tag: 'message-statusbar',
                    message: 'Houve um erro ao enviar a mensagem'
                });
            },
            success: function (data) {
                close_msg();
                var parsedData = $.parseJSON(data);
                if (parsedData.status !== 'ok') {
                    // For any reason, the user got loged out, so, as it's
                    // an async call, we have to request login credentials
                    // again. It's not actually usual to happen but better
                    // being safe than sorry.
                    if (parsedData.code === 'NobodyHome') {
                        auth.showLoginForm({
                            success: function (userData) {
                                postNotice(message);
                            }
                        });
                        return;
                    } else {
                        // Feedback the user. something wrong happened.
                        $.status_message({
                            tag: 'message-statusbar',
                            message: parsedData.msg
                        });
                    }
                } else {
                    // delivery ok
                    $.status_message({
                        tag: 'message-statusbar',
                        message: parsedData.msg
                    });
                    clear_message_area();
                }
            }});
    }

    function clear_message_area() {
        $(".current-msg-length").text(MAX_LENGTH);
        $("#message-area").val('');
    }

    $('#internal_chat').submit(function () {
        var $form = $(this);
        var $field = $('textarea', $form);
        var aid = $('#aid').val();
        var data = $.trim($field.val());

        // None received in the form. We cannot process this
        // request. Just feedback the user.
        if (data === '') {
            $field.addClass('error');
            return false;
        }

        // Just making sure that the user will not be confused by any
        // old error report.
        $field.removeClass('error');

        // Not authenticated users must access the login form
        if (!auth.isAuthenticated()) {
            auth.showLoginForm({
                success: function (userData) {
                    postNotice(data);
                }
            });
        } else {
            // Time to send the request to the server
            postNotice(data);
        }
        return false;
    });

    $('a.filter').tooltip({ opacity: 0.7 });

    // Starts a new instance of the buzz stream

    new Buzz(SIO_BASE, {
        new_buzz: function (msg) {
            var $el = $(tmpl("buzzTemplate", msg));
            $('#buzz-public').prepend($el);
        },

        buzz_accepted: function (msg) {
            var $el = $(tmpl("buzzTemplate", msg));
            $('#buzz-moderated').prepend($el);
        },

        buzz_published: function (msg) {
            var $el = $(tmpl("selectedNotice", msg));
            $('#beingAnswered').fadeOut(function () {
                $(this).html('');
                $(this).append($el);
                $(this).fadeIn();
            });
        }
    });

    // Initializing "how it works" stuff
    audience_how_it_works_spinning_gears_setup();

    $('div[rel]').overlay({
        oneInstance: false,
        speed: 'fast',
        fixed: false,
        mask: {
            color: '#111',
            opacity: 0.7
        },

        onLoad: function () {
            if ($(this.getOverlay()).data('firstRun') === undefined) {
                audience_show_how_it_works();
                $(this.getOverlay()).data('firstRun', 1);
            }
        }
    });

  var msg_area = $("textarea[name=message]");
  msg_area[0].onkeydown = msg_area[0].onkeyup = (function() {
    $(".current-msg-length").text(MAX_LENGTH-msg_area.val().length);
    if (msg_area.val().length > MAX_LENGTH) {
      $("#send_comment").enable(false);
      msg_area.addClass("error");
    } else {
      $("#send_comment").enable(true);
      msg_area.removeClass("error");
    }
  });
});

function audience_how_it_works_spinning_gears_setup() {
  var big_gear = $("#how-it-works-big-gear");
  var small_gear = $("#how-it-works-small-gear");
  var how_it_works = $("#how-it-works");

  big_gear.data("angle",0);
  small_gear.data("angle",0);

  var timer = new Timer();
  timer.then(function(timer,accel) {
    var big_current_angle = big_gear.data("angle");
    var small_current_angle = small_gear.data("angle");

    var big_angle = (big_current_angle + (Math.log(accel+2))) % 360;
    var small_angle = (small_current_angle - (Math.log(accel+2))) % 360;

    big_gear.data("angle",big_angle);
    small_gear.data("angle",small_angle);

    big_gear.rotate(big_angle);
    small_gear.rotate(small_angle);

    if (accel == 0) timer.stop();
  });

  var accel = 0;
  var max_accel = 100;
  how_it_works.hover(
    function() {
      timer.stepper(function() {
        if (accel < max_accel) accel++;
        return accel;
      });
      if (!timer.isRunning()) timer.run();
    },
    function() {
      timer.stepper(function() {
        if (accel > 0) accel--;
        return accel;
      });
    });
}

function audience_show_how_it_works() {
  var couple = $(".step-1-couple");
  var mail = $(".step-1-mail");
  var trail = $(".step-1-mail-trail");

  var big_gear = $(".step-2-big-gear");
  var small_gear = $(".step-2-small-gear");

  big_gear.data("angle",0);
  small_gear.data("angle",0);

  var step_2 = $(".step-2");

  var gentleman = $(".mustache-gentleman");
  var step_3_text = $(".step-3 p");
  var dialog = $(".mustache-gentleman-dialog");

  var spinning_gears = null;
  function spin_gears() {
      spinning_gears = setInterval(function() {
      var big_angle = (big_gear.data("angle")+1) % 360;
      var small_angle = (small_gear.data("angle")-1) % 360;

      big_gear.data("angle",big_angle);
      small_gear.data("angle",small_angle);

      big_gear.rotate(big_angle);
      small_gear.rotate(small_angle);
      }, 13);
  }

  // the animation...
  var timer = new Timer();

  timer.then(function(timer, step) {   /* step 1 */
    var opacity = step * 4 / 100.0;
    if (opacity > 1) opacity = 1;
    couple.css("opacity", opacity);
    if (opacity == 1) timer.next();

  }).then(function(timer,step) {
    var left = parseInt(mail.css("left"))+1;
    mail.css("left", left+"px");

    var opacity = parseFloat(mail.css("opacity")) + 0.05;
    if (opacity > 1) opacity = 1;

    mail.css("opacity", opacity);
    trail.css("opacity", opacity/2);

    if (left == 0) timer.next();
  }).then(function(timer) { /* step 2 */
    if (!spinning_gears) spin_gears();

    var opacity = parseFloat(step_2.css("opacity")) + 0.05;
    if (opacity > 1) opacity = 1;
    step_2.css("opacity", opacity);

    if (opacity == 1) timer.next();
  }).then(function(timer) { /* step 3 */
    var opacity = parseFloat(gentleman.css("opacity")) + 0.05;
    if (opacity > 1) opacity = 1;
    gentleman.css("opacity", opacity);
    step_3_text.css("opacity", opacity);
    dialog.css("opacity",opacity);
    if (opacity == 1) timer.next();
  }).then(function(timer) {
    if (!dialog.is(":visible")) {
      dialog.animate({
        width: 'toggle'
      }, 1000, 'swing');
    }
    timer.next();
  });

  timer.run();
}

function show_how_to() {
    $("#saiba-mais-link").hide();
    $("#saiba-mais-text").show();
}
<|endoftext|>


<commit_before>import {Map} from 'immutable';

import LC3 from './core/lc3';
import LC3Program from './core/program';
import * as Constants from './core/constants';

const initialState = Map({
    lc3: new LC3(),
    viewOptions: Map({
        topAddressShown: 0x3000,
        followPC: true,
    }),
});

function setPC(state, newPC) {
    return state.setIn(["lc3", "registers", "pc"], newPC);
}

function setMemory(state, address, value) {
    return state.setIn(["lc3", "memory", address], value);
}

function setRegister(state, name, value) {
    return state.setIn(["lc3", "registers", name], value);
}

function loadProgram(state, program) {
    const data = new LC3Program.fromJS(program);
    return state.update("lc3", lc3 => lc3.loadProgram(data));
}

function scrollTo(state, address) {
    return state.setIn(["viewOptions", "topAddressShown"],
        Math.max(0, Math.min(address, Constants.MEMORY_SIZE - 1)));
}

function scrollToPC(state) {
    const pc = state.get("lc3").getIn(["registers", "pc"]);
    return state.setIn(["viewOptions", "topAddressShown"], pc);
}

function scrollBy(state, delta) {
    return state.updateIn(["viewOptions", "topAddressShown"],
        x => Math.max(0, Math.min(x + delta, Constants.MEMORY_SIZE - 1)));
}

function step(state) {
    return state.update("lc3", lc3 => lc3.step());
}

export default function reducer(state = initialState, action) {

    switch (action.type) {
        case "SET_PC":
            return setPC(state, action.newPC);
        case "SET_MEMORY":
            return setMemory(state, action.address, action.value);
        case "SET_REGISTER":
            return setRegister(state, action.name, action.value);
        case "LOAD_PROGRAM":
            return loadProgram(state, action.program);
        case "SCROLL_TO":
            return scrollTo(state, action.address);
        case "SCROLL_TO_PC":
            return scrollToPC(state);
        case "SCROLL_BY":
            return scrollBy(state, action.delta);
        case "STEP":
            return step(state);
    }

    return state;
}
<commit_msg>Consolidate SCROLL_TO_PC, SCROLL_BY -> SCROLL_TO<commit_after>import {Map} from 'immutable';

import LC3 from './core/lc3';
import LC3Program from './core/program';
import * as Constants from './core/constants';

const initialState = Map({
    lc3: new LC3(),
    viewOptions: Map({
        topAddressShown: 0x3000,
        followPC: true,
    }),
});

function setPC(state, newPC) {
    return state.setIn(["lc3", "registers", "pc"], newPC);
}

function setMemory(state, address, value) {
    return state.setIn(["lc3", "memory", address], value);
}

function setRegister(state, name, value) {
    return state.setIn(["lc3", "registers", name], value);
}

function loadProgram(state, program) {
    const data = new LC3Program.fromJS(program);
    return state.update("lc3", lc3 => lc3.loadProgram(data));
}

function scrollTo(state, address) {
    return state.setIn(["viewOptions", "topAddressShown"],
        Math.max(0, Math.min(address, Constants.MEMORY_SIZE - 1)));
}

function scrollToPC(state) {
    return scrollTo(state, state.get("lc3").registers.pc);
}

function scrollBy(state, delta) {
    const currentAddress = state.getIn(["viewOptions", "topAddressShown"]);
    return scrollTo(state, currentAddress + delta);
}

function step(state) {
    return state.update("lc3", lc3 => lc3.step());
}

export default function reducer(state = initialState, action) {

    switch (action.type) {
        case "SET_PC":
            return setPC(state, action.newPC);
        case "SET_MEMORY":
            return setMemory(state, action.address, action.value);
        case "SET_REGISTER":
            return setRegister(state, action.name, action.value);
        case "LOAD_PROGRAM":
            return loadProgram(state, action.program);
        case "SCROLL_TO":
            return scrollTo(state, action.address);
        case "SCROLL_TO_PC":
            return scrollToPC(state);
        case "SCROLL_BY":
            return scrollBy(state, action.delta);
        case "STEP":
            return step(state);
    }

    return state;
}
<|endoftext|>


<commit_before>import { List } from 'immutable';

import {
  REQUEST_GET_JKF, RECEIVE_GET_JKF, REQUEST_PUT_JKF, RECEIVE_PUT_JKF,
  FAIL_PUT_JKF, CLEAR_MESSAGE, CHANGE_AUTO_SAVE,
  MOVE_PIECE, CHANGE_COMMENTS, CHANGE_REVERSED,
  GOTO_PATH, MOVE_UP_FORK, MOVE_DOWN_FORK, REMOVE_FORK
} from './actions';
import { jkfToKifuTree, kifuTreeToJKF, pathToKeyPath, getStringPath } from "./treeUtils";
import { buildJKFPlayerFromState } from './playerUtils';

const initialJKF = { header: {}, moves: [{}] };
const initialState = {
  jkf: initialJKF,
  kifuTree: jkfToKifuTree(initialJKF),
  reversed: false,
  currentPath: List(),
  message: "",
  autoSaveEnabled: false,
  needSave: false,
};

export default function kifuTree(state = initialState, action) {
  switch (action.type) {
    case REQUEST_GET_JKF: {
      return Object.assign({}, state, { message: "Fetching..." });
    }
    case RECEIVE_GET_JKF: {
      const jkf = action.jkf;
      const tree = jkfToKifuTree(jkf);

      return Object.assign({}, state, initialState, { kifuTree: tree, jkf: jkf, message: "Fetched" });
    }
    case REQUEST_PUT_JKF: {
      return Object.assign({}, state, { message: "Saving...", needSave: false });
    }
    case RECEIVE_PUT_JKF: {
      return Object.assign({}, state, { message: "Saved" });
    }
    case FAIL_PUT_JKF: {
      return Object.assign({}, state, { message: "Failed" });
    }
    case CLEAR_MESSAGE: {
      return Object.assign({}, state, { message: "" });
    }
    case CHANGE_AUTO_SAVE: {
      const enabled = action.enabled;
      return Object.assign({}, state, { autoSaveEnabled: enabled });
    }
    case MOVE_PIECE: {
      const player = buildJKFPlayerFromState(state);
      const { jkf, kifuTree } = state;
      const move = action.move;
      if (!move.to) {
        return state; // drop to mochigoma
      }
      if (move.from && move.from.x === move.to.x && move.from.y === move.to.y) {
        return state; // drop to the same place. do nothing
      }
      move.to = { x: move.to.x, y: move.to.y }; // In some environment, move.to contains dropEffect attribute. Get rid of it.

      const originalJKFString = JSON.stringify(jkf);

      try {
        if (!player.inputMove(move)) {
          move.promote = confirm("成りますか？");
          player.inputMove(move);
        }
      } catch (e) {
        alert(e);
        return state;
      }

      const newJKF = player.kifu;
      const newJKFString = JSON.stringify(newJKF);
      const changed = originalJKFString !== newJKFString;
      //console.log(changed);

      if (changed) {
        const newKifuTree = jkfToKifuTree(newJKF);
        const newPath = findCurrentPath(newKifuTree, player);
        return Object.assign({}, state, { kifuTree: newKifuTree, currentPath: newPath, jkf: newJKF, needSave: true });
      }

      const newPath = findCurrentPath(kifuTree, player);
      return Object.assign({}, state, { currentPath: newPath });
    }
    case GOTO_PATH: {
      return Object.assign({}, state, { currentPath: List(action.path) });
    }
    case CHANGE_COMMENTS: {
      const { kifuTree, currentPath, jkf } = state;
      const value = action.value;

      const currentKeyPath = pathToKeyPath(currentPath);
      const newKifuTree = kifuTree.setIn(currentKeyPath.concat(['comment']), value);
      const newJKF = kifuTreeToJKF(newKifuTree, jkf);

      return Object.assign({}, state, { kifuTree: newKifuTree, jkf: newJKF, needSave: true });
    }
    case CHANGE_REVERSED: {
      const value = action.value;
      return Object.assign({}, state, { reversed: value });
    }
    case MOVE_UP_FORK: {
      const path = action.path;

      return Object.assign({}, state, updateFork(state, path, (children, lastIndex) => {
        if (lastIndex === 0) {
          return children;
        }
        const prevNode = children.get(lastIndex - 1);
        return children.delete(lastIndex - 1).insert(lastIndex, prevNode);
      }));
    }
    case MOVE_DOWN_FORK: {
      const path = action.path;

      return Object.assign({}, state, updateFork(state, path, (children, lastIndex) => {
        if (lastIndex === children.size - 1) {
          return children;
        }
        const targetNode = children.get(lastIndex);
        return children.delete(lastIndex).insert(lastIndex + 1, targetNode);
      }));
    }
    case REMOVE_FORK: {
      const path = action.path;

      return Object.assign({}, state, updateFork(state, path, (children, lastIndex) => {
        return children.delete(lastIndex);
      }));
    }
    default:
      return state;
  }
};

function updateFork(state, path, forkUpdater) {
  const { kifuTree, currentPath, jkf } = state;
  const currentStringPath = getStringPath(kifuTree, currentPath);
  const newKifuTree = updateForkOfKifuTree(kifuTree, path, forkUpdater);
  if (newKifuTree === kifuTree) {
    return {}; // no change
  }
  const newJKF = kifuTreeToJKF(newKifuTree, jkf);
  const newPath = getPath(newKifuTree, currentStringPath);
  const needSave = newKifuTree !== kifuTree;

  return { kifuTree: newKifuTree, currentPath: newPath, jkf: newJKF, needSave: needSave };
}

function updateForkOfKifuTree(kifuTree, path, forkUpdater) {
  const lastIndex = path[path.length - 1];
  const parentKeyPath = pathToKeyPath(path.slice(0, -1));
  const newKifuTree = kifuTree.updateIn(parentKeyPath.concat(['children']), children => {
    return forkUpdater(children, lastIndex);
  });

  return newKifuTree;
}

// Find current path array from tree depending on the state of player
function findCurrentPath(tree, player, stopIfMissing = false) {
  const path = [];
  let currentNode = tree;
  for (let state of player.getReadableKifuState().slice(1, player.tesuu + 1)) {
    const nextNodeIndex = currentNode.children.findIndex(childNode => childNode.readableKifu === state.kifu);
    const nextNode = currentNode.children.get(nextNodeIndex);
    if (!nextNode) {
      if (stopIfMissing) {
        break;
      } else {
        throw new Error("Active node not found");
      }
    }

    path.push(nextNodeIndex);
    currentNode = nextNode;
  }
  return List(path);
}

function getPath(tree, stringPath) {
  const path = [];
  let currentNode = tree;
  for (let kifu of stringPath) {
    const nextNodeIndex = currentNode.children.findIndex(childNode => childNode.readableKifu === kifu);
    if (nextNodeIndex < 0) {
      break;  // stop if node is missing (e.g. node is removed)
    }
    const nextNode = currentNode.children.get(nextNodeIndex);

    path.push(nextNodeIndex);
    currentNode = nextNode;
  }

  return List(path);
}
<commit_msg>Rename getPath to getPathFromStringPath<commit_after>import { List } from 'immutable';

import {
  REQUEST_GET_JKF, RECEIVE_GET_JKF, REQUEST_PUT_JKF, RECEIVE_PUT_JKF,
  FAIL_PUT_JKF, CLEAR_MESSAGE, CHANGE_AUTO_SAVE,
  MOVE_PIECE, CHANGE_COMMENTS, CHANGE_REVERSED,
  GOTO_PATH, MOVE_UP_FORK, MOVE_DOWN_FORK, REMOVE_FORK
} from './actions';
import { jkfToKifuTree, kifuTreeToJKF, pathToKeyPath, getStringPath } from "./treeUtils";
import { buildJKFPlayerFromState } from './playerUtils';

const initialJKF = { header: {}, moves: [{}] };
const initialState = {
  jkf: initialJKF,
  kifuTree: jkfToKifuTree(initialJKF),
  reversed: false,
  currentPath: List(),
  message: "",
  autoSaveEnabled: false,
  needSave: false,
};

export default function kifuTree(state = initialState, action) {
  switch (action.type) {
    case REQUEST_GET_JKF: {
      return Object.assign({}, state, { message: "Fetching..." });
    }
    case RECEIVE_GET_JKF: {
      const jkf = action.jkf;
      const tree = jkfToKifuTree(jkf);

      return Object.assign({}, state, initialState, { kifuTree: tree, jkf: jkf, message: "Fetched" });
    }
    case REQUEST_PUT_JKF: {
      return Object.assign({}, state, { message: "Saving...", needSave: false });
    }
    case RECEIVE_PUT_JKF: {
      return Object.assign({}, state, { message: "Saved" });
    }
    case FAIL_PUT_JKF: {
      return Object.assign({}, state, { message: "Failed" });
    }
    case CLEAR_MESSAGE: {
      return Object.assign({}, state, { message: "" });
    }
    case CHANGE_AUTO_SAVE: {
      const enabled = action.enabled;
      return Object.assign({}, state, { autoSaveEnabled: enabled });
    }
    case MOVE_PIECE: {
      const player = buildJKFPlayerFromState(state);
      const { jkf, kifuTree } = state;
      const move = action.move;
      if (!move.to) {
        return state; // drop to mochigoma
      }
      if (move.from && move.from.x === move.to.x && move.from.y === move.to.y) {
        return state; // drop to the same place. do nothing
      }
      move.to = { x: move.to.x, y: move.to.y }; // In some environment, move.to contains dropEffect attribute. Get rid of it.

      const originalJKFString = JSON.stringify(jkf);

      try {
        if (!player.inputMove(move)) {
          move.promote = confirm("成りますか？");
          player.inputMove(move);
        }
      } catch (e) {
        alert(e);
        return state;
      }

      const newJKF = player.kifu;
      const newJKFString = JSON.stringify(newJKF);
      const changed = originalJKFString !== newJKFString;
      //console.log(changed);

      if (changed) {
        const newKifuTree = jkfToKifuTree(newJKF);
        const newPath = findCurrentPath(newKifuTree, player);
        return Object.assign({}, state, { kifuTree: newKifuTree, currentPath: newPath, jkf: newJKF, needSave: true });
      }

      const newPath = findCurrentPath(kifuTree, player);
      return Object.assign({}, state, { currentPath: newPath });
    }
    case GOTO_PATH: {
      return Object.assign({}, state, { currentPath: List(action.path) });
    }
    case CHANGE_COMMENTS: {
      const { kifuTree, currentPath, jkf } = state;
      const value = action.value;

      const currentKeyPath = pathToKeyPath(currentPath);
      const newKifuTree = kifuTree.setIn(currentKeyPath.concat(['comment']), value);
      const newJKF = kifuTreeToJKF(newKifuTree, jkf);

      return Object.assign({}, state, { kifuTree: newKifuTree, jkf: newJKF, needSave: true });
    }
    case CHANGE_REVERSED: {
      const value = action.value;
      return Object.assign({}, state, { reversed: value });
    }
    case MOVE_UP_FORK: {
      const path = action.path;

      return Object.assign({}, state, updateFork(state, path, (children, lastIndex) => {
        if (lastIndex === 0) {
          return children;
        }
        const prevNode = children.get(lastIndex - 1);
        return children.delete(lastIndex - 1).insert(lastIndex, prevNode);
      }));
    }
    case MOVE_DOWN_FORK: {
      const path = action.path;

      return Object.assign({}, state, updateFork(state, path, (children, lastIndex) => {
        if (lastIndex === children.size - 1) {
          return children;
        }
        const targetNode = children.get(lastIndex);
        return children.delete(lastIndex).insert(lastIndex + 1, targetNode);
      }));
    }
    case REMOVE_FORK: {
      const path = action.path;

      return Object.assign({}, state, updateFork(state, path, (children, lastIndex) => {
        return children.delete(lastIndex);
      }));
    }
    default:
      return state;
  }
};

function updateFork(state, path, forkUpdater) {
  const { kifuTree, currentPath, jkf } = state;
  const currentStringPath = getStringPath(kifuTree, currentPath);
  const newKifuTree = updateForkOfKifuTree(kifuTree, path, forkUpdater);
  if (newKifuTree === kifuTree) {
    return {}; // no change
  }
  const newJKF = kifuTreeToJKF(newKifuTree, jkf);
  const newPath = getPathFromStringPath(newKifuTree, currentStringPath);
  const needSave = newKifuTree !== kifuTree;

  return { kifuTree: newKifuTree, currentPath: newPath, jkf: newJKF, needSave: needSave };
}

function updateForkOfKifuTree(kifuTree, path, forkUpdater) {
  const lastIndex = path[path.length - 1];
  const parentKeyPath = pathToKeyPath(path.slice(0, -1));
  const newKifuTree = kifuTree.updateIn(parentKeyPath.concat(['children']), children => {
    return forkUpdater(children, lastIndex);
  });

  return newKifuTree;
}

// Find current path array from tree depending on the state of player
function findCurrentPath(tree, player, stopIfMissing = false) {
  const path = [];
  let currentNode = tree;
  for (let state of player.getReadableKifuState().slice(1, player.tesuu + 1)) {
    const nextNodeIndex = currentNode.children.findIndex(childNode => childNode.readableKifu === state.kifu);
    const nextNode = currentNode.children.get(nextNodeIndex);
    if (!nextNode) {
      if (stopIfMissing) {
        break;
      } else {
        throw new Error("Active node not found");
      }
    }

    path.push(nextNodeIndex);
    currentNode = nextNode;
  }
  return List(path);
}

function getPathFromStringPath(tree, stringPath) {
  const path = [];
  let currentNode = tree;
  for (let kifu of stringPath) {
    const nextNodeIndex = currentNode.children.findIndex(childNode => childNode.readableKifu === kifu);
    if (nextNodeIndex < 0) {
      break;  // stop if node is missing (e.g. node is removed)
    }
    const nextNode = currentNode.children.get(nextNodeIndex);

    path.push(nextNodeIndex);
    currentNode = nextNode;
  }

  return List(path);
}
<|endoftext|>


<commit_before>import { combineReducers } from 'redux';
import * as actions from './actions';
import moment from 'moment';

import { cleanEventsData } from './models/events';
import { cleanStoriesData } from './models/stories';
import { cleanProjectsData } from './models/projects';

export default {

	/*
	mode (state = 'page', action) {
		switch (action.type) {
			case actions.MODE_CHANGED:
				return action.value;
			default:
				return state;
		}
	},
	*/

	menuOpen (state = false, action) {
		switch (action.type) {
			case actions.MENU_HIDDEN:
				return false;
			case actions.MENU_TOGGLED:
				return !state;
			default:
				return state;
		};
	},

	map (state = {}, action) {
		switch (action.type) {
			case actions.MAP_MOVED:
				return {
					...state,
					...action.value
				};
			default:
				return {
					...state
				};
		}
	},

	mapLayersPicker: combineReducers({
		layers (state = [], action) {
			switch (action.type) {
				case actions.MAP_LAYERS_PICKER_LAYERS_CHANGED:
					return state.map(layer => {
						if (layer.key === action.key) {
							layer.checked = action.value;
						}
						return layer;
					});
				default:
					return state;
			}
		},

		transportation (state = [], action) {
			switch (action.type) {
				case actions.MAP_LAYERS_PICKER_TRANSPORTATION_CHANGED:
					return state.map(layer => {
						if (layer.key === action.key) {
							layer.checked = action.value;
						}
						return layer;
					});
				default:
					return state;
			}
		},

		stories (state = {}, action) {
			switch (action.type) {
				case actions.MAP_LAYERS_PICKER_STORIES_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		events (state = {}, action) {
			switch (action.type) {
				case actions.MAP_LAYERS_PICKER_EVENTS_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		projects (state = {}, action) {
			switch (action.type) {
				case actions.MAP_LAYERS_PICKER_PROJECTS_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		storyCategories (state = [], action) {
			switch (action.type) {
				case actions.MAP_LAYERS_PICKER_STORY_CATEGORIES_CHANGED:
					// if allCategories specified, overwrite with new list
					if (action.allCategories) return action.allCategories.concat();

					// else, just set selection state at specified key
					return state.map(type => {
						if (type.key === action.key) {
							type.checked = action.checked;
						}
						return type;
					});
				default:
					return state;
			}
		},

		eventTypes (state = [], action) {
			switch (action.type) {
				case actions.MAP_LAYERS_PICKER_EVENT_TYPES_CHANGED:
					// if allTypes specified, overwrite with new list
					if (action.allTypes) return action.allTypes.concat();

					// else, just set selection state at specified key
					return state.map(type => {
						if (type.key === action.key) {
							type.checked = action.checked;
						}
						return type;
					});
				default:
					return state;
			}
		}

	}),

	events: combineReducers({

		// current filter
		startDate (state = null, action) {
			switch (action.type) {
				case actions.EVENTS_START_DATE_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// current filter
		endDate (state = null, action) {
			switch (action.type) {
				case actions.EVENTS_END_DATE_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// current filter
		ageRange (state = '', action) {
			switch (action.type) {
				case actions.EVENTS_AGE_RANGE_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// current filter
		cost (state = '', action) {
			switch (action.type) {
				case actions.EVENTS_COST_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// current filter
		type (state = '', action) {
			switch (action.type) {
				case actions.EVENTS_TYPE_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// current filter
		location (state = '', action) {
			switch (action.type) {
				case actions.EVENTS_LOCATION_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// filter options
		ageRangeOptions (state = [], action) {
			switch (action.type) {
				case actions.AGE_RANGES_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// filter options
		costOptions (state = [], action) {
			switch (action.type) {
				case actions.COSTS_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// filter options
		eventTypeOptions (state = [], action) {
			switch (action.type) {
				case actions.EVENT_TYPES_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// filter options
		locationOptions (state = [], action) {
			switch (action.type) {
				case actions.EVENT_LOCATIONS_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// data from CMS
		data (state = { isFetching: false, items: [] }, action) {
			switch (action.type) {
				case actions.EVENTS_DATA_REQUEST:
					return Object.assign({}, state, {
						isFetching: true,
						error: null
					});
				case actions.EVENTS_DATA_RESPONSE:
					return Object.assign({}, state, {
						isFetching: false,
						items: cleanEventsData(action.items),
						error: null
					});
				case actions.EVENTS_DATA_ERROR_RESPONSE:
					return Object.assign({}, state, {
						isFetching: false,
						error: action.error
					});
				default:
					return state;
			}
		}

	}),

	stories: combineReducers({

		// current filter
		startDate (state = null, action) {
			switch (action.type) {
				case actions.STORIES_START_DATE_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// current filter
		endDate (state = null, action) {
			switch (action.type) {
				case actions.STORIES_END_DATE_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// current filter
		category (state = '', action) {
			switch (action.type) {
				case actions.STORY_CATEGORY_CHANGE:
					return action.value;
				default:
					return state;
			}
		},

		// filter options
		categoryOptions (state = [], action) {
			switch (action.type) {
				case actions.STORY_CATEGORIES_CHANGE:
					return action.value;
				default:
					return state;
			}
		},

		// data from CMS
		data (state = { isFetching: false, items: [] }, action) {
			switch (action.type) {
				case actions.STORIES_DATA_REQUEST:
					return Object.assign({}, state, {
						isFetching: true,
						error: null
					});
				case actions.STORIES_DATA_RESPONSE:
					return Object.assign({}, state, {
						isFetching: false,
						items: cleanStoriesData(action.items),
						error: null
					});
				case actions.STORIES_DATA_ERROR_RESPONSE:
					return Object.assign({}, state, {
						isFetching: false,
						error: action.error
					});
				default:
					return state;
			}
		},

		// currently selected item
		selectedStory (state = null, action) {
			switch (action.type) {
				case actions.UPDATE_SELECTED_STORY:
					return action.story;
				default:
					return state;
			}
		}

	}),

	projects: combineReducers({

		data (state = { isFetching: false, items: [] }, action) {
			switch (action.type) {
				case actions.PROJECTS_DATA_REQUEST:
					return Object.assign({}, state, {
						isFetching: true,
						error: null
					});
				case actions.PROJECTS_DATA_RESPONSE:
					return Object.assign({}, state, {
						isFetching: false,
						items: cleanProjectsData(action.items),
						error: null
					});
				case actions.PROJECTS_DATA_ERROR_RESPONSE:
					return Object.assign({}, state, {
						isFetching: false,
						error: action.error
					});
				default:
					return state;
			}
		},

		selectedProject (state = null, action) {
			switch (action.type) {
				case actions.UPDATE_SELECTED_PROJECT:
					return action.project;
				default:
					return state;
			}
		}

	}),

	/**
	 * Zone constants hardcoded in initialState below, used to link FE code, CMS data, and geojson.
	 * Geodata can be retrieved via `getZone*` methods in actions.js.
	 * TODO: shouldn't need the `getZone*` methods, should just merge state from geodata into a single `zones` reducer.
	 */
	zoneConfigs (state = [], action) {

		return state;

	},

	geodata: combineReducers({

		zones (state = { isFetching: false, geojson: {} }, action) {
			switch (action.type) {
				case actions.ZONE_GEODATA_REQUEST:
					return Object.assign({}, state, {
						isFetching: true,
						geojson: null,
						error: null
					});
				case actions.ZONE_GEODATA_RESPONSE:
					return Object.assign({}, state, {
						isFetching: false,
						geojson: action.geojson,
						error: null
					});
				case actions.ZONE_GEODATA_ERROR_RESPONSE:
					return Object.assign({}, state, {
						isFetching: false,
						geojson: null,
						error: action.error
					});
				default:
					return state;
			}
		},

		projects (state = { isFetching: false, geojson: {} }, action) {
			switch (action.type) {
				case actions.PROJECTS_GEODATA_REQUEST:
					return {
						...state,
						isFetching: true,
						geojson: null,
						error: null
					};
				case actions.PROJECTS_GEODATA_RESPONSE:
					return {
						...state,
						isFetching: false,
						geojson: action.geojson,
						error: null
					};
				case actions.PROJECTS_GEODATA_ERROR_RESPONSE:
					return {
						...state,
						isFetching: false,
						geojson: null,
						error: action.error
					};
				default:
					return state;
			}
		}

	})

};

// Default values passed into reducers on store initialization (in `main.jsx`).
// These values will override the defaults specified in each reducer's argument list,
// and can be merged into a set of initial state on store init if desired.
export const initialState = {

	map: {
		zoom: 14,
		bounds: L.latLngBounds(
			L.latLng(37.733, -122.474),
			L.latLng(37.793, -122.346)
		),
		zoomSnap: 0.0,
		zoomControl: false,
		keyboard: false,
		dragging: false,
		touchZoom: false,
		scrollWheelZoom: false,
		doubleClickZoom: false,
		boxZoom: false
	},

	mapLayersPicker: {
		layers: [
			{ key: 'boat_landings', name: 'boat launches / picnic tables / benches', checked: false}
		],
		transportation: [
			{ key: 'walking_biking', name: 'bike network', checked: true },
			{ key: 'green_connector_network', name: 'green connector network', checked: true }
		],
		projects: false
	},

	events: {
		startDate: moment(),
		endDate: moment().add(3, 'months'),
		ageRangeOptions: [],
		costOptions: [],
		locationOptions: [],
		eventTypeOptions: [],
		data: {
			isFetching: false,
			items: [],
			error: null
		}
	},

	stories: {
		startDate: moment('1/1/2016', 'M/D/YYYY'),
		endDate: moment(),
		selectedStory: null,
		categoryOptions: [],
		data: {
			isFetching: false,
			items: [],
			error: null
		}
	},

	zoneConfigs: [
		{
			id: 'mb',
			title: 'Mission Bay / Mission Rock',
			slug: 'mission_bay_mission_rock',
			bgwZoneId: 'Mission Bay/Mission Rock'
		},
		{
			id: 'p70',
			title: 'Pier 70 / Central Waterfront',
			slug: 'pier_70',
			bgwZoneId: 'Pier 70/Central Waterfront'
		},
		{
			id: 'ib',
			title: 'India Basin',
			slug: 'india_basin',
			bgwZoneId: 'India Basin'
		},
		{
			id: 'sc',
			title: 'Shipyard Candlestick',
			slug: 'shipyard_candlestick',
			bgwZoneId: 'Hunters Point Naval Shipyard/Candlestick'
		}
	],

	projects: {
		data: {
			isFetching: false,
			items: [],
			error: null
		}
	},

	geodata: {
		zones: {
			isFetching: false,
			geojson: {},
			error: null
		},

		projects: {
			isFetching: false,
			geojson: {},
			error: null
		}
	},

	menuOpen: false

};
<commit_msg>Correct name for Pier 70/Central Waterfront zone in zoneConfigs.<commit_after>import { combineReducers } from 'redux';
import * as actions from './actions';
import moment from 'moment';

import { cleanEventsData } from './models/events';
import { cleanStoriesData } from './models/stories';
import { cleanProjectsData } from './models/projects';

export default {

	/*
	mode (state = 'page', action) {
		switch (action.type) {
			case actions.MODE_CHANGED:
				return action.value;
			default:
				return state;
		}
	},
	*/

	menuOpen (state = false, action) {
		switch (action.type) {
			case actions.MENU_HIDDEN:
				return false;
			case actions.MENU_TOGGLED:
				return !state;
			default:
				return state;
		};
	},

	map (state = {}, action) {
		switch (action.type) {
			case actions.MAP_MOVED:
				return {
					...state,
					...action.value
				};
			default:
				return {
					...state
				};
		}
	},

	mapLayersPicker: combineReducers({
		layers (state = [], action) {
			switch (action.type) {
				case actions.MAP_LAYERS_PICKER_LAYERS_CHANGED:
					return state.map(layer => {
						if (layer.key === action.key) {
							layer.checked = action.value;
						}
						return layer;
					});
				default:
					return state;
			}
		},

		transportation (state = [], action) {
			switch (action.type) {
				case actions.MAP_LAYERS_PICKER_TRANSPORTATION_CHANGED:
					return state.map(layer => {
						if (layer.key === action.key) {
							layer.checked = action.value;
						}
						return layer;
					});
				default:
					return state;
			}
		},

		stories (state = {}, action) {
			switch (action.type) {
				case actions.MAP_LAYERS_PICKER_STORIES_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		events (state = {}, action) {
			switch (action.type) {
				case actions.MAP_LAYERS_PICKER_EVENTS_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		projects (state = {}, action) {
			switch (action.type) {
				case actions.MAP_LAYERS_PICKER_PROJECTS_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		storyCategories (state = [], action) {
			switch (action.type) {
				case actions.MAP_LAYERS_PICKER_STORY_CATEGORIES_CHANGED:
					// if allCategories specified, overwrite with new list
					if (action.allCategories) return action.allCategories.concat();

					// else, just set selection state at specified key
					return state.map(type => {
						if (type.key === action.key) {
							type.checked = action.checked;
						}
						return type;
					});
				default:
					return state;
			}
		},

		eventTypes (state = [], action) {
			switch (action.type) {
				case actions.MAP_LAYERS_PICKER_EVENT_TYPES_CHANGED:
					// if allTypes specified, overwrite with new list
					if (action.allTypes) return action.allTypes.concat();

					// else, just set selection state at specified key
					return state.map(type => {
						if (type.key === action.key) {
							type.checked = action.checked;
						}
						return type;
					});
				default:
					return state;
			}
		}

	}),

	events: combineReducers({

		// current filter
		startDate (state = null, action) {
			switch (action.type) {
				case actions.EVENTS_START_DATE_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// current filter
		endDate (state = null, action) {
			switch (action.type) {
				case actions.EVENTS_END_DATE_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// current filter
		ageRange (state = '', action) {
			switch (action.type) {
				case actions.EVENTS_AGE_RANGE_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// current filter
		cost (state = '', action) {
			switch (action.type) {
				case actions.EVENTS_COST_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// current filter
		type (state = '', action) {
			switch (action.type) {
				case actions.EVENTS_TYPE_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// current filter
		location (state = '', action) {
			switch (action.type) {
				case actions.EVENTS_LOCATION_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// filter options
		ageRangeOptions (state = [], action) {
			switch (action.type) {
				case actions.AGE_RANGES_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// filter options
		costOptions (state = [], action) {
			switch (action.type) {
				case actions.COSTS_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// filter options
		eventTypeOptions (state = [], action) {
			switch (action.type) {
				case actions.EVENT_TYPES_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// filter options
		locationOptions (state = [], action) {
			switch (action.type) {
				case actions.EVENT_LOCATIONS_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// data from CMS
		data (state = { isFetching: false, items: [] }, action) {
			switch (action.type) {
				case actions.EVENTS_DATA_REQUEST:
					return Object.assign({}, state, {
						isFetching: true,
						error: null
					});
				case actions.EVENTS_DATA_RESPONSE:
					return Object.assign({}, state, {
						isFetching: false,
						items: cleanEventsData(action.items),
						error: null
					});
				case actions.EVENTS_DATA_ERROR_RESPONSE:
					return Object.assign({}, state, {
						isFetching: false,
						error: action.error
					});
				default:
					return state;
			}
		}

	}),

	stories: combineReducers({

		// current filter
		startDate (state = null, action) {
			switch (action.type) {
				case actions.STORIES_START_DATE_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// current filter
		endDate (state = null, action) {
			switch (action.type) {
				case actions.STORIES_END_DATE_CHANGED:
					return action.value;
				default:
					return state;
			}
		},

		// current filter
		category (state = '', action) {
			switch (action.type) {
				case actions.STORY_CATEGORY_CHANGE:
					return action.value;
				default:
					return state;
			}
		},

		// filter options
		categoryOptions (state = [], action) {
			switch (action.type) {
				case actions.STORY_CATEGORIES_CHANGE:
					return action.value;
				default:
					return state;
			}
		},

		// data from CMS
		data (state = { isFetching: false, items: [] }, action) {
			switch (action.type) {
				case actions.STORIES_DATA_REQUEST:
					return Object.assign({}, state, {
						isFetching: true,
						error: null
					});
				case actions.STORIES_DATA_RESPONSE:
					return Object.assign({}, state, {
						isFetching: false,
						items: cleanStoriesData(action.items),
						error: null
					});
				case actions.STORIES_DATA_ERROR_RESPONSE:
					return Object.assign({}, state, {
						isFetching: false,
						error: action.error
					});
				default:
					return state;
			}
		},

		// currently selected item
		selectedStory (state = null, action) {
			switch (action.type) {
				case actions.UPDATE_SELECTED_STORY:
					return action.story;
				default:
					return state;
			}
		}

	}),

	projects: combineReducers({

		data (state = { isFetching: false, items: [] }, action) {
			switch (action.type) {
				case actions.PROJECTS_DATA_REQUEST:
					return Object.assign({}, state, {
						isFetching: true,
						error: null
					});
				case actions.PROJECTS_DATA_RESPONSE:
					return Object.assign({}, state, {
						isFetching: false,
						items: cleanProjectsData(action.items),
						error: null
					});
				case actions.PROJECTS_DATA_ERROR_RESPONSE:
					return Object.assign({}, state, {
						isFetching: false,
						error: action.error
					});
				default:
					return state;
			}
		},

		selectedProject (state = null, action) {
			switch (action.type) {
				case actions.UPDATE_SELECTED_PROJECT:
					return action.project;
				default:
					return state;
			}
		}

	}),

	/**
	 * Zone constants hardcoded in initialState below, used to link FE code, CMS data, and geojson.
	 * Geodata can be retrieved via `getZone*` methods in actions.js.
	 * TODO: shouldn't need the `getZone*` methods, should just merge state from geodata into a single `zones` reducer.
	 */
	zoneConfigs (state = [], action) {

		return state;

	},

	geodata: combineReducers({

		zones (state = { isFetching: false, geojson: {} }, action) {
			switch (action.type) {
				case actions.ZONE_GEODATA_REQUEST:
					return Object.assign({}, state, {
						isFetching: true,
						geojson: null,
						error: null
					});
				case actions.ZONE_GEODATA_RESPONSE:
					return Object.assign({}, state, {
						isFetching: false,
						geojson: action.geojson,
						error: null
					});
				case actions.ZONE_GEODATA_ERROR_RESPONSE:
					return Object.assign({}, state, {
						isFetching: false,
						geojson: null,
						error: action.error
					});
				default:
					return state;
			}
		},

		projects (state = { isFetching: false, geojson: {} }, action) {
			switch (action.type) {
				case actions.PROJECTS_GEODATA_REQUEST:
					return {
						...state,
						isFetching: true,
						geojson: null,
						error: null
					};
				case actions.PROJECTS_GEODATA_RESPONSE:
					return {
						...state,
						isFetching: false,
						geojson: action.geojson,
						error: null
					};
				case actions.PROJECTS_GEODATA_ERROR_RESPONSE:
					return {
						...state,
						isFetching: false,
						geojson: null,
						error: action.error
					};
				default:
					return state;
			}
		}

	})

};

// Default values passed into reducers on store initialization (in `main.jsx`).
// These values will override the defaults specified in each reducer's argument list,
// and can be merged into a set of initial state on store init if desired.
export const initialState = {

	map: {
		zoom: 14,
		bounds: L.latLngBounds(
			L.latLng(37.733, -122.474),
			L.latLng(37.793, -122.346)
		),
		zoomSnap: 0.0,
		zoomControl: false,
		keyboard: false,
		dragging: false,
		touchZoom: false,
		scrollWheelZoom: false,
		doubleClickZoom: false,
		boxZoom: false
	},

	mapLayersPicker: {
		layers: [
			{ key: 'boat_landings', name: 'boat launches / picnic tables / benches', checked: false}
		],
		transportation: [
			{ key: 'walking_biking', name: 'bike network', checked: true },
			{ key: 'green_connector_network', name: 'green connector network', checked: true }
		],
		projects: false
	},

	events: {
		startDate: moment(),
		endDate: moment().add(3, 'months'),
		ageRangeOptions: [],
		costOptions: [],
		locationOptions: [],
		eventTypeOptions: [],
		data: {
			isFetching: false,
			items: [],
			error: null
		}
	},

	stories: {
		startDate: moment('1/1/2016', 'M/D/YYYY'),
		endDate: moment(),
		selectedStory: null,
		categoryOptions: [],
		data: {
			isFetching: false,
			items: [],
			error: null
		}
	},

	zoneConfigs: [
		{
			id: 'mb',
			title: 'Mission Bay / Mission Rock',
			slug: 'mission_bay_mission_rock',
			bgwZoneId: 'Mission Bay/Mission Rock'
		},
		{
			id: 'p70',
			title: 'Pier 70/Central Waterfront',
			slug: 'pier_70',
			bgwZoneId: 'Pier 70/Central Waterfront'
		},
		{
			id: 'ib',
			title: 'India Basin',
			slug: 'india_basin',
			bgwZoneId: 'India Basin'
		},
		{
			id: 'sc',
			title: 'Shipyard Candlestick',
			slug: 'shipyard_candlestick',
			bgwZoneId: 'Hunters Point Naval Shipyard/Candlestick'
		}
	],

	projects: {
		data: {
			isFetching: false,
			items: [],
			error: null
		}
	},

	geodata: {
		zones: {
			isFetching: false,
			geojson: {},
			error: null
		},

		projects: {
			isFetching: false,
			geojson: {},
			error: null
		}
	},

	menuOpen: false

};
<|endoftext|>


<commit_before>window.Cow = window.Cow || {};

Cow.websocket = function(config){
    this._core = config.core;
    //socket connection object
    this._url = config.url;
    this._connection = this.connect();
};

    /**
        disconnect() - disconnect us from websocket server
    **/
Cow.websocket.prototype.disconnect = function() {
    if (this._connection){
        this._connection.close();
        this._connection = null;
    }
    else {
        throw('No websocket active');
    }
};
    /**
        connect(url) - connect to websocket server on url, returns connection
    **/
Cow.websocket.prototype.connect = function() {
    var self = this;
    var core = this.core;
    if (!this._connection || this._connection.readyState != 1) //if no connection
    {
        //if(url.indexOf('ws') === 0) {
            // var connection = new WebSocket(this._url, 'connect');
            // connection.onopen=this._onOpen;
            // connection.onmessage = this._onMessage;
            // connection.onclose = this._onClose;
            // connection.onerror = this._onError;
            // connection.obj = this;
        //}
        //else {throw('Incorrect URL: ' + url);}

        var hub = $.connection.cowHub;
        this._hub = hub;
        $.connection.hub.url = 'http://wingis/cow/signalr';
        $.connection.hub.start().done(function () {
            var payload = {
                "peerID" : $.connection.hub.id
            };
            self._onConnect(payload);
            //$("#chatWindow").val("Connected\n");
            //$("#chatWindow").val($("#chatWindow").val() + "my connection id: " + $.connection.hub.id + "\n");
            //$("#sendButton").click(function () {
            //    hub.server.send("bert", $("#messageTextBox").val());
            //    $("#messageTextBox").val("");
            //});
        });
        hub.client.userDisconnected = function (connectionId) {
            var payload = {
                "gonePeerID" : connectionId
            };
            self._onPeerGone(payload);
        };
        hub.client.broadcastMessage = function(message) {
            console.log(message);
            self._onMessage(message);
        };
    }
    else {
        //connection = this._connection;
        hub = this._hub;
    }
    //return connection;
    return hub;
};
    /**
        connection() - returns connection object
    **/
Cow.websocket.prototype.connection = function(){
    return this._connection;
};
    /**
        sendData(data, action, target) - send data to websocket server with params:
            data - json object
            action - string that describes the context of the message
            target - (optional) id of the target peer
    **/
Cow.websocket.prototype.sendData = function(data, action, target){
    //TODO: check if data is an object
    var message = {};
    message.sender = this._core.peerid();
    message.target = target;
    message.action = action;
    message.payload = data;
    var stringified;
    try {
        stringified = JSON.stringify(message);
    }
    catch (e){
        console.error(e, message);
    }
    if (1 == 1 || (this._connection && this._connection.readyState == 1)){
        //console.log('Sending ',message);
        //this._connection.send(JSON.stringify(message));
        var string = JSON.stringify(message);
        this._hub.server.send(string);
    }
    else{
        console.warn('Could not send, socket not connected?');
    }
};
Cow.websocket.prototype._onMessage = function(message){
    //var core = this.obj._core;
    var core = this._core;
    this.obj = this;
    var data = JSON.parse(message); //TODO: catch parse errors
    var sender = data.sender;
    var PEERID = core.peerid();
    var action = data.action;
    var payload = data.payload;
    var target = data.target;
    if (sender != PEERID){
        console.log('Receiving ',data);
    }
    switch (action) {
    /**
        Commands
    **/
        case 'command':
            if (sender != PEERID){
                this.obj._onCommand(data);
            }
        break;
    /**
        Messages related to the websocket connection
    **/
        //websocket confirms connection by returning the unique peerID (targeted)
        case 'connected':
            this.obj._onConnect(payload);
        break;

        //websocket tells everybody a peer has gone, with ID: peerID
        case 'peerGone':
            this.obj._onPeerGone(payload);
        break;

    /**
        Messages related to the syncing protocol
    **/
        //a new peer has arrived and gives a list of its records
        case 'newList':
            if(sender != PEERID) {
                this.obj._onNewList(payload,sender);
            }
        break;

        //you just joined and you receive a list of records the others want (targeted)
        case 'wantedList':
            if(target == PEERID) {
                this.obj._onWantedList(payload);
            }
        break;

        //you just joined and receive the records you are missing (targeted)
        case 'missingRecords':
            if(target == PEERID) {
                this.obj._onMissingRecords(payload);
            }
        break;

        //a new peer has arrived and sends everybody the records that are requested in the *wantedList*
        case 'requestedRecords':
            if(sender != PEERID) {
                this.obj._onMissingRecords(payload);
                //OBS: this.obj._onRequestedRecords(payload);
            }
        break;
    /**
        Messages related to real-time changes in records
    **/
        //a peer sends a new or updated record
        case 'updatedRecord':
            if(sender != PEERID) {
                this.obj._onUpdatedRecords(payload);
            }
        break;

    }

};
Cow.websocket.prototype._onClose = function(event){
    var code = event.code;
    var reason = event.reason;
    var wasClean = event.wasClean;
    var self = this;
    //this.close(); //FIME: TT: why was this needed?
    this.obj._core.peerStore().clear();
    //TODO this.obj._core.trigger('ws-disconnected');
    var restart = function(){
        try{
            self.obj._core.websocket().disconnect();
        }
        catch(err){
            console.warn(err);
        }
        var url = self.obj._url;
        self.obj._connection = self.obj._core.websocket().connect(url);
    };
    setTimeout(restart,5000);
};
Cow.websocket.prototype._onConnect = function(payload){
    var self = this;
    this._core.peerid(payload.peerID);
    var mypeer = this._core.peers({_id: payload.peerID});
    //add userid to peer object
    if (this._core.user()){
        mypeer.data('userid',this._core.user()._id);
    }
    mypeer.sync();
    this.trigger('connected',payload);

    //initiate peer sync
    this._core.peerStore().sync();

    //initiate user sync
    this._core.userStore().sync();

    //initiate project sync
    var projectstore = this._core.projectStore();
    projectstore.sync();

    //wait for projectstore to load
    projectstore.loaded.then(function(d){
        var projects = self._core.projects();
        for (var i=0;i<projects.length;i++){
            var project = projects[i];
            self._core.projects(project._id).itemStore().sync();
            self._core.projects(project._id).groupStore().sync();
        }
    });
};

    //A peer has disconnected, remove it from your peerList
Cow.websocket.prototype._onPeerGone = function(payload) {
    var peerGone = payload.gonePeerID.toString();
    if (this._core.peers(peerGone)){
        this._core.peers(peerGone).deleted(true).sync();
    }
    //this._core.peerStore().removePeer(peerGone);
    //TODO this.core.trigger('ws-peerGone',payload);
};
Cow.websocket.prototype._onError = function(e){
    console.warn('error in websocket connection: ' + e.type);
};
Cow.websocket.prototype._getStore = function(payload){
    var storetype = payload.syncType;
    var projectid = payload.project;
    var project;
    switch (storetype) {
        case 'peers':
            return this._core.peerStore();
        case 'projects':
            return this._core.projectStore();
        case 'users':
            return this._core.userStore();
        case 'items':
            if (!projectid) {throw('No project id given');}
            if (this._core.projects(projectid)){
                project = this._core.projects(projectid);
            }
            else {
                project = this._core.projects({_id:projectid});
            }
            return project.itemStore();
        case 'groups':
            if (!projectid) {throw('No project id given');}
            if (this._core.projects(projectid)){
                project = this._core.projects(projectid);
            }
            else {
                project = this._core.projects({_id:projectid});
            }
            return project.groupStore();
    }
};

//A peer initiates a sync
Cow.websocket.prototype._onNewList = function(payload,sender) {
    //Only answer if we are the alpha peer
    if (this._amIAlpha()){
        var store = this._getStore(payload);
        var project = store._projectid;
        var syncobject = store.compareRecords({uid:sender, list: payload.list});
        var data;

        data =  {
            "syncType" : payload.syncType,
            "project" : project,
            "list" : syncobject.requestlist
        };
        this.sendData(data, 'wantedList', sender);

        data =  {
            "syncType" : payload.syncType,
            "project" : project,
            "list" : syncobject.pushlist
        };
        this.sendData(data, 'missingRecords', sender);
        //TODO this.core.trigger('ws-newList',message);
    }
};
Cow.websocket.prototype._amIAlpha = function(){ //find out wether I am alpha
    /**
    peers all have a unique id from the server based on the timestamp
    the peer with the oldest timestamp AND member of the alpha familty is alpha
    **/
    var returnval = null;
    //First only get alpha peers
    var alphaPeers = _.sortBy(
        _.filter(this._core.peers(),function(d){
            return (d.data('family') == 'alpha' && !d.deleted());
        }),
     function(d){return d.id();});
    //If we are the oldest of alpha peers
    var oldestpeer = alphaPeers[0];
    var me = this._core.peer();
    if (me.id() == oldestpeer.id()) {//yes, I certainly am (the oldest)
        returnval =  true;
    }
    else {
        returnval = false; //Not the oldest in the project
    }
    return returnval;
};
Cow.websocket.prototype._onWantedList = function(payload) {
    var store = this._getStore(payload);
    var returnlist = store.requestRecords(payload.list);
    var data =  {
        "syncType" : payload.syncType,
        "project" : store._projectid,
        "list" : returnlist
    };
    this.sendData(data, 'requestedRecords');
    //TODO this.core.trigger('ws-wantedList',payload);
};

Cow.websocket.prototype._onMissingRecords = function(payload) {
    var store = this._getStore(payload);
    var list = payload.list;
    var synclist = [];
    var i;
    for (i=0;i<list.length;i++){
        var data = list[i];
        //var record = store._addRecord({source: 'WS', data: data});
        var record = store._addRecord({source: 'WS', data: data});
        //if we receive a new project, we also have to get the items and groups in it
        if (store._type == 'projects'){
            record.groupStore().sync();
            record.itemStore().sync();
        }
        //Do the syncing for the deltas
        if (data.deltas && record.deltas()){
            var localarr = _.pluck(record.deltas(),'timestamp');
            var remotearr = _.pluck(data.deltas,'timestamp');
            var diff = _.difference(localarr, remotearr);
            //TODO: nice solution for future, when dealing more with deltas
            //For now we just respond with a forced sync our own record so the delta's get synced anyway
            if (diff.length > 0){
                synclist.push(record);
            }
        }
    }
    for (i=0;i<synclist.length;i++){
        store.syncRecord(synclist[i]);
    }
    store.trigger('datachange');
};

Cow.websocket.prototype._onUpdatedRecords = function(payload) {
    var store = this._getStore(payload);
    var data = payload.record;
    store._addRecord({source: 'WS', data: data});
    store.trigger('datachange');
};
    // END Syncing messages

    /**
        Command messages:
            commands are ways to control peer behaviour.
            Commands can be targeted or non-targeted. Some commands are handled here (all purpose) but all commands
            will send a trigger with the command including the message data.
    **/
Cow.websocket.prototype._onCommand = function(data) {
    var core = this._core;
    var payload = data.payload;
    var command = payload.command;
    var targetuser = payload.targetuser;
    var params = payload.params;
    this.trigger('command',data);
    //TODO: move to icm
    if (command == 'zoomTo'){
        if (targetuser && targetuser == core.user().id()){
            this.trigger(command, payload.location);
        }
    }
    //Closes a (misbehaving or stale) peer
    if (command == 'kickPeer'){
        if (targetuser && targetuser == core.peerid()){
            //TODO: make this more gentle, possibly with a trigger
            window.open('', '_self', '');
            window.close();
        }
    }
    //Remove all data from a peer
    if (command == 'purgePeer'){
        if (targetuser && targetuser == this._core.peerid()){
            _.each(core.projects(), function(d){
                d.itemStore().clear();
                d.groupStore().clear();
            });
            core.projectStore().clear();
            core.userStore().clear();
        }
    }
    //Close project and flush the items and groups in the project (use with utter caution!)
    if (command == 'flushProject'){
        var projectid = payload.projectid;
        var project;
        if (core.projects(projectid)){
            project = core.projects(projectid);
            project.itemStore().clear(); //remove objects from store
            project.itemStore()._db.main.clear(); //remove records from db
        }
    }
    //Answer a ping with a pong
    if (command == 'ping'){
        var target = data.sender;
        this.sendData({command: 'pong'},'command',target);
    }
};

//Adding some Backbone event binding functionality to the store
_.extend(Cow.websocket.prototype, Events);
<commit_msg>Preparing for HHNK (changed to signalR)<commit_after>window.Cow = window.Cow || {};

Cow.websocket = function(config){
    this._core = config.core;
    //socket connection object
    this._url = config.url;
    this._connection = this.connect();
};

    /**
        disconnect() - disconnect us from websocket server
    **/
Cow.websocket.prototype.disconnect = function() {
    if (this._connection){
        this._connection.close();
        this._connection = null;
    }
    else {
        throw('No websocket active');
    }
};
    /**
        connect(url) - connect to websocket server on url, returns connection
    **/
Cow.websocket.prototype.connect = function() {
    var self = this;
    var core = this.core;
    if (!this._connection || this._connection.readyState != 1) //if no connection
    {
        //if(url.indexOf('ws') === 0) {
            // var connection = new WebSocket(this._url, 'connect');
            // connection.onopen=this._onOpen;
            // connection.onmessage = this._onMessage;
            // connection.onclose = this._onClose;
            // connection.onerror = this._onError;
            // connection.obj = this;
        //}
        //else {throw('Incorrect URL: ' + url);}

        var hub = $.connection.cowHub;
        this._hub = hub;
        //$.connection.hub.url = 'http://wingis/cow/signalr';
        $.connection.hub.url = this._url;
        $.connection.hub.start()
            .done(function () {
                var payload = {
                    "peerID" : $.connection.hub.id
                };
                self._onConnect(payload);
                //$("#chatWindow").val("Connected\n");
                //$("#chatWindow").val($("#chatWindow").val() + "my connection id: " + $.connection.hub.id + "\n");
                //$("#sendButton").click(function () {
                //    hub.server.send("bert", $("#messageTextBox").val());
                //    $("#messageTextBox").val("");
                //});
            })
            .fail(function(e,d){
                console.error('Fail: ',e);
            });
        hub.client.userDisconnected = function (connectionId) {
            var payload = {
                "gonePeerID" : connectionId
            };
            self._onPeerGone(payload);
        };
        hub.client.broadcastMessage = function(message) {
            console.log(message);
            self._onMessage(message);
        };
    }
    else {
        //connection = this._connection;
        hub = this._hub;
    }
    //return connection;
    return hub;
};
    /**
        connection() - returns connection object
    **/
Cow.websocket.prototype.connection = function(){
    return this._connection;
};
    /**
        sendData(data, action, target) - send data to websocket server with params:
            data - json object
            action - string that describes the context of the message
            target - (optional) id of the target peer
    **/
Cow.websocket.prototype.sendData = function(data, action, target){
    //TODO: check if data is an object
    var message = {};
    message.sender = this._core.peerid();
    message.target = target;
    message.action = action;
    message.payload = data;
    var stringified;
    try {
        stringified = JSON.stringify(message);
    }
    catch (e){
        console.error(e, message);
    }
    if (1 == 1 || (this._connection && this._connection.readyState == 1)){
        //console.log('Sending ',message);
        //this._connection.send(JSON.stringify(message));
        var string = JSON.stringify(message);
        this._hub.server.send(string);
    }
    else{
        console.warn('Could not send, socket not connected?');
    }
};
Cow.websocket.prototype._onMessage = function(message){
    //var core = this.obj._core;
    var core = this._core;
    this.obj = this;
    var data = JSON.parse(message); //TODO: catch parse errors
    var sender = data.sender;
    var PEERID = core.peerid();
    var action = data.action;
    var payload = data.payload;
    var target = data.target;
    if (sender != PEERID){
        console.log('Receiving ',data);
    }
    switch (action) {
    /**
        Commands
    **/
        case 'command':
            if (sender != PEERID){
                this.obj._onCommand(data);
            }
        break;
    /**
        Messages related to the websocket connection
    **/
        //websocket confirms connection by returning the unique peerID (targeted)
        case 'connected':
            this.obj._onConnect(payload);
        break;

        //websocket tells everybody a peer has gone, with ID: peerID
        case 'peerGone':
            this.obj._onPeerGone(payload);
        break;

    /**
        Messages related to the syncing protocol
    **/
        //a new peer has arrived and gives a list of its records
        case 'newList':
            if(sender != PEERID) {
                this.obj._onNewList(payload,sender);
            }
        break;

        //you just joined and you receive a list of records the others want (targeted)
        case 'wantedList':
            if(target == PEERID) {
                this.obj._onWantedList(payload);
            }
        break;

        //you just joined and receive the records you are missing (targeted)
        case 'missingRecords':
            if(target == PEERID) {
                this.obj._onMissingRecords(payload);
            }
        break;

        //a new peer has arrived and sends everybody the records that are requested in the *wantedList*
        case 'requestedRecords':
            if(sender != PEERID) {
                this.obj._onMissingRecords(payload);
                //OBS: this.obj._onRequestedRecords(payload);
            }
        break;
    /**
        Messages related to real-time changes in records
    **/
        //a peer sends a new or updated record
        case 'updatedRecord':
            if(sender != PEERID) {
                this.obj._onUpdatedRecords(payload);
            }
        break;

    }

};
Cow.websocket.prototype._onClose = function(event){
    var code = event.code;
    var reason = event.reason;
    var wasClean = event.wasClean;
    var self = this;
    //this.close(); //FIME: TT: why was this needed?
    this.obj._core.peerStore().clear();
    //TODO this.obj._core.trigger('ws-disconnected');
    var restart = function(){
        try{
            self.obj._core.websocket().disconnect();
        }
        catch(err){
            console.warn(err);
        }
        var url = self.obj._url;
        self.obj._connection = self.obj._core.websocket().connect(url);
    };
    setTimeout(restart,5000);
};
Cow.websocket.prototype._onConnect = function(payload){
    var self = this;
    this._core.peerid(payload.peerID);
    var mypeer = this._core.peers({_id: payload.peerID});
    //add userid to peer object
    if (this._core.user()){
        mypeer.data('userid',this._core.user()._id);
    }
    mypeer.sync();
    this.trigger('connected',payload);

    //initiate peer sync
    this._core.peerStore().sync();

    //initiate user sync
    this._core.userStore().sync();

    //initiate project sync
    var projectstore = this._core.projectStore();
    projectstore.sync();

    //wait for projectstore to load
    projectstore.loaded.then(function(d){
        var projects = self._core.projects();
        for (var i=0;i<projects.length;i++){
            var project = projects[i];
            self._core.projects(project._id).itemStore().sync();
            self._core.projects(project._id).groupStore().sync();
        }
    });
};

    //A peer has disconnected, remove it from your peerList
Cow.websocket.prototype._onPeerGone = function(payload) {
    var peerGone = payload.gonePeerID.toString();
    if (this._core.peers(peerGone)){
        this._core.peers(peerGone).deleted(true).sync();
    }
    //this._core.peerStore().removePeer(peerGone);
    //TODO this.core.trigger('ws-peerGone',payload);
};
Cow.websocket.prototype._onError = function(e){
    console.warn('error in websocket connection: ' + e.type);
};
Cow.websocket.prototype._getStore = function(payload){
    var storetype = payload.syncType;
    var projectid = payload.project;
    var project;
    switch (storetype) {
        case 'peers':
            return this._core.peerStore();
        case 'projects':
            return this._core.projectStore();
        case 'users':
            return this._core.userStore();
        case 'items':
            if (!projectid) {throw('No project id given');}
            if (this._core.projects(projectid)){
                project = this._core.projects(projectid);
            }
            else {
                project = this._core.projects({_id:projectid});
            }
            return project.itemStore();
        case 'groups':
            if (!projectid) {throw('No project id given');}
            if (this._core.projects(projectid)){
                project = this._core.projects(projectid);
            }
            else {
                project = this._core.projects({_id:projectid});
            }
            return project.groupStore();
    }
};

//A peer initiates a sync
Cow.websocket.prototype._onNewList = function(payload,sender) {
    //Only answer if we are the alpha peer
    if (this._amIAlpha()){
        var store = this._getStore(payload);
        var project = store._projectid;
        var syncobject = store.compareRecords({uid:sender, list: payload.list});
        var data;

        data =  {
            "syncType" : payload.syncType,
            "project" : project,
            "list" : syncobject.requestlist
        };
        this.sendData(data, 'wantedList', sender);

        data =  {
            "syncType" : payload.syncType,
            "project" : project,
            "list" : syncobject.pushlist
        };
        this.sendData(data, 'missingRecords', sender);
        //TODO this.core.trigger('ws-newList',message);
    }
};
Cow.websocket.prototype._amIAlpha = function(){ //find out wether I am alpha
    /**
    peers all have a unique id from the server based on the timestamp
    the peer with the oldest timestamp AND member of the alpha familty is alpha
    **/
    var returnval = null;
    //First only get alpha peers
    var alphaPeers = _.sortBy(
        _.filter(this._core.peers(),function(d){
            return (d.data('family') == 'alpha' && !d.deleted());
        }),
     function(d){return d.id();});
    //If we are the oldest of alpha peers
    var oldestpeer = alphaPeers[0];
    var me = this._core.peer();
    if (me.id() == oldestpeer.id()) {//yes, I certainly am (the oldest)
        returnval =  true;
    }
    else {
        returnval = false; //Not the oldest in the project
    }
    return returnval;
};
Cow.websocket.prototype._onWantedList = function(payload) {
    var store = this._getStore(payload);
    var returnlist = store.requestRecords(payload.list);
    var data =  {
        "syncType" : payload.syncType,
        "project" : store._projectid,
        "list" : returnlist
    };
    this.sendData(data, 'requestedRecords');
    //TODO this.core.trigger('ws-wantedList',payload);
};

Cow.websocket.prototype._onMissingRecords = function(payload) {
    var store = this._getStore(payload);
    var list = payload.list;
    var synclist = [];
    var i;
    for (i=0;i<list.length;i++){
        var data = list[i];
        //var record = store._addRecord({source: 'WS', data: data});
        var record = store._addRecord({source: 'WS', data: data});
        //if we receive a new project, we also have to get the items and groups in it
        if (store._type == 'projects'){
            record.groupStore().sync();
            record.itemStore().sync();
        }
        //Do the syncing for the deltas
        if (data.deltas && record.deltas()){
            var localarr = _.pluck(record.deltas(),'timestamp');
            var remotearr = _.pluck(data.deltas,'timestamp');
            var diff = _.difference(localarr, remotearr);
            //TODO: nice solution for future, when dealing more with deltas
            //For now we just respond with a forced sync our own record so the delta's get synced anyway
            if (diff.length > 0){
                synclist.push(record);
            }
        }
    }
    for (i=0;i<synclist.length;i++){
        store.syncRecord(synclist[i]);
    }
    store.trigger('datachange');
};

Cow.websocket.prototype._onUpdatedRecords = function(payload) {
    var store = this._getStore(payload);
    var data = payload.record;
    store._addRecord({source: 'WS', data: data});
    store.trigger('datachange');
};
    // END Syncing messages

    /**
        Command messages:
            commands are ways to control peer behaviour.
            Commands can be targeted or non-targeted. Some commands are handled here (all purpose) but all commands
            will send a trigger with the command including the message data.
    **/
Cow.websocket.prototype._onCommand = function(data) {
    var core = this._core;
    var payload = data.payload;
    var command = payload.command;
    var targetuser = payload.targetuser;
    var params = payload.params;
    this.trigger('command',data);
    //TODO: move to icm
    if (command == 'zoomTo'){
        if (targetuser && targetuser == core.user().id()){
            this.trigger(command, payload.location);
        }
    }
    //Closes a (misbehaving or stale) peer
    if (command == 'kickPeer'){
        if (targetuser && targetuser == core.peerid()){
            //TODO: make this more gentle, possibly with a trigger
            window.open('', '_self', '');
            window.close();
        }
    }
    //Remove all data from a peer
    if (command == 'purgePeer'){
        if (targetuser && targetuser == this._core.peerid()){
            _.each(core.projects(), function(d){
                d.itemStore().clear();
                d.groupStore().clear();
            });
            core.projectStore().clear();
            core.userStore().clear();
        }
    }
    //Close project and flush the items and groups in the project (use with utter caution!)
    if (command == 'flushProject'){
        var projectid = payload.projectid;
        var project;
        if (core.projects(projectid)){
            project = core.projects(projectid);
            project.itemStore().clear(); //remove objects from store
            project.itemStore()._db.main.clear(); //remove records from db
        }
    }
    //Answer a ping with a pong
    if (command == 'ping'){
        var target = data.sender;
        this.sendData({command: 'pong'},'command',target);
    }
};

//Adding some Backbone event binding functionality to the store
_.extend(Cow.websocket.prototype, Events);
<|endoftext|>


<commit_before>function Timer(){
	// pick random duration after which end the current game
	if (localStorage.getItem('dist') === "normal"){
		this.duration = normrv(minDuration, maxDuration, meanDuration, sdDuration);
	}
	else{
		this.duration = uniform(minDuration, maxDuration);
	}
	this.tick = function(){
		this.time = (Date.now() - this.startTime) / 1000;
		$('#timer').html(this.time.toFixed(2) + " seconds");
		$("#reset").show();
		$("#cont").html("Continue");
		if (this.time > this.duration || !(this.ticking)){
			clearInterval(this.interval);
			phrasesLeft.push(nextPhrase);
			$("#gameMenu").hide();
			$("#startMenu").show();
			this.ticking = false;
			if (this.time > this.duration){
				$("#log").html("You ran out of time.");
			}
		}
	};
	this.stop = function(){
		this.ticking = false;
	};
	this.start = function(){
		this.startTime = Date.now();
		this.ticking = true;
		this.interval = setInterval(this.tick.bind(this), 10);
	};
}

var timer;
var cont = function(){
	$("#startMenu").hide();
	$("#gameMenu").show();
	timer = new Timer();
	console.log(timer);
	timer.start();
	next();
};

var nextPhrase;
var next = function(){
	if (phrasesLeft.length === 0){
		$("#log").html("BOOM! You ran out of catchphrases!");
		$("#cont").hide();
		timer.stop();
	}
	var idx = Math.floor(Math.random() * phrasesLeft.length);
	nextPhrase = phrasesLeft.splice(idx, 1)[0];
	$("#phrase").html(nextPhrase);
};

var skip = function(){
	var skippedPhrase = nextPhrase;
	next();
	phrasesLeft.push(skippedPhrase);
}

var reset = function(){
	var response = confirm("Are you sure you want to reset your progress through the pack?");
	if (response === true){
	    phrasesLeft = phrasesTotal.slice();
    	$("#reset").hide();
    	$("#log").html("");
    	$("#cont").html("Play");
    	$("#cont").show();
	}
};

$(document).ready(function(){
    $("#gameMenu").hide();
	$("#reset").hide();
	$("#log").html("");
	$("#cont").html("Play");
    	$("#cont").show();
	$("#cont").click(cont);
	$("#next").click(next);
	$("#reset").click(reset);
});
<commit_msg>boom play added<commit_after>var boom = new Audio('boom.aiff');

function Timer(){
	// pick random duration after which end the current game
	if (localStorage.getItem('dist') === "normal"){
		this.duration = normrv(minDuration, maxDuration, meanDuration, sdDuration);
	}
	else{
		this.duration = uniform(minDuration, maxDuration);
	}
	this.tick = function(){
		this.time = (Date.now() - this.startTime) / 1000;
		$('#timer').html(this.time.toFixed(2) + " seconds");
		$("#reset").show();
		$("#cont").html("Continue");
		if (this.time > this.duration || !(this.ticking)){
			clearInterval(this.interval);
			phrasesLeft.push(nextPhrase);
			$("#gameMenu").hide();
			$("#startMenu").show();
			this.ticking = false;
			if (this.time > this.duration){
				$("#log").html("BOOM! You ran out of time.");
			}
			boom.play();
		}
	};
	this.stop = function(){
		this.ticking = false;
	};
	this.start = function(){
		this.startTime = Date.now();
		this.ticking = true;
		this.interval = setInterval(this.tick.bind(this), 10);
	};
}

var timer;
var cont = function(){
	$("#startMenu").hide();
	$("#gameMenu").show();
	timer = new Timer();
	console.log(timer);
	timer.start();
	next();
};

var nextPhrase;
var next = function(){
	if (phrasesLeft.length === 0){
		$("#log").html("BOOM! You ran out of catchphrases!");
		$("#cont").hide();
		timer.stop();
	}
	var idx = Math.floor(Math.random() * phrasesLeft.length);
	nextPhrase = phrasesLeft.splice(idx, 1)[0];
	$("#phrase").html(nextPhrase);
};

var skip = function(){
	var skippedPhrase = nextPhrase;
	next();
	phrasesLeft.push(skippedPhrase);
}

var reset = function(){
	var response = confirm("Are you sure you want to reset your progress through the pack?");
	if (response === true){
	    phrasesLeft = phrasesTotal.slice();
    	$("#reset").hide();
    	$("#log").html("");
    	$("#cont").html("Play");
    	$("#cont").show();
	}
};

$(document).ready(function(){
    $("#gameMenu").hide();
	$("#reset").hide();
	$("#log").html("");
	$("#cont").html("Play");
    	$("#cont").show();
	$("#cont").click(cont);
	$("#next").click(next);
	$("#reset").click(reset);
});
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import addons from '@kadira/storybook-addons'
import Prism from 'prismjs'

require('prismjs/themes/prism.css')

export class JSX extends Component {
  constructor(...args) {
    super(...args)

    this.state = {}
    this.onAddJSX = this.onAddJSX.bind(this)
    this.stopListeningOnStory = () => this.setState({})
  }

  componentDidMount() {
    const { channel, api } = this.props

    channel.on('kadira/jsx/add_jsx', this.onAddJSX)
    api.onStory(this.setCurrent.bind(this))
  }

  componentWillUnmount() {
    const { channel } = this.props

    if (this.stopListeningOnStory) {
      this.stopListeningOnStory()
    }
    channel.removeListener('kadira/jsx/add_jsx', this.onAddJSX)
  }

  setCurrent(kind, story) {
    this.setState({ current: { kind, story } })
  }

  onAddJSX(kind, story, jsx) {
    const state = this.state

    if (typeof state[kind] === 'undefined') {
      state[kind] = {}
    }
    state[kind][story] = jsx
    this.setState(state)
  }

  render() {
    if (
      typeof this.state.current !== 'undefined' &&
      typeof this.state[this.state.current.kind] !== 'undefined'
    ) {
      const current = this.state.current
      const code = this.state[current.kind][current.story]
      const jsx = Prism.highlight(code, Prism.languages.html)

      return (

      )
    } else {
      return

    }
  }
}
addons.register('kadira/jsx', api => {
  addons.addPanel('kadira/jsx/panel', {
    title: 'JSX',
    render: () => <JSX channel={addons.getChannel()} api={api} />,
  })
})

const styles = {
  pre: {
    padding: '5px 15px',
  },
}
<commit_msg>fix adding prismjs css<commit_after>import React, { Component } from 'react'
import addons from '@kadira/storybook-addons'
import Prism from 'prismjs'

const css = `/**
 * prism.js default theme for JavaScript, CSS and HTML
 * Based on dabblet (http://dabblet.com)
 * @author Lea Verou
 */

code[class*="language-"],
pre[class*="language-"] {
	color: black;
	background: none;
	text-shadow: 0 1px white;
	font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
	text-align: left;
	white-space: pre;
	word-spacing: normal;
	word-break: normal;
	word-wrap: normal;
	line-height: 1.5;

	-moz-tab-size: 4;
	-o-tab-size: 4;
	tab-size: 4;

	-webkit-hyphens: none;
	-moz-hyphens: none;
	-ms-hyphens: none;
	hyphens: none;
}

pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
	text-shadow: none;
	background: #b3d4fc;
}

pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
	text-shadow: none;
	background: #b3d4fc;
}

@media print {
	code[class*="language-"],
	pre[class*="language-"] {
		text-shadow: none;
	}
}

/* Code blocks */
pre[class*="language-"] {
	padding: 1em;
	margin: .5em 0;
	overflow: auto;
}

:not(pre) > code[class*="language-"],
pre[class*="language-"] {
	background: #f5f2f0;
}

/* Inline code */
:not(pre) > code[class*="language-"] {
	padding: .1em;
	border-radius: .3em;
	white-space: normal;
}

.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
	color: slategray;
}

.token.punctuation {
	color: #999;
}

.namespace {
	opacity: .7;
}

.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
	color: #905;
}

.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
	color: #690;
}

.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
	color: #a67f59;
	background: hsla(0, 0%, 100%, .5);
}

.token.atrule,
.token.attr-value,
.token.keyword {
	color: #07a;
}

.token.function {
	color: #DD4A68;
}

.token.regex,
.token.important,
.token.variable {
	color: #e90;
}

.token.important,
.token.bold {
	font-weight: bold;
}
.token.italic {
	font-style: italic;
}

.token.entity {
	cursor: help;
}
`
const prismStyle = document.createElement('style')
prismStyle.innerHTML = css
document.body.appendChild(prismStyle)

export class JSX extends Component {
  constructor(...args) {
    super(...args)

    this.state = {}
    this.onAddJSX = this.onAddJSX.bind(this)
    this.stopListeningOnStory = () => this.setState({})
  }

  componentDidMount() {
    const { channel, api } = this.props

    channel.on('kadira/jsx/add_jsx', this.onAddJSX)
    api.onStory(this.setCurrent.bind(this))
  }

  componentWillUnmount() {
    const { channel } = this.props

    if (this.stopListeningOnStory) {
      this.stopListeningOnStory()
    }
    channel.removeListener('kadira/jsx/add_jsx', this.onAddJSX)
  }

  setCurrent(kind, story) {
    this.setState({ current: { kind, story } })
  }

  onAddJSX(kind, story, jsx) {
    const state = this.state

    if (typeof state[kind] === 'undefined') {
      state[kind] = {}
    }
    state[kind][story] = jsx
    this.setState(state)
  }

  render() {
    if (
      typeof this.state.current !== 'undefined' &&
      typeof this.state[this.state.current.kind] !== 'undefined'
    ) {
      const current = this.state.current
      const code = this.state[current.kind][current.story]
      const jsx = Prism.highlight(code, Prism.languages.html)

      return (

      )
    } else {
      return

    }
  }
}
addons.register('kadira/jsx', api => {
  addons.addPanel('kadira/jsx/panel', {
    title: 'JSX',
    render: () => <JSX channel={addons.getChannel()} api={api} />,
  })
})

const styles = {
  pre: {
    padding: '5px 15px',
  },
}
<|endoftext|>