<commit_before>import React from 'react';
import { Button } from 'semantic-ui-react'

const Roulette = props => {
  const result = props.resultObject.name ? true : false;
  const menu = props.menuUrl.length > 0 ? true : false;

	return (

        {
          (props.restaurants.length > 0) &&
          <Button.Group>
            <Button onClick={props.handleRestaurantRoulette}>Randomly Select A Favorite</Button>
            <Button.Or />
            <Button onClick={props.generateRandomRestaurant}>Surprise me</Button>
          </Button.Group>
        }

			{result &&
You must go to: {props.resultObject.name}
}
      {menu &&
Check out the menu.
}

	);
};

export default Roulette;
<commit_msg>buttons for days<commit_after>import React from 'react';
import { Button } from 'semantic-ui-react'

const Roulette = props => {
  const result = props.resultObject.name ? true : false;
  const menu = props.menuUrl.length > 0 ? true : false;

	return (

        {
          (props.restaurants.length < 1) ? <Button fluid basic color="grey" onClick={props.generateRandomRestaurant}>Just Tell Me Where To Go</Button> :
            <Button.Group>
              <Button onClick={props.handleRestaurantRoulette}>Randomly Select A Favorite</Button>
              <Button.Or />
              <Button onClick={props.generateRandomRestaurant}>Surprise me</Button>
            </Button.Group>
        }

			{result &&
You must go to: {props.resultObject.name}
}
      {menu &&
Check out the menu.
}

	);
};

export default Roulette;
<|endoftext|>


<commit_before>var ngActionCable = angular.module('ngActionCable',['ngWebSocket']);

// default websocket configs
// looks for Rails' <%= action_cable_meta_tag %> in this format:
// <meta name="action-cable-url" content="ws://localhost:3000/cable"/>
ngActionCable.value('WebsocketConfig', {
  autoStart: true,
  wsUri: angular.element("meta[name='action-cable-url']").attr("content") || "",
  debug: false
});

ngActionCable.factory('WebsocketController', function () {

  // add a hash of callbacks here that `route_channel` will call on an incoming message.
  // actions format: actions[channelName][dataParams] = [callback1, callback2, ...]
  // e.g. actions["GlobalsData"][JSON.stringify({"responder_id":1})]= [function(message){...}, assignment_2: function(message){...}, ... ]
  var actions = {
    welcome: function(message){
      // console.log('willkommen');
    },
    ping: function(message){
      // console.log('WS ping');
    },
    confirm_subscription: function(message){
      // console.log('WS confirm_subscription on channel: ' + message.identifier);
    },
    ws_404: function(message){
      // console.log('Route not found: ' + message);
    }
  };

  var routeToActions= function(actionCallbacks, message){
    angular.forEach(actionCallbacks, function(func, id){
      func.apply(null, [message]);
    });
  }

  var route = function(message){
    if (!!actions[message.type]) {
      actions[message.type](message);
    } else if (!!findActionCallbacksForChannel(channel_from(message), params_from(message))) {
      var actionCallbacks= findActionCallbacksForChannel(channel_from(message), params_from(message));
      routeToActions(actionCallbacks, message.message);
    } else {
      actions.ws_404(message);
    };
  };

  function findActionCallbacksForChannel(channelName, params){
    return (actions[channelName] && actions[channelName][params]);
  }

  function channel_from(message){
    if (message && message.identifier) {
      return JSON.parse(message.identifier).channel;
    };
  };

  function params_from(message){
    var paramsData= JSON.parse(message.identifier).data;
    return JSON.stringify(paramsData);
  }

  var methods= {
    post: function(message){
      return route(message);
    },
    actions: actions
  };

  return methods;
});

// ActionCable formats:
// ! Indentifier for subscribe, unsubscribe and message must be the same.
//
// {
//   "command": "subscribe",
//   "identifier": JSON.stringify({"channel": "UpdatesChannel",  "data": "name"}),
// }
//  - will set params to ["identifier"]["data"]
//
// {
//   "command": "unsubscribe",
//   "identifier": JSON.stringify({"channel": "UpdatesChannel",  "data": "name"}),
// }
//  - will set params to ["identifier"]["data"]
//
// {
//   "command": "message",
//   "identifier": JSON.stringify({"channel": "UpdatesChannel",  "data": "name"}),
//   "data": JSON.stringify({"message": "bla bla", "action": "foobar"})
// }
//  - will call foobar(data)
//  - will set params to ["identifier"]["data"]

ngActionCable.factory("Websocket", function($websocket, WebsocketController, WebsocketConfig) {
  var wsUrl = WebsocketConfig.wsUri;
  var controller = WebsocketController;
  var dataStream = null;
  var methods;
  var connected = false;
  var currentChannels = [];
  var close_connection = function(){
    if (dataStream){
      dataStream.close({"force":true});
      dataStream = null;
      connected = false;
    };
  };
  var subscribe_to = function(channel, data){
    if (typeof(data)==='undefined') data = "N/A";
    console.log("-> subscribing to: " + channel)
    new_data_stream().send(JSON.stringify({
        "command": "subscribe",
        "identifier": JSON.stringify({"channel": channel, "data": data})
      }));
  };
  var unsubscribe_from = function(channel, data){
    if (typeof(data)==='undefined') data = "N/A";
    console.log("<- unsubscribing from: " + channel)
    new_data_stream().send(JSON.stringify({
        "command": "unsubscribe",
        "identifier": JSON.stringify({"channel": channel, "data": data})
      }));
  };
  var send_to = function(channel, data, message, action){
    if (typeof(data)==='undefined') data = "N/A";
    console.log("=> sending to: " + channel)
    new_data_stream().send(JSON.stringify({
        "command": "message",
        "identifier": JSON.stringify({"channel": channel, "data": data}),
        "data": JSON.stringify({"message": message, "action": action})
      }));
  };
  var new_data_stream = function(){
    if(dataStream == null) {
      dataStream = $websocket(wsUrl);
      dataStream.onClose(function(arg){
        close_connection();
        connected = false;
        methods.on_connection_close_callback();
      });
      dataStream.onOpen(function(arg){
        connected = true;
        currentChannels.forEach(function(channel){ subscribe_to(channel.name, channel.data); });
        methods.on_connection_open_callback();
      });
      dataStream.onMessage(function(message) {   //arriving message from backend
        controller.post(JSON.parse(message.data));
      });
    };
    return dataStream;
  };
  methods = {
    connected: function(){ return connected },
    attempt_restart: function(){
      close_connection();
      new_data_stream();
      return true;
    },
    currentChannels: currentChannels,
    close: function(){ return close_connection(); },
    on_connection_close_callback: function(){},
    on_connection_open_callback: function(){},
    subscribe: function(channel, data){
      currentChannels.push({name: channel, data: data});
      this.connected() && subscribe_to(channel, data);
    },
    unsubscribe: function(channel, data){
      for(var i=0; i<currentChannels.length; i++){ if (currentChannels[i].name===channel) {currentChannels.splice(i, 1);} }
      this.connected() && unsubscribe_from(channel, data);
    },
    send: function(channel, data, message, action){
      console.log("send requested");
      this.connected() && send_to(channel, data, message, action);
    }
  };
  return methods;
});

ngActionCable.factory("WebsocketChannel",function (WebsocketController, Websocket){
  return function(channelName, channelParams){
    this._websocketControllerActions= function(){
      var _channelParamsString= JSON.stringify(this.channelParams);
      WebsocketController.actions[this.channelName]= WebsocketController.actions[this.channelName] || {};
      WebsocketController.actions[this.channelName][_channelParamsString]= WebsocketController.actions[this.channelName][_channelParamsString] || [];
      return WebsocketController.actions[this.channelName][_channelParamsString];
    }

    this._subscriptionCount= function(){
      return this.callbacks.length;
    };

  }
});

// SocketWrangler to start, stop or try reconnect websockets every intervalTime milliseconds.
//
// Current status is denoted by three booleans:
// connected(), connecting(), and disconnected(), in an abstraction
// of the internal trivalent logic. Exactly one will be true at all times.
//
// Actions are start() and stop()
ngActionCable.factory("SocketWrangler", function(Websocket) {
  var intervalTime= 8647;
  var websocket= Websocket;
  var _live= false;
  var _connecting= false;
  var connectNow= function(){
    websocket.attempt_restart();
  };
  var startInterval= function(){
    _connecting= _connecting || setInterval(function(){
      connectNow();
    }, intervalTime);
  };
  var stopInterval= function(){
    clearInterval(_connecting);
    _connecting= false;
  };
  websocket.on_connection_close_callback = function(){
    if (_live) { startInterval(); };
    console.log("close callback");
  };
  websocket.on_connection_open_callback = function(){
    stopInterval();
    console.log("open callback");
  };
  var methods= {
    connected: function(){
      return (_live && !_connecting);
    },
    connecting: function(){
      return (_live && !!_connecting);
    },
    disconnected: function(){
      return !_live;
    },
    start: function(){
      console.info("Live STARTED");
      _live= true;
      startInterval();
      connectNow();
    },
    stop: function(){
      console.info("Live stopped");
      _live= false;
      stopInterval();
      websocket.close();
    }
  };
  return methods;
});
<commit_msg>adds WebsocketChannel instance attrs<commit_after>var ngActionCable = angular.module('ngActionCable',['ngWebSocket']);

// default websocket configs
// looks for Rails' <%= action_cable_meta_tag %> in this format:
// <meta name="action-cable-url" content="ws://localhost:3000/cable"/>
ngActionCable.value('WebsocketConfig', {
  autoStart: true,
  wsUri: angular.element("meta[name='action-cable-url']").attr("content") || "",
  debug: false
});

ngActionCable.factory('WebsocketController', function () {

  // add a hash of callbacks here that `route_channel` will call on an incoming message.
  // actions format: actions[channelName][dataParams] = [callback1, callback2, ...]
  // e.g. actions["GlobalsData"][JSON.stringify({"responder_id":1})]= [function(message){...}, assignment_2: function(message){...}, ... ]
  var actions = {
    welcome: function(message){
      // console.log('willkommen');
    },
    ping: function(message){
      // console.log('WS ping');
    },
    confirm_subscription: function(message){
      // console.log('WS confirm_subscription on channel: ' + message.identifier);
    },
    ws_404: function(message){
      // console.log('Route not found: ' + message);
    }
  };

  var routeToActions= function(actionCallbacks, message){
    angular.forEach(actionCallbacks, function(func, id){
      func.apply(null, [message]);
    });
  }

  var route = function(message){
    if (!!actions[message.type]) {
      actions[message.type](message);
    } else if (!!findActionCallbacksForChannel(channel_from(message), params_from(message))) {
      var actionCallbacks= findActionCallbacksForChannel(channel_from(message), params_from(message));
      routeToActions(actionCallbacks, message.message);
    } else {
      actions.ws_404(message);
    };
  };

  function findActionCallbacksForChannel(channelName, params){
    return (actions[channelName] && actions[channelName][params]);
  }

  function channel_from(message){
    if (message && message.identifier) {
      return JSON.parse(message.identifier).channel;
    };
  };

  function params_from(message){
    var paramsData= JSON.parse(message.identifier).data;
    return JSON.stringify(paramsData);
  }

  var methods= {
    post: function(message){
      return route(message);
    },
    actions: actions
  };

  return methods;
});

// ActionCable formats:
// ! Indentifier for subscribe, unsubscribe and message must be the same.
//
// {
//   "command": "subscribe",
//   "identifier": JSON.stringify({"channel": "UpdatesChannel",  "data": "name"}),
// }
//  - will set params to ["identifier"]["data"]
//
// {
//   "command": "unsubscribe",
//   "identifier": JSON.stringify({"channel": "UpdatesChannel",  "data": "name"}),
// }
//  - will set params to ["identifier"]["data"]
//
// {
//   "command": "message",
//   "identifier": JSON.stringify({"channel": "UpdatesChannel",  "data": "name"}),
//   "data": JSON.stringify({"message": "bla bla", "action": "foobar"})
// }
//  - will call foobar(data)
//  - will set params to ["identifier"]["data"]

ngActionCable.factory("Websocket", function($websocket, WebsocketController, WebsocketConfig) {
  var wsUrl = WebsocketConfig.wsUri;
  var controller = WebsocketController;
  var dataStream = null;
  var methods;
  var connected = false;
  var currentChannels = [];
  var close_connection = function(){
    if (dataStream){
      dataStream.close({"force":true});
      dataStream = null;
      connected = false;
    };
  };
  var subscribe_to = function(channel, data){
    if (typeof(data)==='undefined') data = "N/A";
    console.log("-> subscribing to: " + channel)
    new_data_stream().send(JSON.stringify({
        "command": "subscribe",
        "identifier": JSON.stringify({"channel": channel, "data": data})
      }));
  };
  var unsubscribe_from = function(channel, data){
    if (typeof(data)==='undefined') data = "N/A";
    console.log("<- unsubscribing from: " + channel)
    new_data_stream().send(JSON.stringify({
        "command": "unsubscribe",
        "identifier": JSON.stringify({"channel": channel, "data": data})
      }));
  };
  var send_to = function(channel, data, message, action){
    if (typeof(data)==='undefined') data = "N/A";
    console.log("=> sending to: " + channel)
    new_data_stream().send(JSON.stringify({
        "command": "message",
        "identifier": JSON.stringify({"channel": channel, "data": data}),
        "data": JSON.stringify({"message": message, "action": action})
      }));
  };
  var new_data_stream = function(){
    if(dataStream == null) {
      dataStream = $websocket(wsUrl);
      dataStream.onClose(function(arg){
        close_connection();
        connected = false;
        methods.on_connection_close_callback();
      });
      dataStream.onOpen(function(arg){
        connected = true;
        currentChannels.forEach(function(channel){ subscribe_to(channel.name, channel.data); });
        methods.on_connection_open_callback();
      });
      dataStream.onMessage(function(message) {   //arriving message from backend
        controller.post(JSON.parse(message.data));
      });
    };
    return dataStream;
  };
  methods = {
    connected: function(){ return connected },
    attempt_restart: function(){
      close_connection();
      new_data_stream();
      return true;
    },
    currentChannels: currentChannels,
    close: function(){ return close_connection(); },
    on_connection_close_callback: function(){},
    on_connection_open_callback: function(){},
    subscribe: function(channel, data){
      currentChannels.push({name: channel, data: data});
      this.connected() && subscribe_to(channel, data);
    },
    unsubscribe: function(channel, data){
      for(var i=0; i<currentChannels.length; i++){ if (currentChannels[i].name===channel) {currentChannels.splice(i, 1);} }
      this.connected() && unsubscribe_from(channel, data);
    },
    send: function(channel, data, message, action){
      console.log("send requested");
      this.connected() && send_to(channel, data, message, action);
    }
  };
  return methods;
});

ngActionCable.factory("WebsocketChannel",function (WebsocketController, Websocket){
  return function(channelName, channelParams){
    this._websocketControllerActions= function(){
      var _channelParamsString= JSON.stringify(this.channelParams);
      WebsocketController.actions[this.channelName]= WebsocketController.actions[this.channelName] || {};
      WebsocketController.actions[this.channelName][_channelParamsString]= WebsocketController.actions[this.channelName][_channelParamsString] || [];
      return WebsocketController.actions[this.channelName][_channelParamsString];
    }

    this._subscriptionCount= function(){
      return this.callbacks.length;
    };

    this.channelName= channelName;
    this.channelParams= channelParams || {};
    this.onMessageCallback= null;
    this.callbacks= this._websocketControllerActions();

  }
});

// SocketWrangler to start, stop or try reconnect websockets every intervalTime milliseconds.
//
// Current status is denoted by three booleans:
// connected(), connecting(), and disconnected(), in an abstraction
// of the internal trivalent logic. Exactly one will be true at all times.
//
// Actions are start() and stop()
ngActionCable.factory("SocketWrangler", function(Websocket) {
  var intervalTime= 8647;
  var websocket= Websocket;
  var _live= false;
  var _connecting= false;
  var connectNow= function(){
    websocket.attempt_restart();
  };
  var startInterval= function(){
    _connecting= _connecting || setInterval(function(){
      connectNow();
    }, intervalTime);
  };
  var stopInterval= function(){
    clearInterval(_connecting);
    _connecting= false;
  };
  websocket.on_connection_close_callback = function(){
    if (_live) { startInterval(); };
    console.log("close callback");
  };
  websocket.on_connection_open_callback = function(){
    stopInterval();
    console.log("open callback");
  };
  var methods= {
    connected: function(){
      return (_live && !_connecting);
    },
    connecting: function(){
      return (_live && !!_connecting);
    },
    disconnected: function(){
      return !_live;
    },
    start: function(){
      console.info("Live STARTED");
      _live= true;
      startInterval();
      connectNow();
    },
    stop: function(){
      console.info("Live stopped");
      _live= false;
      stopInterval();
      websocket.close();
    }
  };
  return methods;
});
<|endoftext|>


<commit_before>// @flow
import React from 'react'
import uuid from 'uuid'
import equal from 'deep-equal'

import SearchActions from '../actions/SearchActions.js'
import Components from '../constants/QueryComponentConstants.js'

import { connect } from 'react-redux'
import { addQueryComponent, modifyQueryComponent, removeQueryComponent, makeSearchRequest } from '../actions/query'

class base extends React.Component {
  constructor(props) {
    super(props)
    this.state = { uuid: uuid.v4() }
  }

  componentDidMount() {
    const { namespace, componentName, data } = this.props
    const { uuid } = this.state
    SearchActions.update(namespace, uuid, componentName, data)

    if (this.props.runOnMount) {
      // SearchActions.nsearch(this.props.namespace)
      this.props.makeSearchRequest()
    }

    this.props.addQueryComponent(uuid, namespace, data, componentName)
  }

  componentWillReceiveProps(newProps) {
    if (!newProps.runIfSame && equal(newProps, this.props)) {
      return
    }
    const { namespace, componentName, data } = newProps
    const { uuid } = this.state
    SearchActions.update(namespace, uuid, componentName, data)

    if (newProps.runOnUpdate) {
      // SearchActions.nsearch(namespace)
      this.props.makeSearchRequest()
    }

    this.props.modifyQueryComponent(uuid, namespace, data, componentName)
  }

  componentWillUnmount() {
    const { namespace, componentName } = this.props
    const { uuid } = this.state
    SearchActions.remove(namespace, uuid, componentName)

    if (this.props.runOnUnmount) {
      // SearchActions.nsearch(this.props.namespace)
      this.props.makeSearchRequest()
    }

    this.props.removeQueryComponent(uuid, namespace)
  }

  render() { return null }
}

base.propTypes = {
  runOnMount: React.PropTypes.bool,
  runOnUnmount: React.PropTypes.bool,
  runOnUpdate: React.PropTypes.bool,
  namespace: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.arrayOf(React.PropTypes.string),
  ]),
  componentName: React.PropTypes.oneOf(Components.list).isRequired,
  data: React.PropTypes.any.isRequired,
}

base.defaultProps = {
  runOnMount: false,
  runOnUnmount: false,
  runOnUpdate: false,
  namespace: 'default',
}

const Base = connect(
  null,
  (dispatch, props) => ({
    addQueryComponent: (uuid, namespace, data, queryDataType) => dispatch(addQueryComponent(uuid, namespace, data, queryDataType)),
    modifyQueryComponent: (uuid, namespace, data, queryDataType) => dispatch(modifyQueryComponent(uuid, namespace, data, queryDataType)),
    removeQueryComponent: (uuid, namespace) => dispatch(removeQueryComponent(uuid, namespace)),
    makeSearchRequest: () => dispatch(makeSearchRequest(props.namespace)),
  }),
)(base)

export default Base
<commit_msg>Fix the order that events are dispatched from base<commit_after>// @flow
import React from 'react'
import uuid from 'uuid'
import equal from 'deep-equal'

import SearchActions from '../actions/SearchActions.js'
import Components from '../constants/QueryComponentConstants.js'

import { connect } from 'react-redux'
import { addQueryComponent, modifyQueryComponent, removeQueryComponent, makeSearchRequest } from '../actions/query'

class base extends React.Component {
  constructor(props) {
    super(props)
    this.state = { uuid: uuid.v4() }
  }

  componentDidMount() {
    const { namespace, componentName, data } = this.props
    const { uuid } = this.state
    SearchActions.update(namespace, uuid, componentName, data)
    this.props.addQueryComponent(uuid, namespace, data, componentName)

    if (this.props.runOnMount) {
      // SearchActions.nsearch(this.props.namespace)
      this.props.makeSearchRequest()
    }

  }

  componentWillReceiveProps(newProps) {
    if (!newProps.runIfSame && equal(newProps, this.props)) {
      return
    }
    const { namespace, componentName, data } = newProps
    const { uuid } = this.state
    SearchActions.update(namespace, uuid, componentName, data)
    this.props.modifyQueryComponent(uuid, namespace, data, componentName)

    if (newProps.runOnUpdate) {
      // SearchActions.nsearch(namespace)
      this.props.makeSearchRequest()
    }

  }

  componentWillUnmount() {
    const { namespace, componentName } = this.props
    const { uuid } = this.state
    SearchActions.remove(namespace, uuid, componentName)
    this.props.removeQueryComponent(uuid, namespace)

    if (this.props.runOnUnmount) {
      // SearchActions.nsearch(this.props.namespace)
      this.props.makeSearchRequest()
    }

  }

  render() { return null }
}

base.propTypes = {
  runOnMount: React.PropTypes.bool,
  runOnUnmount: React.PropTypes.bool,
  runOnUpdate: React.PropTypes.bool,
  namespace: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.arrayOf(React.PropTypes.string),
  ]),
  componentName: React.PropTypes.oneOf(Components.list).isRequired,
  data: React.PropTypes.any.isRequired,
}

base.defaultProps = {
  runOnMount: false,
  runOnUnmount: false,
  runOnUpdate: false,
  namespace: 'default',
}

const Base = connect(
  null,
  (dispatch, props) => ({
    addQueryComponent: (uuid, namespace, data, queryDataType) => dispatch(addQueryComponent(uuid, namespace, data, queryDataType)),
    modifyQueryComponent: (uuid, namespace, data, queryDataType) => dispatch(modifyQueryComponent(uuid, namespace, data, queryDataType)),
    removeQueryComponent: (uuid, namespace) => dispatch(removeQueryComponent(uuid, namespace)),
    makeSearchRequest: () => dispatch(makeSearchRequest(props.namespace)),
  }),
)(base)

export default Base
<|endoftext|>


<commit_before>//   (c) 2007-2016 by Kajona, www.kajona.de
//       Published under the GNU LGPL v2.1, see /system/licence_lgpl.txt
//       $Id$

$(function () {

    $.widget('custom.catcomplete', $.ui.autocomplete, {
        _renderMenu: function(ul, items) {
            var self = this;
            var currentCategory = '';

            $.each(items, function(index, item) {
                if (item.module != currentCategory) {
                    ul.append('

' + item.module + '

');
                    currentCategory = item.module;
                }
                self._renderItemData(ul, item);
            });

            ul.append('
'+searchExtendText+'
');
            ul.addClass('dropdown-menu');
            ul.addClass('search-dropdown-menu');

            ul.find('.detailedResults a').click(function () {
                $('.navbar-search').submit();
            });
        },
        _renderItemData: function (ul, item) {
            return $('

')
                .data('ui-autocomplete-item', item)
                .append('' + item.icon + item.description + '')
                .appendTo(ul);
        }
    });

    $('#globalSearchInput').catcomplete({

        minLength: 2,
        delay: 500,

        //source: '_skinwebpath_/search.json',
        source: function(request, response) {
            $.ajax({
                url: KAJONA_WEBPATH+'/xml.php?admin=1',
                type: 'POST',
                dataType: 'json',
                data: {
                    search_query: request.term,
                    module: 'search',
                    action: 'searchXml',
                    asJson: '1'
                },
                success: response
            });
        },
        select: function (event, ui) {
            if(ui.item) {
                document.location = ui.item.link;
            }
        },
        messages: {
            noResults: '',
            results: function() {}
        },
        search: function(event, ui) {
            $(this).parent().find('.input-group-addon').html('');
            KAJONA.admin.WorkingIndicator.getInstance().start();
        },
        response: function(event, ui) {
            //$(this).css("background-image", "none");
            $(this).parent().find('.input-group-addon').html('');
            KAJONA.admin.WorkingIndicator.getInstance().stop();
        }
    });

    // init popovers & tooltips
    $('#content a[rel=popover]').popover();
    KAJONA.admin.tooltip.initTooltip();

    KAJONA.admin.statusDisplay.classOfMessageBox = "alert alert-info";
    KAJONA.admin.statusDisplay.classOfErrorBox = "alert alert-error";

    KAJONA.admin.scroll = null;
    $(window).scroll(function() {
        var scroll = $(this).scrollTop();
        if(scroll > 10 && KAJONA.admin.scroll != 'top') {
            $("ul.breadcrumb").addClass("breadcrumbTop");
            $("#quickhelp").addClass("quickhelpTop");
            KAJONA.admin.scroll = "top";
        }
        else if(scroll <= 10 && KAJONA.admin.scroll != 'margin') {
            $("ul.breadcrumb").removeClass("breadcrumbTop");
            $("#quickhelp").removeClass("quickhelpTop");
            KAJONA.admin.scroll = "fixed";
        }

    });

    KAJONA.v4skin.breadcrumb.updatePathNavigationEllipsis();
    $(window).on("resize", function() {
        KAJONA.v4skin.breadcrumb.updatePathNavigationEllipsis();

    });

    //register desktop notifications for messaging
    //KAJONA.util.desktopNotification.grantPermissions();

    //init offacnvas menu
    $('[data-toggle="offcanvas"]').click(function () {
        $('.row-offcanvas').toggleClass('active')
    });

});

if (typeof KAJONA == "undefined") {
    alert('load kajona.js before!');
}

KAJONA.v4skin = {

    properties : {
        messaging : {},
        tags : {}
    },

    defaultAutoComplete : function() {
        this.minLength = 0;

        this.delay = 500;

        this.messages = {
            noResults: '',
            results: function() {return ''}
        };

        this.search = function(event, ui) {
            //If input field changes -> reset hidden id field
            var $objCur = $(this);
            if(!$objCur.is('[readonly]')) {
                if($('#'+$objCur.attr('id')+'_id')) {
                    $( '#'+$objCur.attr('id')+'_id' ).val( "" );
                }
            }

            //Formentry must have at least 2 charackters to trigger search.
            if(event.target.value.length < 2) {
                event.stopPropagation();
                return false;
            }
            $objCur.parent().find('.loading-feedback').html('');
        };

        this.response = function(event, ui) {
            $(this).parent().find('.loading-feedback').html('');
        };

        this.focus = function(event, ui) {
            return false;
        };

        this.select = function( event, ui ) {
            if(ui.item) {
                var $objCur = $(this);
                $objCur.val( ui.item.title );
                if($('#'+$objCur.attr('id')+'_id')) {
                    $( '#'+$objCur.attr('id')+'_id' ).val( ui.item.systemid);
                }
            }
        };

        this.create = function( event, ui ) {
            var $objCur = $(this);
            $objCur.closest('.form-group').addClass('has-feedback');
            $objCur.after("");
        }
    },

    messaging : {

        bitFirstLoad : true,

        pollMessages : function() {
            KAJONA.admin.messaging.getRecentMessages(function (objResponse) {
                var $userNotificationsCount = $('#userNotificationsCount');
                var oldCount = $userNotificationsCount.text();
                $userNotificationsCount.text(objResponse.messageCount);
                if (objResponse.messageCount > 0) {
                    $userNotificationsCount.show();
                    if (oldCount != objResponse.messageCount) {
                        var strTitle = document.title.replace("(" + oldCount + ")", "");
                        document.title = "(" + objResponse.messageCount + ") " + strTitle;

                        //if (!KAJONA.v4skin.messaging.bitFirstLoad && oldCount < objResponse.messageCount) {
                        //    KAJONA.util.desktopNotification.showMessage(KAJONA.v4skin.properties.messaging.notification_title, KAJONA.v4skin.properties.messaging.notification_body, function () {
                        //        document.location.href = KAJONA_WEBPATH+'/index.php?admin=1&module=messaging';
                        //    });
                        //}
                    }

                } else {
                    $userNotificationsCount.hide();
                }

                $('#messagingShortlist').empty();
                $.each(objResponse.messages, function (index, item) {
                    if (item.unread == 0)
                        $('#messagingShortlist').append("
 " + item.title + "
");
                    else
                        $('#messagingShortlist').append("
 " + item.title + "
");
                });
                $('#messagingShortlist').append("

 " + KAJONA.v4skin.properties.messaging.show_all + "
");

                window.setTimeout("KAJONA.v4skin.messaging.pollMessages()", 20000);
                KAJONA.v4skin.messaging.bitFirstLoad = false;
            });
        }
    }
};

KAJONA.v4skin.breadcrumb = {
    updatePathNavigationEllipsis : function() {

        var $arrPathLIs = $(".pathNaviContainer  .breadcrumb  li.pathentry");
        var $objBreadcrumb = $(".pathNaviContainer  .breadcrumb");

        //first run: get the number of entries and a first styling
        var intEntries = ($arrPathLIs.length);
        var intWidth = $objBreadcrumb.width();
        var intMaxWidth = Math.ceil(intWidth/intEntries);

        $arrPathLIs.css("max-width", intMaxWidth);

        //second run: calc the remaining x-space
        var intTotalUnused = KAJONA.v4skin.breadcrumb.getUnusedSpace(intMaxWidth);

        if(intTotalUnused > intMaxWidth) {
            intMaxWidth = Math.ceil(intWidth/ (intEntries - (Math.floor(intTotalUnused / intMaxWidth)) ));
            $arrPathLIs.css("max-width", intMaxWidth);
        }

    },

    getUnusedSpace : function(intMaxWidth) {
        var intTotalUnused = 0;
        $(".pathNaviContainer  .breadcrumb  li.pathentry").each(function() {
            var $li = $(this);
            if($li.width() < intMaxWidth) {
                intTotalUnused += (intMaxWidth - $li.width());
            }
        });

        return intTotalUnused;
    },

    appendLinkToPathNavigation : function(strLinkContent) {
        var link = $("

").append(strLinkContent+" ");
        $("div.pathNaviContainer  ul.breadcrumb").append(link);
        KAJONA.v4skin.breadcrumb.updatePathNavigationEllipsis();
    }

};

KAJONA.v4skin.initTagMenu = function() {
    KAJONA.admin.ajax.genericAjaxCall("tags", "getFavoriteTags", "", function(data, status, jqXHR) {
        if(status == 'success') {
            $.each($.parseJSON(data), function(index, item) {
                $('#tagsSubemenu').append("
 "+item.name+"
");
            });
            $('#tagsSubemenu').append("

 "+KAJONA.v4skin.properties.tags.show_all+"
")
        }
    });
};

/**
 * Removes an object list row from the list
 *
 * @param el
 */
KAJONA.v4skin.removeObjectListItem = function(el){
    // remove all active tooltips
    KAJONA.admin.tooltip.removeTooltip(el);

    // remove element
    $(el).parent().parent().fadeOut(0, function(){
        $(this).remove();
    });
};

/**
 * Gets all items containd in the object list
 *
 * @param strElementName
 * @returns {Array}
 */
KAJONA.v4skin.getObjectListItems = function(strElementName) {
    var table = KAJONA.util.getElementFromOpener(strElementName);

    var arrItems = [];

    var tbody = table.find('tbody');
    if(tbody.length > 0) {
        // remove only elements which are in the arrAvailableIds array
        tbody.children().each(function(){
            var strId = $(this).find('input[type="hidden"]').val();
            arrItems.push(strId);
        });
    }

    return arrItems;
};

/**
 * Sets an array of items to an object list. We remove only elements which are available in the arrAvailableIds array
 *
 * @param strElementName  - name of the objectlist element
 * @param arrItems        - array with item of the following format {strSystemId: <systemid>, strDisplayName:<displayname>, strIcon:<icon>}
 * @param arrAvailableIds -
 */
KAJONA.v4skin.setObjectListItems = function(strElementName, arrItems, arrAvailableIds, strDeleteButton){
    var table = KAJONA.util.getElementFromOpener(strElementName);

    var tbody = table.find('tbody');
    if(tbody.length > 0) {
        // remove only elements which are in the arrAvailableIds array
        tbody.children().each(function(){
            var strId = $(this).find('input[type="hidden"]').val();
            if($.inArray(strId, arrAvailableIds) !== -1) {//if strId in array
                $(this).remove();
            }
        });

        // add new elements
        for(var i = 0; i < arrItems.length; i++) {
            var strEscapedTitle = $('

').text(arrItems[i].strDisplayName).html();
            var html = '';
            html+= '
';
            html+= '    ' + arrItems[i].strIcon + '	';
            html+= '    ' + strEscapedTitle + ' <input type="hidden" name="' + strElementName + '[]" value="' + arrItems[i].strSystemId + '" />	';
            html+= '    ';
            html+= '        ' + strDeleteButton + '';
            html+= '    	';
            html+= '
';

            tbody.append(html);
        }
    }
};

/**
 * We get the current tree selection from the iframe element and set the selection in the object list
 *
 * @param objIframeEl
 * @param strElementName
 */
KAJONA.v4skin.updateCheckboxTreeSelection = function(objIframeEl, strElementName, strDeleteButton){
    if(objIframeEl && objIframeEl.contentWindow) {
        var jstree = objIframeEl.contentWindow.$('.jstree');
        if(jstree.length > 0) {
            // we modify only the ids which are visible for the user all other ids stay untouched
            var arrAvailableIds = [];
            jstree.find('li').each(function(){
                arrAvailableIds.push($(this).attr('systemid'));
            });

            var arrEls = jstree.jstree('get_checked');
            var arrItems = [];
            for(var i = 0; i < arrEls.length; i++) {
                var el = $(arrEls[i]);
                var strSystemId = el.attr('id');
                var strDisplayName = el.text().trim();
                var strIcon = el.find('[rel="tooltip"]').html();

                arrItems.push({
                    strSystemId: strSystemId,
                    strDisplayName: strDisplayName,
                    strIcon: strIcon
                });
            }

            KAJONA.v4skin.setObjectListItems(strElementName, arrItems, arrAvailableIds, strDeleteButton);

            jsDialog_1.hide();
        }
    }
};

/**
 * Returns all systemids which are available in the object list. The name of the object list element name must be
 * available as GET parameter "element_name"
 *
 * @returns array
 */
KAJONA.v4skin.getCheckboxTreeSelectionFromParent = function(){
    if($('.jstree').length > 0) {
        // the query parameter contains the name of the form element where we insert the selected elements
        var strElementName = KAJONA.v4skin.getQueryParameter("element_name");
        var table = parent.$('#' + strElementName);
        var arrSystemIds = [];
        if(table.length > 0) {
            table.find('input[type="hidden"]').each(function(){
                arrSystemIds.push($(this).val());
            });
        }

        return arrSystemIds;
    }
};

/**
 * Extracts an query parameter from the location query string
 *
 * @param string name
 * @returns string
 */
KAJONA.v4skin.getQueryParameter = function(name) {
    var pos = location.search.indexOf("&" + name + "=");
    if(pos != -1) {
        var endPos = location.search.indexOf("&", pos + 1);
        if(endPos == -1) {
            return location.search.substr(pos + name.length + 2);
        }
        else {
            return location.search.substr(pos + name.length + 2, endPos - (pos + name.length + 2));
        }
    }
    return null;
};
<commit_msg>UPDATE: v4skin | v4skin.js -> the default autocomplete triggers the loading animation, too<commit_after>//   (c) 2007-2016 by Kajona, www.kajona.de
//       Published under the GNU LGPL v2.1, see /system/licence_lgpl.txt
//       $Id$

$(function () {

    $.widget('custom.catcomplete', $.ui.autocomplete, {
        _renderMenu: function(ul, items) {
            var self = this;
            var currentCategory = '';

            $.each(items, function(index, item) {
                if (item.module != currentCategory) {
                    ul.append('

' + item.module + '

');
                    currentCategory = item.module;
                }
                self._renderItemData(ul, item);
            });

            ul.append('
'+searchExtendText+'
');
            ul.addClass('dropdown-menu');
            ul.addClass('search-dropdown-menu');

            ul.find('.detailedResults a').click(function () {
                $('.navbar-search').submit();
            });
        },
        _renderItemData: function (ul, item) {
            return $('

')
                .data('ui-autocomplete-item', item)
                .append('' + item.icon + item.description + '')
                .appendTo(ul);
        }
    });

    $('#globalSearchInput').catcomplete({

        minLength: 2,
        delay: 500,

        //source: '_skinwebpath_/search.json',
        source: function(request, response) {
            $.ajax({
                url: KAJONA_WEBPATH+'/xml.php?admin=1',
                type: 'POST',
                dataType: 'json',
                data: {
                    search_query: request.term,
                    module: 'search',
                    action: 'searchXml',
                    asJson: '1'
                },
                success: response
            });
        },
        select: function (event, ui) {
            if(ui.item) {
                document.location = ui.item.link;
            }
        },
        messages: {
            noResults: '',
            results: function() {}
        },
        search: function(event, ui) {
            $(this).parent().find('.input-group-addon').html('');
            KAJONA.admin.WorkingIndicator.getInstance().start();
        },
        response: function(event, ui) {
            //$(this).css("background-image", "none");
            $(this).parent().find('.input-group-addon').html('');
            KAJONA.admin.WorkingIndicator.getInstance().stop();
        }
    });

    // init popovers & tooltips
    $('#content a[rel=popover]').popover();
    KAJONA.admin.tooltip.initTooltip();

    KAJONA.admin.statusDisplay.classOfMessageBox = "alert alert-info";
    KAJONA.admin.statusDisplay.classOfErrorBox = "alert alert-error";

    KAJONA.admin.scroll = null;
    $(window).scroll(function() {
        var scroll = $(this).scrollTop();
        if(scroll > 10 && KAJONA.admin.scroll != 'top') {
            $("ul.breadcrumb").addClass("breadcrumbTop");
            $("#quickhelp").addClass("quickhelpTop");
            KAJONA.admin.scroll = "top";
        }
        else if(scroll <= 10 && KAJONA.admin.scroll != 'margin') {
            $("ul.breadcrumb").removeClass("breadcrumbTop");
            $("#quickhelp").removeClass("quickhelpTop");
            KAJONA.admin.scroll = "fixed";
        }

    });

    KAJONA.v4skin.breadcrumb.updatePathNavigationEllipsis();
    $(window).on("resize", function() {
        KAJONA.v4skin.breadcrumb.updatePathNavigationEllipsis();

    });

    //register desktop notifications for messaging
    //KAJONA.util.desktopNotification.grantPermissions();

    //init offacnvas menu
    $('[data-toggle="offcanvas"]').click(function () {
        $('.row-offcanvas').toggleClass('active')
    });

});

if (typeof KAJONA == "undefined") {
    alert('load kajona.js before!');
}

KAJONA.v4skin = {

    properties : {
        messaging : {},
        tags : {}
    },

    defaultAutoComplete : function() {
        this.minLength = 0;

        this.delay = 500;

        this.messages = {
            noResults: '',
            results: function() {return ''}
        };

        this.search = function(event, ui) {
            //If input field changes -> reset hidden id field
            var $objCur = $(this);
            if(!$objCur.is('[readonly]')) {
                if($('#'+$objCur.attr('id')+'_id')) {
                    $( '#'+$objCur.attr('id')+'_id' ).val( "" );
                }
            }

            //Formentry must have at least 2 charackters to trigger search.
            if(event.target.value.length < 2) {
                event.stopPropagation();
                return false;
            }
            $objCur.parent().find('.loading-feedback').html('');
            KAJONA.admin.WorkingIndicator.getInstance().start();
        };

        this.response = function(event, ui) {
            $(this).parent().find('.loading-feedback').html('');
            KAJONA.admin.WorkingIndicator.getInstance().stop();
        };

        this.focus = function(event, ui) {
            return false;
        };

        this.select = function( event, ui ) {
            if(ui.item) {
                var $objCur = $(this);
                $objCur.val( ui.item.title );
                if($('#'+$objCur.attr('id')+'_id')) {
                    $( '#'+$objCur.attr('id')+'_id' ).val( ui.item.systemid);
                }
            }
        };

        this.create = function( event, ui ) {
            var $objCur = $(this);
            $objCur.closest('.form-group').addClass('has-feedback');
            $objCur.after("");
        }
    },

    messaging : {

        bitFirstLoad : true,

        pollMessages : function() {
            KAJONA.admin.messaging.getRecentMessages(function (objResponse) {
                var $userNotificationsCount = $('#userNotificationsCount');
                var oldCount = $userNotificationsCount.text();
                $userNotificationsCount.text(objResponse.messageCount);
                if (objResponse.messageCount > 0) {
                    $userNotificationsCount.show();
                    if (oldCount != objResponse.messageCount) {
                        var strTitle = document.title.replace("(" + oldCount + ")", "");
                        document.title = "(" + objResponse.messageCount + ") " + strTitle;

                        //if (!KAJONA.v4skin.messaging.bitFirstLoad && oldCount < objResponse.messageCount) {
                        //    KAJONA.util.desktopNotification.showMessage(KAJONA.v4skin.properties.messaging.notification_title, KAJONA.v4skin.properties.messaging.notification_body, function () {
                        //        document.location.href = KAJONA_WEBPATH+'/index.php?admin=1&module=messaging';
                        //    });
                        //}
                    }

                } else {
                    $userNotificationsCount.hide();
                }

                $('#messagingShortlist').empty();
                $.each(objResponse.messages, function (index, item) {
                    if (item.unread == 0)
                        $('#messagingShortlist').append("
 " + item.title + "
");
                    else
                        $('#messagingShortlist').append("
 " + item.title + "
");
                });
                $('#messagingShortlist').append("

 " + KAJONA.v4skin.properties.messaging.show_all + "
");

                window.setTimeout("KAJONA.v4skin.messaging.pollMessages()", 20000);
                KAJONA.v4skin.messaging.bitFirstLoad = false;
            });
        }
    }
};

KAJONA.v4skin.breadcrumb = {
    updatePathNavigationEllipsis : function() {

        var $arrPathLIs = $(".pathNaviContainer  .breadcrumb  li.pathentry");
        var $objBreadcrumb = $(".pathNaviContainer  .breadcrumb");

        //first run: get the number of entries and a first styling
        var intEntries = ($arrPathLIs.length);
        var intWidth = $objBreadcrumb.width();
        var intMaxWidth = Math.ceil(intWidth/intEntries);

        $arrPathLIs.css("max-width", intMaxWidth);

        //second run: calc the remaining x-space
        var intTotalUnused = KAJONA.v4skin.breadcrumb.getUnusedSpace(intMaxWidth);

        if(intTotalUnused > intMaxWidth) {
            intMaxWidth = Math.ceil(intWidth/ (intEntries - (Math.floor(intTotalUnused / intMaxWidth)) ));
            $arrPathLIs.css("max-width", intMaxWidth);
        }

    },

    getUnusedSpace : function(intMaxWidth) {
        var intTotalUnused = 0;
        $(".pathNaviContainer  .breadcrumb  li.pathentry").each(function() {
            var $li = $(this);
            if($li.width() < intMaxWidth) {
                intTotalUnused += (intMaxWidth - $li.width());
            }
        });

        return intTotalUnused;
    },

    appendLinkToPathNavigation : function(strLinkContent) {
        var link = $("

").append(strLinkContent+" ");
        $("div.pathNaviContainer  ul.breadcrumb").append(link);
        KAJONA.v4skin.breadcrumb.updatePathNavigationEllipsis();
    }

};

KAJONA.v4skin.initTagMenu = function() {
    KAJONA.admin.ajax.genericAjaxCall("tags", "getFavoriteTags", "", function(data, status, jqXHR) {
        if(status == 'success') {
            $.each($.parseJSON(data), function(index, item) {
                $('#tagsSubemenu').append("
 "+item.name+"
");
            });
            $('#tagsSubemenu').append("

 "+KAJONA.v4skin.properties.tags.show_all+"
")
        }
    });
};

/**
 * Removes an object list row from the list
 *
 * @param el
 */
KAJONA.v4skin.removeObjectListItem = function(el){
    // remove all active tooltips
    KAJONA.admin.tooltip.removeTooltip(el);

    // remove element
    $(el).parent().parent().fadeOut(0, function(){
        $(this).remove();
    });
};

/**
 * Gets all items containd in the object list
 *
 * @param strElementName
 * @returns {Array}
 */
KAJONA.v4skin.getObjectListItems = function(strElementName) {
    var table = KAJONA.util.getElementFromOpener(strElementName);

    var arrItems = [];

    var tbody = table.find('tbody');
    if(tbody.length > 0) {
        // remove only elements which are in the arrAvailableIds array
        tbody.children().each(function(){
            var strId = $(this).find('input[type="hidden"]').val();
            arrItems.push(strId);
        });
    }

    return arrItems;
};

/**
 * Sets an array of items to an object list. We remove only elements which are available in the arrAvailableIds array
 *
 * @param strElementName  - name of the objectlist element
 * @param arrItems        - array with item of the following format {strSystemId: <systemid>, strDisplayName:<displayname>, strIcon:<icon>}
 * @param arrAvailableIds -
 */
KAJONA.v4skin.setObjectListItems = function(strElementName, arrItems, arrAvailableIds, strDeleteButton){
    var table = KAJONA.util.getElementFromOpener(strElementName);

    var tbody = table.find('tbody');
    if(tbody.length > 0) {
        // remove only elements which are in the arrAvailableIds array
        tbody.children().each(function(){
            var strId = $(this).find('input[type="hidden"]').val();
            if($.inArray(strId, arrAvailableIds) !== -1) {//if strId in array
                $(this).remove();
            }
        });

        // add new elements
        for(var i = 0; i < arrItems.length; i++) {
            var strEscapedTitle = $('

').text(arrItems[i].strDisplayName).html();
            var html = '';
            html+= '
';
            html+= '    ' + arrItems[i].strIcon + '	';
            html+= '    ' + strEscapedTitle + ' <input type="hidden" name="' + strElementName + '[]" value="' + arrItems[i].strSystemId + '" />	';
            html+= '    ';
            html+= '        ' + strDeleteButton + '';
            html+= '    	';
            html+= '
';

            tbody.append(html);
        }
    }
};

/**
 * We get the current tree selection from the iframe element and set the selection in the object list
 *
 * @param objIframeEl
 * @param strElementName
 */
KAJONA.v4skin.updateCheckboxTreeSelection = function(objIframeEl, strElementName, strDeleteButton){
    if(objIframeEl && objIframeEl.contentWindow) {
        var jstree = objIframeEl.contentWindow.$('.jstree');
        if(jstree.length > 0) {
            // we modify only the ids which are visible for the user all other ids stay untouched
            var arrAvailableIds = [];
            jstree.find('li').each(function(){
                arrAvailableIds.push($(this).attr('systemid'));
            });

            var arrEls = jstree.jstree('get_checked');
            var arrItems = [];
            for(var i = 0; i < arrEls.length; i++) {
                var el = $(arrEls[i]);
                var strSystemId = el.attr('id');
                var strDisplayName = el.text().trim();
                var strIcon = el.find('[rel="tooltip"]').html();

                arrItems.push({
                    strSystemId: strSystemId,
                    strDisplayName: strDisplayName,
                    strIcon: strIcon
                });
            }

            KAJONA.v4skin.setObjectListItems(strElementName, arrItems, arrAvailableIds, strDeleteButton);

            jsDialog_1.hide();
        }
    }
};

/**
 * Returns all systemids which are available in the object list. The name of the object list element name must be
 * available as GET parameter "element_name"
 *
 * @returns array
 */
KAJONA.v4skin.getCheckboxTreeSelectionFromParent = function(){
    if($('.jstree').length > 0) {
        // the query parameter contains the name of the form element where we insert the selected elements
        var strElementName = KAJONA.v4skin.getQueryParameter("element_name");
        var table = parent.$('#' + strElementName);
        var arrSystemIds = [];
        if(table.length > 0) {
            table.find('input[type="hidden"]').each(function(){
                arrSystemIds.push($(this).val());
            });
        }

        return arrSystemIds;
    }
};

/**
 * Extracts an query parameter from the location query string
 *
 * @param string name
 * @returns string
 */
KAJONA.v4skin.getQueryParameter = function(name) {
    var pos = location.search.indexOf("&" + name + "=");
    if(pos != -1) {
        var endPos = location.search.indexOf("&", pos + 1);
        if(endPos == -1) {
            return location.search.substr(pos + name.length + 2);
        }
        else {
            return location.search.substr(pos + name.length + 2, endPos - (pos + name.length + 2));
        }
    }
    return null;
};
<|endoftext|>


<commit_before>import React from 'react'

function NoResults ({query}) {
  return (

        No results found matching for '{query}'.

  )
}

export default NoResults
<commit_msg>Improve no results style<commit_after>import React from 'react'

function NoResults ({query}) {
  return (

        No results found matching for {query}.

  )
}

export default NoResults
<|endoftext|>


<commit_before>import mapValues from 'lodash/mapValues';
import { parse } from 'jsan';

export default function importState(state, { deserializeState, deserializeAction }) {
  if (!state) return undefined;
  let preloadedState;
  let nextLiftedState = parse(state);
  if (nextLiftedState.payload) {
    if (nextLiftedState.preloadedState) preloadedState = parse(nextLiftedState.preloadedState);
    nextLiftedState = parse(nextLiftedState.payload);
  }
  if (deserializeState) {
    if (typeof nextLiftedState.computedStates !== 'undefined') {
      nextLiftedState.computedStates = nextLiftedState.computedStates.map(computedState => ({
        ...computedState,
        state: deserializeState(computedState.state)
      }));
    }
    if (typeof nextLiftedState.committedState !== 'undefined') {
      nextLiftedState.committedState = deserializeState(nextLiftedState.committedState);
    }
  }
  if (deserializeAction) {
    nextLiftedState.actionsById = mapValues(nextLiftedState.actionsById, liftedAction => ({
      ...liftedAction,
      action: deserializeAction(liftedAction.action)
    }));
  }

  return { nextLiftedState, preloadedState };
}
<commit_msg>Deserialize `preloadedState`<commit_after>import mapValues from 'lodash/mapValues';
import { parse } from 'jsan';

export default function importState(state, { deserializeState, deserializeAction }) {
  if (!state) return undefined;
  let preloadedState;
  let nextLiftedState = parse(state);
  if (nextLiftedState.payload) {
    if (nextLiftedState.preloadedState) preloadedState = parse(nextLiftedState.preloadedState);
    nextLiftedState = parse(nextLiftedState.payload);
  }
  if (deserializeState) {
    if (typeof nextLiftedState.computedStates !== 'undefined') {
      nextLiftedState.computedStates = nextLiftedState.computedStates.map(computedState => ({
        ...computedState,
        state: deserializeState(computedState.state)
      }));
    }
    if (typeof nextLiftedState.committedState !== 'undefined') {
      nextLiftedState.committedState = deserializeState(nextLiftedState.committedState);
    }
    if (typeof preloadedState !== 'undefined') {
      preloadedState = deserializeState(preloadedState);
    }
  }
  if (deserializeAction) {
    nextLiftedState.actionsById = mapValues(nextLiftedState.actionsById, liftedAction => ({
      ...liftedAction,
      action: deserializeAction(liftedAction.action)
    }));
  }

  return { nextLiftedState, preloadedState };
}
<|endoftext|>


<commit_before>/*
Copyright (C) 2011 by Gregory Burlet, Alastair Porter

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

/**
 * Manages the GUI creation and interactions
 *
 * @class GUI handling
 * @param {Object} guiToggles Boolean values toggling instantiation of GUI elements
 */
Toe.View.GUI = function(prefix, fileName, rendEng, page, guiToggles) {
    var toggles = {
        sldr_bgImgOpacity: true,
        initBgImgOpacity: 0.60,
        btn_neumify: true,
        btn_delete: true,
        btn_explode: true,
        radio_mode: true,
        initMode: "edit"
    };

    $.extend(toggles, guiToggles);

    this.rendEng = rendEng;
    this.page = page;

    // set up punctum that follows the pointer in insert mode
    this.punctGlyph = this.rendEng.getGlyph("punctum");
    this.punct = this.punctGlyph.clone();

    var parentDivId = "#gui-sidebar";

    // create background image opacity slider
    if (toggles.sldr_bgImgOpacity) {
        $(parentDivId).prepend('
Background
\n
\nImage Opacity:\n<input id="sldr_bgImgOpacity" type="range" name="bgImgOpacity" min="0.0" max="1.0" step="0.05" value="' + toggles.initBgImgOpacity + '" />\n
');

        $("#sldr_bgImgOpacity").bind("change", function() {
            rendEng.canvas.backgroundImageOpacity = $(this).val();
            rendEng.repaint();
        });
    }

    // cache references which are overridden inside functions
    punctGlyph = this.punctGlyph;
    punct = this.punct;

    $("#btn_edit").bind("click", function() {
        // first remove insert options
        $(parentDivId + "> #sidebar-insert").remove();

        // unbind insert event handlers
        delete rendEng.canvas.__eventListeners["mouse:move"];
        delete rendEng.canvas.__eventListeners["mouse:up"];
        rendEng.canvas.remove(punct);
        rendEng.repaint();

        if ($(parentDivId + "> #sidebar-edit").length == 0) {
            $(parentDivId).append('

Edit
\n
\n<button id="btn_delete" class="btn"> Delete</button>\n
\n
\n
\n<button id="btn_neumify" class="btn"> Neumify</button><button id="btn_ungroup" class="btn"> Ungroup</button>

');
        }

        // handler for delete
        $("#btn_delete").bind("click.edit", function() {
            // get current canvas selection
            // check individual selection and group selections
            nids = new Array();
            var selection = rendEng.canvas.getActiveObject();
            if (selection) {
                // individual element selected
                nids.push(selection.eleRef.id);
                rendEng.canvas.remove(selection);

                // remove element from internal representation
                selection.staffRef.removeElementByRef(selection.eleRef);

                rendEng.canvas.discardActiveObject();
                rendEng.repaint();
            }
            else {
                selection = rendEng.canvas.getActiveGroup();
                if (selection) {
                    // group of neumes selected
                    for (var i = selection.objects.length-1; i >= 0; i--) {
                        nids.push(selection.objects[i].neumeRef.id);
                        rendEng.canvas.remove(selection.objects[i]);

                        // remove element from internal representation
                        selection.staffRef.removeElementByRef(selection.eleRef);
                    }
                    rendEng.canvas.discardActiveGroup();
                    rendEng.repaint();
                }
            }

            // send delete command to server to change underlying MEI
            $.post(prefix + "/edit/" + fileName + "/delete/note",  {id: nids.join(",")})
            .error(function() {
                // show alert to user
                // replace text with error message
                $(".alert > p").text("Server failed to delete note. Client and server are not syncronized.");
                $(".alert").toggleClass("fade");
            });
        });

    });

    $("#btn_insert").bind("click.insert", function() {
        // first remove edit options
        $(parentDivId + "> #sidebar-edit").remove();

        // unbind edit event handlers
        $("#btn_delete").unbind("click.edit");

        // then add insert options
        if ($(parentDivId + "> #sidebar-insert").length == 0) {
            $(parentDivId).append('

Insert
\n
\nOrnamentation:
\n<button id="btn_delete" class="btn">Dot</button>\n<button id="btn_horizepisema" class="btn"> Episema</button>\n<button id="btn_vertepisema" class="btn"> Episema</button>\n
\n');
        }

        punct.set({left: -50, top: -50, opacity: 0.60});
        rendEng.draw({static: [], modify: [punct]}, {repaint: true, selectable: false});

        // render transparent punctum at pointer location
        rendEng.canvas.observe('mouse:move', function(e) {
            var pnt = rendEng.canvas.getPointer(e.memo.e);
            punct.set({left: pnt.x - punctGlyph.centre[0]/2, top: pnt.y - punctGlyph.centre[1]/2});
            rendEng.repaint();
        });

        rendEng.canvas.observe('mouse:up', function(e) {
            var coords = {x: punct.left, y: punct.top};
            var sModel = page.getClosestStaff(coords);

            // instantiate a punctum
            var nModel = new Toe.Model.Neume();

            // calculate snapped coords
            var snapInfo = sModel.ohSnap(coords, punct.currentWidth);
            var snapCoords = snapInfo["snapCoords"];
            var pElementID = snapInfo["pElementID"];

            // update bounding box with physical position on the page
            var ulx = snapCoords.x - punct.currentWidth/2;
            var uly = snapCoords.y - punct.currentHeight/2;
            var bb = [Math.round(ulx), Math.round(uly), Math.round(ulx + punct.currentWidth), Math.round(uly + punct.currentHeight)];
            nModel.setBoundingBox(bb);

            // get pitch name and octave of snapped coords of note
            var noteInfo = sModel.calcNoteInfo(snapCoords);
            var pname = noteInfo["pname"];
            var oct = noteInfo["oct"];

            // TODO: check ornamentation toggles to add to component
            nModel.addComponent("punctum", pname, oct);

            // instantiate neume view and controller
            var nView = new Toe.View.NeumeView(rendEng);
            var nCtrl = new Toe.Ctrl.NeumeController(nModel, nView);

            // mount neume on the staff
            sModel.addNeume(nModel);

            /*
            // send insert command to server to change underlying MEI
            $.post(prefix + "/edit/" + fileName + "/new/note", {})
            .error(function() {
                // show alert to user
                // replace text with error message
                $(".alert > p").text("Server failed to insert note. Client and server are not syncronized.");
                $(".alert").toggleClass("fade");
            });*/
        });
    });

    // set active button on startup
    $("#btn_" + toggles.initMode).trigger('click');
}

Toe.View.GUI.prototype.constructor = Toe.View.GUI;
<commit_msg>Neume move (pitch shift)<commit_after>/*
Copyright (C) 2011 by Gregory Burlet, Alastair Porter

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

/**
 * Manages the GUI creation and interactions
 *
 * @class GUI handling
 * @param {Object} guiToggles Boolean values toggling instantiation of GUI elements
 */
Toe.View.GUI = function(prefix, fileName, rendEng, page, guiToggles) {
    var toggles = {
        sldr_bgImgOpacity: true,
        initBgImgOpacity: 0.60,
        btn_neumify: true,
        btn_delete: true,
        btn_explode: true,
        radio_mode: true,
        initMode: "edit"
    };

    $.extend(toggles, guiToggles);

    this.rendEng = rendEng;
    this.page = page;

    // set up punctum that follows the pointer in insert mode
    this.punctGlyph = this.rendEng.getGlyph("punctum");
    this.punct = this.punctGlyph.clone();

    var parentDivId = "#gui-sidebar";

    // create background image opacity slider
    if (toggles.sldr_bgImgOpacity) {
        $(parentDivId).prepend('
Background
\n
\nImage Opacity:\n<input id="sldr_bgImgOpacity" type="range" name="bgImgOpacity" min="0.0" max="1.0" step="0.05" value="' + toggles.initBgImgOpacity + '" />\n
');

        $("#sldr_bgImgOpacity").bind("change", function() {
            rendEng.canvas.backgroundImageOpacity = $(this).val();
            rendEng.repaint();
        });
    }

    // cache references which are overridden inside functions
    punctGlyph = this.punctGlyph;
    punct = this.punct;

    $("#btn_edit").bind("click", function() {
        // first remove insert options
        $(parentDivId + "> #sidebar-insert").remove();

        // unbind insert event handlers
        delete rendEng.canvas.__eventListeners["mouse:move"];
        delete rendEng.canvas.__eventListeners["mouse:up"];
        rendEng.canvas.remove(punct);
        rendEng.repaint();

        if ($(parentDivId + "> #sidebar-edit").length == 0) {
            $(parentDivId).append('

Edit
\n
\n<button id="btn_delete" class="btn"> Delete</button>\n
\n
\n
\n<button id="btn_neumify" class="btn"> Neumify</button><button id="btn_ungroup" class="btn"> Ungroup</button>

');
        }

        gui = this;
        rendEng.canvas.observe('mouse:down', function(e) {
            // cache pointer coordinates for mouse up
            gui.downCoords = rendEng.canvas.getPointer(e.memo.e);
        });

        rendEng.canvas.observe('mouse:up', function(e) {
            var upCoords = rendEng.canvas.getPointer(e.memo.e);
            var sModel = page.getClosestStaff(upCoords);

            // if something is selected we need to do some housekeeping
            var selection = rendEng.canvas.getActiveObject();
            if (selection) {
                var ele = selection.eleRef;

                // get delta of the mouse movement
                var delta_x = gui.downCoords.x - upCoords.x;
                var delta_y = gui.downCoords.y - upCoords.y;

                // a single neume has been dragged
                if (ele instanceof Toe.Model.Neume) {
                    // we have a neume, this is a pitch shift
                    console.log("dragging neume");

                    // get y position of first neume component
                    var nc_y = selection.staffRef.clef.y - (ele.rootDiff * selection.staffRef.delta_y/2);
                    //var finalCoords = {x: selection.left - delta_x, y: nc_y - delta_y};
                    var finalCoords = {x: selection.left, y: nc_y - delta_y};

                    // snap to staff
                    var snapInfo = sModel.ohSnap(finalCoords, selection.currentWidth, {ignoreID: ele.id});
                    var snapCoords = snapInfo["snapCoords"];
                    var pElementID = snapInfo["pElementID"];

                    var ncdelta_y = nc_y - snapCoords.y;

                    // change certain attributes of the element
                    // [ulx, uly, lrx, lry]
                    var ulx = snapCoords.x-(selection.currentWidth/2);
                    var uly = selection.top-(selection.currentHeight/2)-(finalCoords.y-snapCoords.y);
                    var bb = [ulx, uly, ulx + selection.currentWidth, uly + selection.currentHeight];
                    ele.setBoundingBox(bb);
                    //rendEng.outlineBoundingBox(bb, {fill: "blue"});

                    // derive pitch name and octave of notes in the neume on the appropriate staff
                    for (var i = 0; i < ele.components.length; i++) {
                        var noteInfo = sModel.calcNoteInfo({x: snapCoords.x, y: snapCoords.y - (sModel.delta_y/2 * ele.components[i].pitchDiff)});
                        //console.log("new pname: " + noteInfo["pname"] + ", oct: " + noteInfo["oct"]);
                        ele.components[i].setPitchInfo(noteInfo["pname"], noteInfo["oct"]);
                    }

                    // remove the old neume
                    selection.staffRef.removeElementByRef(ele);
                    rendEng.canvas.remove(selection);

                    // mount the new neume on the most appropriate staff
                    sModel.addNeume(ele);
                    console.log(ele);

                    rendEng.repaint();
                }
                else if (ele instanceof Toe.Model.Custos) {

                }
                else if (ele instanceof Toe.Model.Clef) {
                    // this is a clef
                }
                else {
                    // this is a division
                }
            }
            else {
                selection = rendEng.canvas.getActiveGroup();
                if (selection) {
                    // a group of elements have been dragged
                    console.log("dragging elementS");
                }
            }
        });

        // handler for delete
        $("#btn_delete").bind("click.edit", function() {
            // get current canvas selection
            // check individual selection and group selections
            nids = new Array();
            var selection = rendEng.canvas.getActiveObject();
            if (selection) {
                // individual element selected
                nids.push(selection.eleRef.id);
                rendEng.canvas.remove(selection);

                // remove element from internal representation
                selection.staffRef.removeElementByRef(selection.eleRef);

                rendEng.canvas.discardActiveObject();
                rendEng.repaint();
            }
            else {
                selection = rendEng.canvas.getActiveGroup();
                if (selection) {
                    // group of neumes selected
                    for (var i = selection.objects.length-1; i >= 0; i--) {
                        nids.push(selection.objects[i].neumeRef.id);
                        rendEng.canvas.remove(selection.objects[i]);

                        // remove element from internal representation
                        selection.staffRef.removeElementByRef(selection.eleRef);
                    }
                    rendEng.canvas.discardActiveGroup();
                    rendEng.repaint();
                }
            }

            // send delete command to server to change underlying MEI
            $.post(prefix + "/edit/" + fileName + "/delete/note",  {id: nids.join(",")})
            .error(function() {
                // show alert to user
                // replace text with error message
                $(".alert > p").text("Server failed to delete note. Client and server are not syncronized.");
                $(".alert").toggleClass("fade");
            });
        });

    });

    $("#btn_insert").bind("click.insert", function() {
        // first remove edit options
        $(parentDivId + "> #sidebar-edit").remove();

        // unbind edit event handlers
        $("#btn_delete").unbind("click.edit");

        // unbind move event handlers
        delete rendEng.canvas.__eventListeners["mouse:down"];
        delete rendEng.canvas.__eventListeners["mouse:up"];

        // then add insert options
        if ($(parentDivId + "> #sidebar-insert").length == 0) {
            $(parentDivId).append('

Insert
\n
\nOrnamentation:
\n<button id="btn_delete" class="btn">Dot</button>\n<button id="btn_horizepisema" class="btn"> Episema</button>\n<button id="btn_vertepisema" class="btn"> Episema</button>\n
\n');
        }

        // put the punctum off the screen for now
        punct.set({left: -50, top: -50, opacity: 0.60});
        rendEng.draw({static: [], modify: [punct]}, {repaint: true, selectable: false});

        // render transparent punctum at pointer location
        rendEng.canvas.observe('mouse:move', function(e) {
            var pnt = rendEng.canvas.getPointer(e.memo.e);
            punct.set({left: pnt.x - punctGlyph.centre[0]/2, top: pnt.y - punctGlyph.centre[1]/2});
            rendEng.repaint();
        });

        rendEng.canvas.observe('mouse:up', function(e) {
            var coords = {x: punct.left, y: punct.top};
            var sModel = page.getClosestStaff(coords);

            // instantiate a punctum
            var nModel = new Toe.Model.Neume();

            // calculate snapped coords
            var snapInfo = sModel.ohSnap(coords, punct.currentWidth);
            var snapCoords = snapInfo["snapCoords"];
            var pElementID = snapInfo["pElementID"];

            // update bounding box with physical position on the page
            var ulx = snapCoords.x - punct.currentWidth/2;
            var uly = snapCoords.y - punct.currentHeight/2;
            var bb = [Math.round(ulx), Math.round(uly), Math.round(ulx + punct.currentWidth), Math.round(uly + punct.currentHeight)];
            nModel.setBoundingBox(bb);

            // get pitch name and octave of snapped coords of note
            var noteInfo = sModel.calcNoteInfo(snapCoords);
            var pname = noteInfo["pname"];
            var oct = noteInfo["oct"];

            // TODO: check ornamentation toggles to add to component
            nModel.addComponent("punctum", pname, oct);

            // instantiate neume view and controller
            var nView = new Toe.View.NeumeView(rendEng);
            var nCtrl = new Toe.Ctrl.NeumeController(nModel, nView);

            // mount neume on the staff
            sModel.addNeume(nModel);

            /*
            // send insert command to server to change underlying MEI
            $.post(prefix + "/edit/" + fileName + "/new/note", {})
            .error(function() {
                // show alert to user
                // replace text with error message
                $(".alert > p").text("Server failed to insert note. Client and server are not syncronized.");
                $(".alert").toggleClass("fade");
            });*/
        });
    });

    // set active button on startup
    $("#btn_" + toggles.initMode).trigger('click');
}

Toe.View.GUI.prototype.constructor = Toe.View.GUI;
<|endoftext|>


<commit_before>import List from "./list"
import Redis from "./redis"

import Lobby from "./lobby"
import Game from "./game"

import Revision from "./revision"
import Capture from "./capture"

import { POSITION, RESULT } from "~/share/constants/game_update_types"

const UNIVERSE_CHANNEL = "universe"
const GAME_CREATION_CHANNEL = "universe:game"
const USERS_KEY = "universe:users"

export { UNIVERSE_CHANNEL, GAME_CREATION_CHANNEL }

export default class Universe {
  constructor(opts = { bind: true }) {
    this.redis = new Redis()

    // TODO: restore state from redis
    this.redis.set(USERS_KEY, 0)

    this.lobby = new Lobby(Game)
    this.games = new List("games")

    if (opts.bind) {
      Game.on("create", this.handleGameCreationWrapper.bind(this))
      Revision.on("create", this.handleRevisionCreationWrapper.bind(this))
    }
  }

  async addSocket() {
    await this.redis.incr(USERS_KEY)
    await this.publish()
  }

  async removeSocket({ client }) {
    await this.redis.decr(USERS_KEY)

    // TODO: grace period
    if (client.gameUUID) {
      await Revision.forfeit(client.gameUUID, client.user)
      await this.games.remove(client.gameUUID)
    }

    await this.publish()
  }

  async play(user) {
    await this.lobby.push(user)
  }

  handleGameCreationWrapper(game) {
    process.nextTick(this.handleGameCreation.bind(this))
  }

  async handleGameCreation(game) {
    await this.games.push(game.get("uuid"))

    await game.serializePrepare()
    const serializedGame = game.serialize()

    await this.redis.publish(
      GAME_CREATION_CHANNEL,
      JSON.stringify(serializedGame)
    )

    await this.publish("test")
  }

  async users() {
    return await this.redis.get(USERS_KEY)
  }

  async serialize() {
    return {
      users: parseInt(await this.users(), 10),
      games: parseInt(await this.games.length(), 10)
    }
  }

  async publish() {
    await this.redis.publish(
      UNIVERSE_CHANNEL,
      JSON.stringify(await this.serialize())
    )
  }

  publishPosition(uuid, position) {
    this.redis.publish(uuid, JSON.stringify({
      type: POSITION,
      payload: position.serialize()
    }))
  }

  publishResult(uuid, result) {
    this.games.remove(uuid)

    this.redis.publish(uuid, JSON.stringify({
      type: RESULT,
      payload: result
    }))
  }

  async publishCapture(game, color, piece) {
    const { uuid, position } = await new Capture(this).process(game, color, piece)

    this.publishPosition(uuid, position)
  }
}
<commit_msg>exit early on nil game to fix testing issues<commit_after>import { isNil } from "ramda"

import List from "./list"
import Redis from "./redis"

import Lobby from "./lobby"
import Game from "./game"

import Revision from "./revision"
import Capture from "./capture"

import { POSITION, RESULT } from "~/share/constants/game_update_types"

const UNIVERSE_CHANNEL = "universe"
const GAME_CREATION_CHANNEL = "universe:game"
const USERS_KEY = "universe:users"

export { UNIVERSE_CHANNEL, GAME_CREATION_CHANNEL }

export default class Universe {
  constructor(opts = { bind: true }) {
    this.redis = new Redis()

    // TODO: restore state from redis
    this.redis.set(USERS_KEY, 0)

    this.lobby = new Lobby(Game)
    this.games = new List("games")

    if (opts.bind) {
      Game.on("create", this.handleGameCreationWrapper.bind(this))
      Revision.on("create", this.handleRevisionCreationWrapper.bind(this))
    }
  }

  async addSocket() {
    await this.redis.incr(USERS_KEY)
    await this.publish()
  }

  async removeSocket({ client }) {
    await this.redis.decr(USERS_KEY)

    // TODO: grace period
    if (client.gameUUID) {
      await Revision.forfeit(client.gameUUID, client.user)
      await this.games.remove(client.gameUUID)
    }

    await this.publish()
  }

  async play(user) {
    await this.lobby.push(user)
  }

  handleGameCreationWrapper(game) {
    process.nextTick(this.handleGameCreation.bind(this))
  }

  async handleGameCreation(game) {
    if (isNil(game)) {
      return
    }

    await this.games.push(game.get("uuid"))

    await game.serializePrepare()
    const serializedGame = game.serialize()

    await this.redis.publish(
      GAME_CREATION_CHANNEL,
      JSON.stringify(serializedGame)
    )

    await this.publish("test")
  }

  async users() {
    return await this.redis.get(USERS_KEY)
  }

  async serialize() {
    return {
      users: parseInt(await this.users(), 10),
      games: parseInt(await this.games.length(), 10)
    }
  }

  async publish() {
    await this.redis.publish(
      UNIVERSE_CHANNEL,
      JSON.stringify(await this.serialize())
    )
  }

  publishPosition(uuid, position) {
    this.redis.publish(uuid, JSON.stringify({
      type: POSITION,
      payload: position.serialize()
    }))
  }

  publishResult(uuid, result) {
    this.games.remove(uuid)

    this.redis.publish(uuid, JSON.stringify({
      type: RESULT,
      payload: result
    }))
  }

  async publishCapture(game, color, piece) {
    const { uuid, position } = await new Capture(this).process(game, color, piece)

    this.publishPosition(uuid, position)
  }
}
<|endoftext|>