<commit_before>var Article = require("lens-article");
var ArticleRenderer = Article.Renderer;
var nodes = require("lens-article/nodes");
var $$ = require("substance-application").$$;

// Enhances the default node views
// --------
//
// .resource-header
//   .name
//   .reference-count
//   .type.image

var addResourceHeader = function(nodeView) {
  var node = nodeView.node;
  var typeDescr = node.constructor.description;
  var frag = document.createDocumentFragment();

  var children = [
    $$('a.name', {
      href: "#",
      text: node.label ,
      "sbs-click": "toggleResource("+node.id+")"
    }),
    // $$('.reference-count', {text: "cited x times"}),
    // $$('.type.figure.publication', {text: typeDescr.name}),
  ];

  var config = node.constructor.config;
  if (config && config.zoomable) {
    children.push($$('a.toggle-fullscreen', {
      "href": "#",
      "html": "",
      "sbs-click": "toggleFullscreen("+node.id+")"
    }));
  }

  var resourceHeader = $$('.resource-header', {
    children: children
  });
  nodeView.el.insertBefore(resourceHeader, nodeView.content);
  return frag;
};

// Renders the content view
// --------
//
// Provides focus toggles by overriding the default NodeView's renderer

var ResourceRenderer = function(doc) {
  ArticleRenderer.call(this, doc);
};

ResourceRenderer.Prototype = function() {

  // Render
  // --------
  //
  // Renders the all node views and passes the addFocusControls
  // enhancer to the render method

  this.render = function() {
    var frag = document.createDocumentFragment();

    var docNodes = this.doc.getNodes();
    _.each(docNodes, function(n) {
      var nodeView = this.nodes[n.id];
      frag.appendChild(nodeView.render().el);
      addResourceHeader(nodeView);
    }, this);

    return frag;
  }
};

ResourceRenderer.Prototype.prototype = Article.Renderer.prototype;
ResourceRenderer.prototype = new ResourceRenderer.Prototype();
ResourceRenderer.prototype.constructor = ResourceRenderer;

module.exports = ResourceRenderer;
<commit_msg>Use virtual header property for resource headers.<commit_after>var Article = require("lens-article");
var ArticleRenderer = Article.Renderer;
var nodes = require("lens-article/nodes");
var $$ = require("substance-application").$$;

// Enhances the default node views
// --------
//
// .resource-header
//   .name
//   .reference-count
//   .type.image

var addResourceHeader = function(nodeView) {
  var node = nodeView.node;
  var typeDescr = node.constructor.description;
  var frag = document.createDocumentFragment();

  var children = [
    $$('a.name', {
      href: "#",
      text: node.header ,
      "sbs-click": "toggleResource("+node.id+")"
    }),
    // $$('.reference-count', {text: "cited x times"}),
    // $$('.type.figure.publication', {text: typeDescr.name}),
  ];

  var config = node.constructor.config;
  if (config && config.zoomable) {
    children.push($$('a.toggle-fullscreen', {
      "href": "#",
      "html": "",
      "sbs-click": "toggleFullscreen("+node.id+")"
    }));
  }

  var resourceHeader = $$('.resource-header', {
    children: children
  });
  nodeView.el.insertBefore(resourceHeader, nodeView.content);
  return frag;
};

// Renders the content view
// --------
//
// Provides focus toggles by overriding the default NodeView's renderer

var ResourceRenderer = function(doc) {
  ArticleRenderer.call(this, doc);
};

ResourceRenderer.Prototype = function() {

  // Render
  // --------
  //
  // Renders the all node views and passes the addFocusControls
  // enhancer to the render method

  this.render = function() {
    var frag = document.createDocumentFragment();

    var docNodes = this.doc.getNodes();
    _.each(docNodes, function(n) {
      var nodeView = this.nodes[n.id];
      frag.appendChild(nodeView.render().el);
      addResourceHeader(nodeView);
    }, this);

    return frag;
  }
};

ResourceRenderer.Prototype.prototype = Article.Renderer.prototype;
ResourceRenderer.prototype = new ResourceRenderer.Prototype();
ResourceRenderer.prototype.constructor = ResourceRenderer;

module.exports = ResourceRenderer;
<|endoftext|>


<commit_before>/*
 * This file is part of gorilla-repl. Copyright (C) 2014-, Jony Hudson.
 *
 * gorilla-repl is licenced to you under the MIT licence. See the file LICENCE.txt for full details.
 */

// The viewmodel for the 'palette' user interface component. This is used for a few things in Gorilla's UI.
// There is one palette viewmodel held by the app, corresponding to the palette div in the markup. It is reused each
// time it is shown. The only function that you should need to use is the `show` function, which brings up the palette.

var palette = function () {

    var self = {};

    self.shown = ko.observable(false);
    self.caption = ko.observable("Choose a command:");
    // These are the items that the palette was shown with - we keep these because when we filter ...
    self.allItems = [];
    // ... we will only show a subset of the items, held in the following observable array.
    self.items = ko.observableArray();
    self.highlight = ko.observable(1);
    // this is used to control/read the focus state of the text input. This is the only part of the palette that will
    // take the focus, and is focused when the palette appears.
    self.focused = ko.observable(false);
    // the text the user has put in the filter box
    self.filterText = ko.observable("");
    self.filterText.subscribe(function (nv) {self.updateFilter(nv)});

    // This function shows the palette with the given items. It's the only function on the palette that you should need
    // to call. The `items` should be an array of objects, with each object having a `desc` property, which is an HTML
    // string that will be shown to the user, a `text` property which is what the item will be filtered against (and so
    // had better match up with how the user reads the `desc` property, and an `action` property which a function that
    // will be called if that item is selected.
    self.show = function (caption, items) {
        self.caption(caption);
        self.allItems = items;
        self.updateItems(items);
        self.filterText("");
        self.shown(true);
        self.focused(true);
        self.scrollToNth(0,true);
    };

    self.hide = function () {
        self.shown(false);
    };

    self.updateItems = function (newItems) {
        self.highlight(0);
        self.items.removeAll();
        self.items.push.apply(self.items, newItems);
    };

    self.updateFilter = function (filterText) {
        var filteredItems = self.allItems.filter(
            function (i) {
                // the idea to do it this way comes from http://stackoverflow.com/a/24570566
                var re = new RegExp(filterText.toLowerCase().split('').join('.*'));
                return i.text.toLowerCase().match(re);
            });
        self.updateItems(filteredItems);
    };

    self.moveSelectionDown = function () {
        var curPos = self.highlight();
        var newPos;
        if (curPos < (self.items().length - 1)) newPos = curPos + 1;
        else newPos = 0;
        self.highlight(newPos);
        self.scrollToNth(newPos, false);
    };

    self.moveSelectionUp = function () {
        var curPos = self.highlight();
        var newPos;
        if (curPos > 0) newPos = curPos - 1;
        else newPos = self.items().length - 1;
        self.highlight(newPos);
        self.scrollToNth(newPos, false);
    };

    self.scrollToNth = function (n, top) {
        var el = document.getElementById('palette-item-' + n);
        if (el) {
            // This isn't availble cross-browser, but it's much better when it is there
            if (el.scrollIntoViewIfNeeded) el.scrollIntoViewIfNeeded(top);
            // This is a bit janky at the moment, but it will do.
            else (el.scrollIntoView(top));
        }
    };

    self.handleItemClick = function (item) {
        item.action();
        self.hide();
    };

    self.handleOverlayClick = function () {
        self.hide();
    };

    self.handleKeyPress = function (d, event) {
        // up
        if (event.keyCode === 38) {
            self.moveSelectionUp();
            return false;
        }
        // down
        if (event.keyCode === 40) {
            self.moveSelectionDown();
            return false;
        }
        // esc
        if (event.keyCode === 27) {
            self.hide();
            return false;
        }
        // enter
        if (event.keyCode === 13) {
            var item = self.items()[self.highlight()];
            if (item) item.action();
            self.hide();
            return false;
        }
        // Pass through keypresses to the default handler.
        return true;
    };

    return self;
};<commit_msg>Improve comments.<commit_after>/*
 * This file is part of gorilla-repl. Copyright (C) 2014-, Jony Hudson.
 *
 * gorilla-repl is licenced to you under the MIT licence. See the file LICENCE.txt for full details.
 */

// The viewmodel for the 'palette' user interface component. This is used for a few things in Gorilla's UI.
// There is one palette viewmodel held by the app, corresponding to the palette div in the markup. It is reused each
// time it is shown. The only function that you should need to use is the `show` function, which brings up the palette.

var palette = function () {

    var self = {};

    self.shown = ko.observable(false);
    self.caption = ko.observable("Choose a command:");
    // These are the items that the palette was shown with - we keep these because when we filter ...
    self.allItems = [];
    // ... we will only show a subset of the items, held in the following observable array.
    self.items = ko.observableArray();
    self.highlight = ko.observable(1);
    // this is used to control/read the focus state of the text input. The text input is the only part of the palette
    // that will take the focus, and is focused when the palette appears.
    self.focused = ko.observable(false);
    // the text the user has put in the filter box
    self.filterText = ko.observable("");
    self.filterText.subscribe(function (nv) {self.updateFilter(nv)});

    // This function shows the palette with the given items. It's the only function on the palette that you should need
    // to call. The `items` should be an array of objects, with each object having a `desc` property, which is an HTML
    // string that will be shown to the user, a `text` property which is what the item will be filtered against (and so
    // had better match up with how the user reads the `desc` property, and an `action` property which a function that
    // will be called if that item is selected.
    self.show = function (caption, items) {
        self.caption(caption);
        self.allItems = items;
        self.updateItems(items);
        self.filterText("");
        self.shown(true);
        self.focused(true);
        self.scrollToNth(0,true);
    };

    self.hide = function () {
        self.shown(false);
    };

    // updates the list of _visible_ items.
    self.updateItems = function (newItems) {
        self.highlight(0);
        self.items.removeAll();
        self.items.push.apply(self.items, newItems);
    };

    // update the list of visible items, based on the filter text. Uses that matching that I don't really know the name
    // of that you get in IntelliJ/Sublime/Atom etc where as long as the chars in the filter text appear, in that order,
    // in the string, then it matches.
    self.updateFilter = function (filterText) {
        var filteredItems = self.allItems.filter(
            function (i) {
                // the idea to do it this way comes from http://stackoverflow.com/a/24570566
                var re = new RegExp(filterText.toLowerCase().split('').join('.*'));
                return i.text.toLowerCase().match(re);
            });
        self.updateItems(filteredItems);
    };

    // This method and the next move the selected item highlight
    self.moveSelectionDown = function () {
        var curPos = self.highlight();
        var newPos;
        if (curPos < (self.items().length - 1)) newPos = curPos + 1;
        else newPos = 0;
        self.highlight(newPos);
        self.scrollToNth(newPos, false);
    };

    self.moveSelectionUp = function () {
        var curPos = self.highlight();
        var newPos;
        if (curPos > 0) newPos = curPos - 1;
        else newPos = self.items().length - 1;
        self.highlight(newPos);
        self.scrollToNth(newPos, false);
    };

    self.scrollToNth = function (n, top) {
        var el = document.getElementById('palette-item-' + n);
        if (el) {
            // This isn't availble cross-browser, but it's much better when it is there
            if (el.scrollIntoViewIfNeeded) el.scrollIntoViewIfNeeded(top);
            // This is a bit janky at the moment, but it will do.
            else (el.scrollIntoView(top));
        }
    };

    self.handleItemClick = function (item) {
        item.action();
        self.hide();
    };

    // The overlay is a viewport sized div that sits behind the palette, but over everything else.
    self.handleOverlayClick = function () {
        self.hide();
    };

    // This is bound to keypresses on the text input.
    self.handleKeyPress = function (d, event) {
        // up
        if (event.keyCode === 38) {
            self.moveSelectionUp();
            return false;
        }
        // down
        if (event.keyCode === 40) {
            self.moveSelectionDown();
            return false;
        }
        // esc
        if (event.keyCode === 27) {
            self.hide();
            return false;
        }
        // enter
        if (event.keyCode === 13) {
            var item = self.items()[self.highlight()];
            if (item) item.action();
            self.hide();
            return false;
        }
        // Pass through keypresses to the default handler.
        return true;
    };

    return self;
};<|endoftext|>


<commit_before>import storage from './storage';
import * as utils from './utils';
import * as xhr from './xhr';
import i18n from './i18n';
import friendsApi from './lichess/friends';
import challengesApi from './lichess/challenges';
import session from './session';
import work from 'webworkify';
import socketWorker from './socketWorker';
import m from 'mithril';

const worker = work(socketWorker);

var socketHandlers;
var errorDetected = false;
var connectedWS = true;

var alreadyWarned = false;
var redrawOnDisconnectedTimeoutID;
var proxyFailTimeoutID;
const proxyFailMsg = 'The connection to lichess server has failed. If the problem is persistent this may be caused by proxy or network issues. In that case, we\'re sorry: lichess online features such as games, connected friends or challenges won\'t work.';

const defaultHandlers = {
  following_onlines: data => utils.autoredraw(utils.partialf(friendsApi.set, data)),
  following_enters: name => utils.autoredraw(utils.partialf(friendsApi.add, name)),
  following_leaves: name => utils.autoredraw(utils.partialf(friendsApi.remove, name)),
  challenges: data => {
    challengesApi.set(data);
    m.redraw();
  }
};

function createGame(url, version, handlers, gameUrl, userTv) {
  errorDetected = false;
  socketHandlers = {
    onError: function() {
      // we can't get socket error, so we send an xhr to test whether the
      // rejection is an authorization issue
      if (!errorDetected) {
        // just to be sure that we don't send an xhr every second when the
        // websocket is trying to reconnect
        errorDetected = true;
        xhr.game(gameUrl.substring(1)).then(function() {}, function(err) {
          if (err.status === 401) {
            window.plugins.toast.show(i18n('unauthorizedError'), 'short', 'center');
            m.route('/');
          }
        });
      }
    },
    events: Object.assign({}, defaultHandlers, handlers)
  };
  const opts = {
    options: {
      name: 'game',
      debug: false,
      sendOnOpen: 'following_onlines',
      registeredEvents: Object.keys(socketHandlers.events)
    }
  };
  if (userTv) opts.params = { userTv };
  worker.postMessage({ topic: 'create', payload: {
    clientId: utils.lichessSri,
    socketEndPoint: window.lichess.socketEndPoint,
    url,
    version,
    opts
  }});
}

function createTournament(tournamentVersion, tournamentId, handlers) {
  let url = '/tournament/' + tournamentId + '/socket/v1';
  socketHandlers = {
    events: Object.assign({}, defaultHandlers, handlers)
  };
  const opts = {
    options: {
      name: 'tournament',
      debug: false,
      ignoreUnknownMessages: true,
      pingDelay: 2000,
      sendOnOpen: 'following_onlines',
      registeredEvents: Object.keys(socketHandlers.events)
    }
  };
  worker.postMessage({ topic: 'create', payload: {
    clientId: utils.lichessSri,
    socketEndPoint: window.lichess.socketEndPoint,
    url,
    tournamentVersion,
    opts
  }});
}

function createChallenge(id, version, onOpen, handlers) {
  socketHandlers = {
    onOpen,
    events: Object.assign({}, defaultHandlers, handlers)
  };
  const url = `/challenge/${id}/socket/v${version}`;
  const opts = {
    options: {
      name: 'challenge',
      debug: false,
      ignoreUnknownMessages: true,
      pingDelay: 2000,
      sendOnOpen: 'following_onlines'
    },
    events: Object.assign({}, defaultHandlers, handlers)
  };
  worker.postMessage({ topic: 'create', payload: {
    clientId: utils.lichessSri,
    socketEndPoint: window.lichess.socketEndPoint,
    url,
    version,
    opts
  }});
}

function createLobby(lobbyVersion, onOpen, handlers) {
  socketHandlers = {
    onOpen,
    events: Object.assign({}, defaultHandlers, handlers)
  };
  const opts = {
    options: {
      name: 'lobby',
      debug: false,
      pingDelay: 2000,
      sendOnOpen: 'following_onlines',
      registeredEvents: Object.keys(socketHandlers.events)
    }
  };
  worker.postMessage({ topic: 'create', payload: {
    clientId: utils.lichessSri,
    socketEndPoint: window.lichess.socketEndPoint,
    url: '/lobby/socket/v1',
    version: lobbyVersion,
    opts
  }});
}

function createDefault() {
  // default socket is useless when anon.
  if (utils.hasNetwork() && session.isConnected()) {
    socketHandlers = {
      events: defaultHandlers
    };
    const opts = {
      options: {
        name: 'default',
        debug: false,
        pingDelay: 2000,
        sendOnOpen: 'following_onlines',
        registeredEvents: Object.keys(socketHandlers.events)
      }
    };
    worker.postMessage({ topic: 'create', payload: {
      clientId: utils.lichessSri,
      socketEndPoint: window.lichess.socketEndPoint,
      url: '/socket',
      version: 0,
      opts
    }});
  }
}

function redirectToGame(obj) {
  let url;
  if (typeof obj === 'string') url = obj;
  else {
    url = obj.url;
    if (obj.cookie) {
      const domain = document.domain.replace(/^.+(\.[^\.]+\.[^\.]+)$/, '$1');
      const cookie = [
        encodeURIComponent(obj.cookie.name) + '=' + obj.cookie.value,
        '; max-age=' + obj.cookie.maxAge,
        '; path=/',
        '; domain=' + domain
        ].join('');
        document.cookie = cookie;
    }
    m.route('/game' + url);
  }
}

function onConnected() {
  const wasOff = !connectedWS;
  connectedWS = true;
  clearTimeout(proxyFailTimeoutID);
  clearTimeout(redrawOnDisconnectedTimeoutID);
  if (wasOff) m.redraw();
}

function onDisconnected() {
  const wasOn = connectedWS;
  connectedWS = false;
  if (wasOn) redrawOnDisconnectedTimeoutID = setTimeout(function() {
    m.redraw();
  }, 2000);
  if (wasOn && !alreadyWarned && !storage.get('donotshowproxyfailwarning')) proxyFailTimeoutID = setTimeout(() => {
    // check if disconnection lasts, it could mean a proxy prevents
    // establishing a tunnel
    if (utils.hasNetwork() && !connectedWS) {
      alreadyWarned = true;
      window.navigator.notification.alert(proxyFailMsg, function() {
        storage.set('donotshowproxyfailwarning', true);
      });
    }
  }, 20000);
}

document.addEventListener('deviceready', () => {
  document.addEventListener('pause', () => clearTimeout(proxyFailTimeoutID), false);
}, false);

worker.addEventListener('message', function(msg) {
  switch (msg.data.topic) {
    case 'onOpen':
      if (socketHandlers.onOpen) socketHandlers.onOpen();
      break;
    case 'disconnected':
      onDisconnected();
      break;
    case 'connected':
      onConnected();
      break;
    case 'onError':
      if (socketHandlers.onError) socketHandlers.onError();
      break;
    case 'handle':
      var h = socketHandlers.events[msg.data.payload.t];
      if (h) h(msg.data.payload.d || null, msg.data.payload);
      break;
  }
});

export default {
  createGame,
  createChallenge,
  createLobby,
  createTournament,
  createDefault,
  redirectToGame,
  setVersion(version) {
    worker.postMessage({ topic: 'setVersion', payload: version });
  },
  getAverageLag(callback) {
    utils.askWorker(worker, { topic: 'averageLag' }, callback);
  },
  send(type, data, opts) {
    worker.postMessage({ topic: 'send', payload: [type, data, opts] });
  },
  connect() {
    worker.postMessage({ topic: 'connect' });
  },
  disconnect() {
    worker.postMessage({ topic: 'disconnect' });
  },
  isConnected() {
    return connectedWS;
  },
  destroy() {
    worker.postMessage({ topic: 'destroy' });
  },
  terminate() {
    worker.terminate();
  }
};
<commit_msg>More clever online friends redraw<commit_after>import storage from './storage';
import difference from 'lodash/difference';
import * as utils from './utils';
import * as xhr from './xhr';
import i18n from './i18n';
import friendsApi from './lichess/friends';
import challengesApi from './lichess/challenges';
import session from './session';
import work from 'webworkify';
import socketWorker from './socketWorker';
import m from 'mithril';

const worker = work(socketWorker);

var socketHandlers;
var errorDetected = false;
var connectedWS = true;

var alreadyWarned = false;
var redrawOnDisconnectedTimeoutID;
var proxyFailTimeoutID;
const proxyFailMsg = 'The connection to lichess server has failed. If the problem is persistent this may be caused by proxy or network issues. In that case, we\'re sorry: lichess online features such as games, connected friends or challenges won\'t work.';

const defaultHandlers = {
  following_onlines: handleFollowingOnline,
  following_enters: name => utils.autoredraw(utils.partialf(friendsApi.add, name)),
  following_leaves: name => utils.autoredraw(utils.partialf(friendsApi.remove, name)),
  challenges: data => {
    challengesApi.set(data);
    m.redraw();
  }
};

function handleFollowingOnline(data) {
  if (difference(data, friendsApi.list()).length > 0) {
    friendsApi.set(data);
    m.redraw();
  }
}

function createGame(url, version, handlers, gameUrl, userTv) {
  errorDetected = false;
  socketHandlers = {
    onError: function() {
      // we can't get socket error, so we send an xhr to test whether the
      // rejection is an authorization issue
      if (!errorDetected) {
        // just to be sure that we don't send an xhr every second when the
        // websocket is trying to reconnect
        errorDetected = true;
        xhr.game(gameUrl.substring(1)).then(function() {}, function(err) {
          if (err.status === 401) {
            window.plugins.toast.show(i18n('unauthorizedError'), 'short', 'center');
            m.route('/');
          }
        });
      }
    },
    events: Object.assign({}, defaultHandlers, handlers)
  };
  const opts = {
    options: {
      name: 'game',
      debug: false,
      sendOnOpen: 'following_onlines',
      registeredEvents: Object.keys(socketHandlers.events)
    }
  };
  if (userTv) opts.params = { userTv };
  worker.postMessage({ topic: 'create', payload: {
    clientId: utils.lichessSri,
    socketEndPoint: window.lichess.socketEndPoint,
    url,
    version,
    opts
  }});
}

function createTournament(tournamentVersion, tournamentId, handlers) {
  let url = '/tournament/' + tournamentId + '/socket/v1';
  socketHandlers = {
    events: Object.assign({}, defaultHandlers, handlers)
  };
  const opts = {
    options: {
      name: 'tournament',
      debug: false,
      ignoreUnknownMessages: true,
      pingDelay: 2000,
      sendOnOpen: 'following_onlines',
      registeredEvents: Object.keys(socketHandlers.events)
    }
  };
  worker.postMessage({ topic: 'create', payload: {
    clientId: utils.lichessSri,
    socketEndPoint: window.lichess.socketEndPoint,
    url,
    tournamentVersion,
    opts
  }});
}

function createChallenge(id, version, onOpen, handlers) {
  socketHandlers = {
    onOpen,
    events: Object.assign({}, defaultHandlers, handlers)
  };
  const url = `/challenge/${id}/socket/v${version}`;
  const opts = {
    options: {
      name: 'challenge',
      debug: false,
      ignoreUnknownMessages: true,
      pingDelay: 2000,
      sendOnOpen: 'following_onlines'
    },
    events: Object.assign({}, defaultHandlers, handlers)
  };
  worker.postMessage({ topic: 'create', payload: {
    clientId: utils.lichessSri,
    socketEndPoint: window.lichess.socketEndPoint,
    url,
    version,
    opts
  }});
}

function createLobby(lobbyVersion, onOpen, handlers) {
  socketHandlers = {
    onOpen,
    events: Object.assign({}, defaultHandlers, handlers)
  };
  const opts = {
    options: {
      name: 'lobby',
      debug: false,
      pingDelay: 2000,
      sendOnOpen: 'following_onlines',
      registeredEvents: Object.keys(socketHandlers.events)
    }
  };
  worker.postMessage({ topic: 'create', payload: {
    clientId: utils.lichessSri,
    socketEndPoint: window.lichess.socketEndPoint,
    url: '/lobby/socket/v1',
    version: lobbyVersion,
    opts
  }});
}

function createDefault() {
  // default socket is useless when anon.
  if (utils.hasNetwork() && session.isConnected()) {
    socketHandlers = {
      events: defaultHandlers
    };
    const opts = {
      options: {
        name: 'default',
        debug: false,
        pingDelay: 2000,
        sendOnOpen: 'following_onlines',
        registeredEvents: Object.keys(socketHandlers.events)
      }
    };
    worker.postMessage({ topic: 'create', payload: {
      clientId: utils.lichessSri,
      socketEndPoint: window.lichess.socketEndPoint,
      url: '/socket',
      version: 0,
      opts
    }});
  }
}

function redirectToGame(obj) {
  let url;
  if (typeof obj === 'string') url = obj;
  else {
    url = obj.url;
    if (obj.cookie) {
      const domain = document.domain.replace(/^.+(\.[^\.]+\.[^\.]+)$/, '$1');
      const cookie = [
        encodeURIComponent(obj.cookie.name) + '=' + obj.cookie.value,
        '; max-age=' + obj.cookie.maxAge,
        '; path=/',
        '; domain=' + domain
        ].join('');
        document.cookie = cookie;
    }
    m.route('/game' + url);
  }
}

function onConnected() {
  const wasOff = !connectedWS;
  connectedWS = true;
  clearTimeout(proxyFailTimeoutID);
  clearTimeout(redrawOnDisconnectedTimeoutID);
  if (wasOff) m.redraw();
}

function onDisconnected() {
  const wasOn = connectedWS;
  connectedWS = false;
  if (wasOn) redrawOnDisconnectedTimeoutID = setTimeout(function() {
    m.redraw();
  }, 2000);
  if (wasOn && !alreadyWarned && !storage.get('donotshowproxyfailwarning')) proxyFailTimeoutID = setTimeout(() => {
    // check if disconnection lasts, it could mean a proxy prevents
    // establishing a tunnel
    if (utils.hasNetwork() && !connectedWS) {
      alreadyWarned = true;
      window.navigator.notification.alert(proxyFailMsg, function() {
        storage.set('donotshowproxyfailwarning', true);
      });
    }
  }, 20000);
}

document.addEventListener('deviceready', () => {
  document.addEventListener('pause', () => clearTimeout(proxyFailTimeoutID), false);
}, false);

worker.addEventListener('message', function(msg) {
  switch (msg.data.topic) {
    case 'onOpen':
      if (socketHandlers.onOpen) socketHandlers.onOpen();
      break;
    case 'disconnected':
      onDisconnected();
      break;
    case 'connected':
      onConnected();
      break;
    case 'onError':
      if (socketHandlers.onError) socketHandlers.onError();
      break;
    case 'handle':
      var h = socketHandlers.events[msg.data.payload.t];
      if (h) h(msg.data.payload.d || null, msg.data.payload);
      break;
  }
});

export default {
  createGame,
  createChallenge,
  createLobby,
  createTournament,
  createDefault,
  redirectToGame,
  setVersion(version) {
    worker.postMessage({ topic: 'setVersion', payload: version });
  },
  getAverageLag(callback) {
    utils.askWorker(worker, { topic: 'averageLag' }, callback);
  },
  send(type, data, opts) {
    worker.postMessage({ topic: 'send', payload: [type, data, opts] });
  },
  connect() {
    worker.postMessage({ topic: 'connect' });
  },
  disconnect() {
    worker.postMessage({ topic: 'disconnect' });
  },
  isConnected() {
    return connectedWS;
  },
  destroy() {
    worker.postMessage({ topic: 'destroy' });
  },
  terminate() {
    worker.terminate();
  }
};
<|endoftext|>


<commit_before>define(['qlik', 'ng!$q'], function(qlik, $q) {

    var currApp = qlik.currApp(this);

    function getAppList(){
        var defer = $q.defer();

        qlik.getGlobal().getAppList(function(list){
            var appList = list
                .filter(function(app){ return app.qDocId !== currApp.id })
                .map(function(app){
                    return { label: app.qTitle, value: app.qDocId }
                });
            return defer.resolve(appList);
        });
        return defer.promise;
    }

    function getSheetsList(appId){
        var defer = $q.defer();
        var selectedApp = qlik.openApp(appId);

        selectedApp.getAppObjectList('sheet', function(reply){
            var sheets = reply.qAppObjectList.qItems.map(function(o){
                return { label: o.qData.title, value: o.qInfo.qId };
            });

            return defer.resolve(sheets);
        });
        return defer.promise;
    }

    var appList = getAppList();
    var maxNumberApps = 5;
    var properties = {
        type: "items",
        component: "accordion",
        uses: 'settings',
        items: {}
    }

    for(var i=1; i<=maxNumberApps; i++){
        properties.items['linkedApps' + i] = {
            type: 'items',
            label: 'App ' + i,
            items: {}
        }

        properties.items['linkedApps' + i]['items']['app' + i] = {
            type: 'string',
            label: 'Name',
            ref: 'props.app' + i,
            defaultValue: '',
            component: 'dropdown',
            options: function () {
                return appList;
            }
        }

        properties.items['linkedApps' + i]['items']['app' + i + 'Sheet'] = {
            type: 'string',
            label: 'Target sheet ID',
            ref: 'props.app' + i + 'Sheet',
            defaultValue: undefined,
            component: 'dropdown',
            options: function(i){
                return function(props) {
                    return (props['props']['app' + i].length > 0) ? getSheetsList(props['props']['app' + i])
                        : [{ label: '<Select an app>', value: '' }];
                }
            }(i)
        }
    }

    return properties;
});
<commit_msg>Smallfix condition<commit_after>define(['qlik', 'ng!$q'], function(qlik, $q) {

    var currApp = qlik.currApp(this);

    function getAppList(){
        var defer = $q.defer();

        qlik.getGlobal().getAppList(function(list){
            var appList = list
                .filter(function(app){ return app.qDocId !== currApp.id })
                .map(function(app){
                    return { label: app.qTitle, value: app.qDocId }
                });
            return defer.resolve(appList);
        });
        return defer.promise;
    }

    function getSheetsList(appId){
        var defer = $q.defer();
        var selectedApp = qlik.openApp(appId);

        selectedApp.getAppObjectList('sheet', function(reply){
            var sheets = reply.qAppObjectList.qItems.map(function(o){
                return { label: o.qData.title, value: o.qInfo.qId };
            });

            return defer.resolve(sheets);
        });
        return defer.promise;
    }

    var appList = getAppList();
    var maxNumberApps = 5;
    var properties = {
        type: "items",
        component: "accordion",
        uses: 'settings',
        items: {}
    }

    for(var i=1; i<=maxNumberApps; i++){
        properties.items['linkedApps' + i] = {
            type: 'items',
            label: 'App ' + i,
            items: {}
        }

        properties.items['linkedApps' + i]['items']['app' + i] = {
            type: 'string',
            label: 'Name',
            ref: 'props.app' + i,
            defaultValue: '',
            component: 'dropdown',
            options: function () {
                return appList;
            }
        }

        properties.items['linkedApps' + i]['items']['app' + i + 'Sheet'] = {
            type: 'string',
            label: 'Target sheet ID',
            ref: 'props.app' + i + 'Sheet',
            defaultValue: undefined,
            component: 'dropdown',
            options: function(i){
                return function(props) {
                    return (props['props'] != null && props['props']['app' + i] != null) ?
                        getSheetsList(props['props']['app' + i])
                        : [{ label: '<Select an app>', value: '' }];
                }
            }(i)
        }
    }

    return properties;
});
<|endoftext|>


<commit_before>// Development API path
const export const kGlobalConstants = {
 = {
    API: 'http://localhost:80/v1/',
    LOCAL_ROOT: 'http://localhost:80',
    CAS_ROOT: '',
    AUTH_CALLBACK: '',
    GITHUB: 'https://github.com/fedspendingtransparency/data-act-validator/tree/development',
    PUBLIC_FILES: '',
    GA_TRACKING_ID: '',
    LOCAL: true,
    DEV: false,
    STAGING: false,
    PROD: false
};
<commit_msg>typo fix in sampleGlobalConstants_local.js<commit_after>// Development API path
export const kGlobalConstants = {
    API: 'http://localhost:80/v1/',
    LOCAL_ROOT: 'http://localhost:80',
    CAS_ROOT: '',
    AUTH_CALLBACK: '',
    GITHUB: 'https://github.com/fedspendingtransparency/data-act-validator/tree/development',
    PUBLIC_FILES: '',
    GA_TRACKING_ID: '',
    LOCAL: true,
    DEV: false,
    STAGING: false,
    PROD: false
};
<|endoftext|>


<commit_before>/* -----------------------------------------------------------------------------
 * PixboPlayer.js, written by Jonathan Timmerlid
 *
 *
 *
 * Documentation about used plugins:
 * Ticker : http://www.jquerynewsticker.com/
 * Vegas  : http://vegas.jaysalvat.com/documentation/
 * -----------------------------------------------------------------------------
 */

var PixboPlayer = {

  // Internal
  Client_ADDR : false,
  EnableControls : false,
  UpdatedAt : false,
  Screens : [],
  Vegas_Started : false,

  // DOM-connections holder
  DOM : {},

  // Start PixboPlayer
  Start : function(options){

    // Set Internal Values
    this.Client_ADDR    = (options && options.Client_ADDR) ? options.Client_ADDR : '';
    this.EnableControls = (options && options.EnableControls) ? options.EnableControls : false;

    // Setup Dom-connections
    this.DOM.VegasTarget  = $('body');
    this.DOM.TickerTarget = $('#ticker-container');

    this.Controls.Vegas.Init();

    // Check for updates every 10s
    setInterval( PixboPlayer.Sync, 1000*10 );

    // Run once with 'first start' set
    this.Sync(true);
  },

  // Replace Contents
  Set_Screens : function(new_screens){
    this.Screens = [];
    for (i=0 ; i<new_screens.length ; i++){
      this.Screens.push( { src: '/' + new_screens[i].image } );
    }
  },
  Put_Tickers : function(tickers){
    var new_content = "
";
    for (i=0 ; i<tickers.length ; i++){
        new_content += "
"+tickers[i].text+"
";
    }
    new_content += "
";
    this.DOM.TickerTarget.html(new_content); // Replace content in Ticker Element
  },

  // Settings & Defaults
  Settings : {
    Vegas : {
      preload: true,
      transitionDuration: 4000,
      delay: 10000,
      slides: false /* This property will be set in Start_Vegas()
        [
          { src: "/screens/images/$2y$10$HUabKjNqFsvOV3jsOp5ePuhUDIBag1zELC0BnMUx3OqwEgZ8SaZVa.png" },
          { src: "/screens/images/image_0.83359500 1455543895.png" }
        ]
      */
    },
    Ticker : {
      speed: 0.10,           // The speed of the reveal
      ajaxFeed: false,       // Populate jQuery News Ticker via a feed
      feedUrl: false,        // The URL of the feed
                             // MUST BE ON THE SAME DOMAIN AS THE TICKER
      feedType: 'xml',       // Currently only XML
      htmlFeed: true,        // Populate jQuery News Ticker via HTML
      debugMode: true,       // Show some helpful errors in the console or as alerts
                             // SHOULD BE SET TO FALSE FOR PRODUCTION SITES!
      controls: false,       // Whether or not to show the jQuery News Ticker controls
      titleText: '',         // To remove the title set this to an empty String
      displayType: 'fade',   // Animation type - current options are 'reveal' or 'fade'
      direction: 'ltr',      // Ticker direction - current options are 'ltr' or 'rtl'
      pauseOnItems: 10000,   // The pause on a news item before being replaced
      fadeInSpeed: 600,      // Speed of fade in animation
      fadeOutSpeed: 300      // Speed of fade out animation
    },
  },
  Apply_Settings : function(new_settings){
    // Apply Vegas-settings
    if (new_settings.vegas)
      for (var property in this.Settings.Vegas)
        if (this.Settings.Vegas.hasOwnProperty(property))
          this.Settings.Vegas[property] = new_settings.vegas[property];

    // Apply Ticker-settings
    if (new_settings.ticker)
      for (var property in this.Settings.Ticker)
        if (this.Settings.Ticker.hasOwnProperty(property))
          this.Settings.Ticker[property] = new_settings.ticker[property];

    // Add Controls in Settings for Ticker
    if (this.EnableControls)
      this.Settings.Ticker.controls=true;

    // Add Controls for Vegas
    if (this.EnableControls)
      this.Controls.Vegas.Enable();
  },

  /* Start Ticker */
  Start_Ticker : function(){
    // Get Target
    var _target = this.DOM.TickerTarget.find('ul#ticker');

    // Check if there is any tickers
    if (!_target || _target.children().length<1)
      return console.log('No tickers...');

    // Run .ticker() Plugin
    target.ticker(this.Settings.Ticker);

    // Start Controls
    if (this.EnableControls)
      this.Controls.Ticker.Init();
  },

  /* Start Vegas */
  Start_Vegas : function(){
    // Check if there is any screens
    if (!this.Screens || this.Screens.length<1)
      return console.log('No Screens...');

    // Set current Screens
    this.Settings.Vegas.slides = this.Screens;

    // Restart
    if (this.Vegas_Started)
      this.DOM.VegasTarget          // Restart Player Chain
        .vegas('pause')             // Pause
        .vegas('destroy')           // Destroy
        .vegas(this.Settings.Vegas) // Start New
        .vegas('play');             // Play

    // First Run
    else
      this.DOM.VegasTarget
        .vegas(this.Settings.Vegas);

    this.Vegas_Started = true;
  },

  /* Update-check */
  Sync : function (first_run) {
      var _request = $.ajax({
          type: "get",
          url: "/play/" + PixboPlayer.Client_ADDR,
      });
      _request.done(function() {
          var _data = JSON.parse(_request.responseText); // Fetch JSON-output
          if(first_run || PixboPlayer.UpdatedAt != _data['updated_at']) { // First run / Newer data found, update player:
              var _mode = first_run ? false : 'restart'; // Tell Update() if it´s a first run or restart (Vegas needs to know)
              if (!first_run) {
                console.log('Client Updated, fetching newer data: (site: '+ PixboPlayer.Client_ADDR +', updated: '+ _data['updated_at'] +')');
              }
              PixboPlayer.Update(
                  _data['photo_list'],
                  _data['tickers'],
                  _data['updated_at'],
                  _data['settings'],
                  _mode
              );
              if (!first_run && _data['reboot']){
                console.log("Reloading Page");
                location.reload();
              }
              /*
              JSON-object structure: {
                "photo_list":[ {"image":" "}, ... ],
                "tickers":[ {"text":" "}, ... ],
                "updated_at":" ",
                "settings":{
                  "ticker":{ "pauseOnItems":10000, ... },
                  "vegas":{ "delay":10000, ... }
                }
              }
              */
          }
      });
  },

  /* Perform update */
  Update : function(new_screens, new_tickers, new_updated_at, new_settings) {

      // Apply settings
      this.Apply_Settings(new_settings);

      // Replace screens-list
      this.Set_Screens( new_screens );

      // Start/Restart Vegas
      this.Start_Vegas();

      // Replace contents in DOM for tickers
      this.Put_Tickers( new_tickers );

      // Load/Reload Ticker
      this.Start_Ticker();

      // Replace UpdatedAt
      this.UpdatedAt = new_updated_at;

      // Done
  },

  // Controls
  Controls : {
    Vegas : {
      Element : false,
      Buttons : {
        Prev  : false,
        Next  : false,
        Play  : false,
        Pause : false,
      },

      // Show Controls
      Enable : function(){
        this.Element.style.display='block';
      },

      // Functions
      Play : function(){
        PixboPlayer.Controls.Vegas.Buttons.Play.style.display = "none";
        PixboPlayer.Controls.Vegas.Buttons.Pause.style.display = "block";
        PixboPlayer.DOM.VegasTarget.vegas('play');
      },
      Pause : function(){
        PixboPlayer.Controls.Vegas.Buttons.Play.style.display = "block";
        PixboPlayer.Controls.Vegas.Buttons.Pause.style.display = "none";
        PixboPlayer.DOM.VegasTarget.vegas('pause');
      },
      Next : function(){
        PixboPlayer.DOM.VegasTarget
          .vegas('options', 'transitionDuration', '100')                                          // Speed-up
          .vegas('next')                                                                          // Show next screen
          .vegas('options', 'transitionDuration', PixboPlayer.Settings.Vegas.transitionDuration); // Restore speed
        PixboPlayer.Controls.Vegas.Pause();                                                       // Pause
      },
      Prev : function(){
        PixboPlayer.DOM.VegasTarget
          .vegas('options', 'transitionDuration', '100')                                          // Speed-up
          .vegas('previous')                                                                      // Show previous screen
          .vegas('options', 'transitionDuration', PixboPlayer.Settings.Vegas.transitionDuration); // Restore speed
        PixboPlayer.Controls.Vegas.Pause();                                                       // Pause
      },

      Init : function (){
        // Create Elements
        this.Element = document.createElement("div");
          this.Buttons.Prev  = document.createElement("a");
          this.Buttons.Next  = document.createElement("a");
          this.Buttons.Play  = document.createElement("a");
          this.Buttons.Pause = document.createElement("a");

        // Add Classes
        this.Element.classList.add('vegas_control');
          this.Buttons.Prev.classList.add('fa');
          this.Buttons.Prev.classList.add('fa-step-backward');
          this.Buttons.Next.classList.add('fa');
          this.Buttons.Next.classList.add('fa-step-forward');
          this.Buttons.Play.classList.add('fa');
          this.Buttons.Play.classList.add('fa-play');
          this.Buttons.Pause.classList.add('fa');
          this.Buttons.Pause.classList.add('fa-pause');

        // Attach event-listeners
          this.Buttons.Prev.onclick  = this.Prev;
          this.Buttons.Next.onclick  = this.Next;
          this.Buttons.Play.onclick  = this.Play;
          this.Buttons.Pause.onclick = this.Pause;

        // CSS
          this.Buttons.Play.style.display = "none"; // Play-button hidden on start
        this.Element.style.display = "none"; // Hide Controls on start.

        // Append Elements
          this.Element.appendChild(this.Buttons.Prev);
          this.Element.appendChild(this.Buttons.Play);
          this.Element.appendChild(this.Buttons.Pause);
          this.Element.appendChild(this.Buttons.Next);
        PixboPlayer.DOM.VegasTarget.append(this.Element);
      },
    },
    Ticker : {
      Element : false,
      Buttons : {
             Prev : false,
             Next : false,
        PlayPause : false,
      },

      Init : function(){
        // This will go through the plugins output and hook into things
        this.DOM.TickerTarget.find('.ticker-controls').each(function(){
          // Got ticker-controls element
          PixboPlayer.Controls.Ticker.Element = this;
          this.classList.add('ticker_control');

          // Collect Buttons
          $(this).find('li').each(function(){
            if (this.classList.contains('jnt-prev'))
              PixboPlayer.Controls.Ticker.Buttons.Prev = this;
            if (this.classList.contains('jnt-next'))
              PixboPlayer.Controls.Ticker.Buttons.Next = this;
            if (this.classList.contains('jnt-play-pause'))
              PixboPlayer.Controls.Ticker.Buttons.PlayPause = this;

            // Attach event-listener
            this.onclick=function (e){
              // Timer (so that the plugin got some time to do its work before check)
              setTimeout(function(){
                // Check play/pause status when ticker-controls are being used and update icon
                if (PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.contains('paused')){
                  PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.remove('fa-pause');
                  PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.add('fa-play');
                } else {
                  PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.add('fa-pause');
                  PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.remove('fa-play');
                }
              }, 100);
            };
          });

          // Add Classes
          PixboPlayer.Controls.Ticker.Buttons.Prev.classList.add('fa');
          PixboPlayer.Controls.Ticker.Buttons.Prev.classList.add('fa-step-backward');
          PixboPlayer.Controls.Ticker.Buttons.Next.classList.add('fa');
          PixboPlayer.Controls.Ticker.Buttons.Next.classList.add('fa-step-forward');
          PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.add('fa');
          PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.add('fa-pause');

          // Order Buttons
          $( PixboPlayer.Controls.Ticker.Buttons.PlayPause ).insertBefore( PixboPlayer.Controls.Ticker.Buttons.Next );
        });
      },
    },
  },
};

/**
 * Forward function jQuery.live()
 * Wrapper for newer jQuery.on()
 * Uses optimized selector context
 * Only add if live() not already existing.
 * Information: The live() method was deprecated in jQuery version 1.7,
 *              and removed in version 1.9.
 *              Use the on() method instead.
 * Reason: Ticker-plugin uses live() @control, forward function instead of refactor plugin.
*/
if (typeof jQuery.fn.live == 'undefined' || !(jQuery.isFunction(jQuery.fn.live))) {
  jQuery.fn.extend({
      live: function (event, callback) {
         if (this.selector) {
              jQuery(document).on(event, this.selector, callback);
          }
      }
  });
}

<commit_msg>PixboPlayer: Added 'Standby' Canvas when waiting for data/client<commit_after>/* -----------------------------------------------------------------------------
 * PixboPlayer.js, written by Jonathan Timmerlid
 *
 *
 *
 * Documentation about used plugins:
 * Ticker : http://www.jquerynewsticker.com/
 * Vegas  : http://vegas.jaysalvat.com/documentation/
 * -----------------------------------------------------------------------------
 */

var PixboPlayer = {

  // Internal
  Client_ADDR : false,
  EnableControls : false,
  UpdatedAt : false,
  Screens : [],
  Vegas_Started : false,
  Backdrop : false,

  // DOM-connections holder
  DOM : {},

  // Start PixboPlayer
  Start : function(options){

    // Set Internal Values
    this.Client_ADDR    = (options && options.Client_ADDR) ? options.Client_ADDR : '';
    this.EnableControls = (options && options.EnableControls) ? options.EnableControls : false;

    // Setup Dom-connections
    this.DOM.VegasTarget  = $('body');
    this.DOM.TickerTarget = $('#ticker-container');

    // Start Standby-page
    this.Standby('Loading');

    this.Controls.Vegas.Init();

    // Check for updates every 10s
    setInterval( PixboPlayer.Sync, 1000*10 );

    // Run once with 'first start' set
    this.Sync(true);
  },

  // Standby-mode
  Standby : function(info){
    if (!this.Backdrop){
      this.Backdrop = document.createElement("canvas");
      this.Backdrop.style.position = "fixed";
      this.Backdrop.style.top = "0px";
      this.Backdrop.width = window.innerWidth;
      this.Backdrop.height = window.innerHeight;
      this.Backdrop.style.width = "100%";
      this.Backdrop.style.height = "100%";
      this.Backdrop.style.zIndex = "1000";
      this.Backdrop.ctx = this.Backdrop.getContext("2d");
      this.DOM.VegasTarget.append(this.Backdrop);
    }
    this.Backdrop.style.display = "block";

      this.Backdrop.ctx.textAlign = 'center';
      this.Backdrop.ctx.lineWidth = '10px';
      this.Backdrop.ctx.grd = this.Backdrop.ctx.createRadialGradient(0,0,50,0,0,this.Backdrop.width/2);
      this.Backdrop.ctx.grd.addColorStop(0,"#333");
      this.Backdrop.ctx.grd.addColorStop(1,"#000");

      this.Backdrop.ctx.info = info;

      PixboPlayer.Standby_Counter = 0;

    this.Standby_Animation = function(){
      PixboPlayer.requestFrame = requestAnimationFrame(PixboPlayer.Standby_Animation, PixboPlayer.Backdrop);
    //this.Standby_Animation = setInterval( function(){
      PixboPlayer.Backdrop.ctx.clearRect( 0, 0, PixboPlayer.Backdrop.width, PixboPlayer.Backdrop.height );
      PixboPlayer.Backdrop.ctx.save();

      PixboPlayer.Backdrop.ctx.translate( PixboPlayer.Backdrop.width/2, PixboPlayer.Backdrop.height/2 );

      PixboPlayer.Backdrop.ctx.fillStyle = PixboPlayer.Backdrop.ctx.grd;
      PixboPlayer.Backdrop.ctx.fillRect(
        -PixboPlayer.Backdrop.width/2,
        -PixboPlayer.Backdrop.height/2,
        PixboPlayer.Backdrop.width,
        PixboPlayer.Backdrop.height
      );

      PixboPlayer.Backdrop.ctx.strokeStyle = '#333';
      PixboPlayer.Backdrop.ctx.beginPath();
      PixboPlayer.Backdrop.ctx.arc(0,0,PixboPlayer.Backdrop.width/4,PixboPlayer.Standby_Counter/Math.PI,(PixboPlayer.Standby_Counter+15)/Math.PI);
      PixboPlayer.Backdrop.ctx.stroke();
      PixboPlayer.Standby_Counter+=0.02;
      if (PixboPlayer.Standby_Counter>=360)
        PixboPlayer.Standby_Counter = 0;

      PixboPlayer.Backdrop.ctx.font      = '96px Verdana';
      PixboPlayer.Backdrop.ctx.fillStyle = '#999';
      if (PixboPlayer.Backdrop.ctx.info=="404"){
        PixboPlayer.Backdrop.ctx.fillText( "Standby", 0, 0 );
        PixboPlayer.Backdrop.ctx.font      = '40px Verdana';
        PixboPlayer.Backdrop.ctx.fillText( PixboPlayer.Client_ADDR, 0, 70 );
      }
      else
        PixboPlayer.Backdrop.ctx.fillText( PixboPlayer.Backdrop.ctx.info, 0, 0 );

      PixboPlayer.Backdrop.ctx.restore();
    };
    //}, 1000/4 );
    this.Standby_Animation();

  },
  Ready : function(){
    PixboPlayer.requestFrame = false;
    //clearTimeout(this.Standby_Animation);
    this.Backdrop.style.display = "none";
  },

  // Replace Contents
  Set_Screens : function(new_screens){
    this.Screens = [];
    for (i=0 ; i<new_screens.length ; i++){
      this.Screens.push( { src: '/' + new_screens[i].image } );
    }
  },
  Put_Tickers : function(tickers){
    var new_content = "
";
    for (i=0 ; i<tickers.length ; i++){
        new_content += "
"+tickers[i].text+"
";
    }
    new_content += "
";
    this.DOM.TickerTarget.html(new_content); // Replace content in Ticker Element
  },

  // Settings & Defaults
  Settings : {
    Vegas : {
      preload: true,
      transitionDuration: 4000,
      delay: 10000,
      slides: false /* This property will be set in Start_Vegas()
        [
          { src: "/screens/images/$2y$10$HUabKjNqFsvOV3jsOp5ePuhUDIBag1zELC0BnMUx3OqwEgZ8SaZVa.png" },
          { src: "/screens/images/image_0.83359500 1455543895.png" }
        ]
      */
    },
    Ticker : {
      speed: 0.10,           // The speed of the reveal
      ajaxFeed: false,       // Populate jQuery News Ticker via a feed
      feedUrl: false,        // The URL of the feed
                             // MUST BE ON THE SAME DOMAIN AS THE TICKER
      feedType: 'xml',       // Currently only XML
      htmlFeed: true,        // Populate jQuery News Ticker via HTML
      debugMode: true,       // Show some helpful errors in the console or as alerts
                             // SHOULD BE SET TO FALSE FOR PRODUCTION SITES!
      controls: false,       // Whether or not to show the jQuery News Ticker controls
      titleText: '',         // To remove the title set this to an empty String
      displayType: 'fade',   // Animation type - current options are 'reveal' or 'fade'
      direction: 'ltr',      // Ticker direction - current options are 'ltr' or 'rtl'
      pauseOnItems: 10000,   // The pause on a news item before being replaced
      fadeInSpeed: 600,      // Speed of fade in animation
      fadeOutSpeed: 300      // Speed of fade out animation
    },
  },
  Apply_Settings : function(new_settings){
    // Apply Vegas-settings
    if (new_settings.vegas)
      for (var property in this.Settings.Vegas)
        if (this.Settings.Vegas.hasOwnProperty(property))
          this.Settings.Vegas[property] = new_settings.vegas[property];

    // Apply Ticker-settings
    if (new_settings.ticker)
      for (var property in this.Settings.Ticker)
        if (this.Settings.Ticker.hasOwnProperty(property))
          this.Settings.Ticker[property] = new_settings.ticker[property];

    // Add Controls in Settings for Ticker
    if (this.EnableControls)
      this.Settings.Ticker.controls=true;

    // Add Controls for Vegas
    if (this.EnableControls)
      this.Controls.Vegas.Enable();
  },

  /* Start Ticker */
  Start_Ticker : function(){
    // Get Target
    var _target = this.DOM.TickerTarget.find('ul#ticker');

    // Check if there is any tickers
    if (!_target || _target.children().length<1)
      return console.log('No tickers...');

    // Run .ticker() Plugin
    target.ticker(this.Settings.Ticker);

    // Start Controls
    if (this.EnableControls)
      this.Controls.Ticker.Init();
  },

  /* Start Vegas */
  Start_Vegas : function(){
    // Check if there is any screens
    if (!this.Screens || this.Screens.length<1)
      return console.log('No Screens...');

    // Set current Screens
    this.Settings.Vegas.slides = this.Screens;

    // Restart
    if (this.Vegas_Started)
      this.DOM.VegasTarget          // Restart Player Chain
        .vegas('pause')             // Pause
        .vegas('destroy')           // Destroy
        .vegas(this.Settings.Vegas) // Start New
        .vegas('play');             // Play

    // First Run
    else
      this.DOM.VegasTarget
        .vegas(this.Settings.Vegas);

    this.Vegas_Started = true;
  },

  /* Update-check */
  Sync : function (first_run) {
      var _request = $.ajax({
          type: "get",
          url: "/play/" + PixboPlayer.Client_ADDR,
      });
      _request.done(function() {
          var _data = JSON.parse(_request.responseText); // Fetch JSON-output
          if(first_run || PixboPlayer.UpdatedAt != _data['updated_at']) { // First run / Newer data found, update player:
              var _mode = first_run ? false : 'restart'; // Tell Update() if it´s a first run or restart (Vegas needs to know)
              if (!first_run) {
                console.log('Client Updated, fetching newer data: (site: '+ PixboPlayer.Client_ADDR +', updated: '+ _data['updated_at'] +')');
              }
              PixboPlayer.Update(
                  _data['photo_list'],
                  _data['tickers'],
                  _data['updated_at'],
                  _data['settings'],
                  false
              );
              if (!first_run && _data['reboot']){
                console.log("Reloading Page");
                location.reload();
              }
              /*
              JSON-object structure: {
                "photo_list":[ {"image":" "}, ... ],
                "tickers":[ {"text":" "}, ... ],
                "updated_at":" ",
                "settings":{
                  "ticker":{ "pauseOnItems":10000, ... },
                  "vegas":{ "delay":10000, ... }
                }
              }
              */
          }
      });
      _request.error(function(xhr) {
        PixboPlayer.Update(
            false,
            false,
            false,
            false,
            xhr.status
        );
      });
  },

  /* Perform update */
  Update : function(new_screens, new_tickers, new_updated_at, new_settings, info) {

      // Info & Errors
      if (info)
        this.Standby(info);
      else
        this.Ready();

      // Apply settings
      this.Apply_Settings(new_settings);

      // Replace screens-list
      this.Set_Screens( new_screens );

      // Start/Restart Vegas
      this.Start_Vegas();

      // Replace contents in DOM for tickers
      this.Put_Tickers( new_tickers );

      // Load/Reload Ticker
      this.Start_Ticker();

      // Replace UpdatedAt
      this.UpdatedAt = new_updated_at;

      // Done
  },

  // Controls
  Controls : {
    Vegas : {
      Element : false,
      Buttons : {
        Prev  : false,
        Next  : false,
        Play  : false,
        Pause : false,
      },

      // Show Controls
      Enable : function(){
        this.Element.style.display='block';
      },

      // Functions
      Play : function(){
        PixboPlayer.Controls.Vegas.Buttons.Play.style.display = "none";
        PixboPlayer.Controls.Vegas.Buttons.Pause.style.display = "block";
        PixboPlayer.DOM.VegasTarget.vegas('play');
      },
      Pause : function(){
        PixboPlayer.Controls.Vegas.Buttons.Play.style.display = "block";
        PixboPlayer.Controls.Vegas.Buttons.Pause.style.display = "none";
        PixboPlayer.DOM.VegasTarget.vegas('pause');
      },
      Next : function(){
        PixboPlayer.DOM.VegasTarget
          .vegas('options', 'transitionDuration', '100')                                          // Speed-up
          .vegas('next')                                                                          // Show next screen
          .vegas('options', 'transitionDuration', PixboPlayer.Settings.Vegas.transitionDuration); // Restore speed
        PixboPlayer.Controls.Vegas.Pause();                                                       // Pause
      },
      Prev : function(){
        PixboPlayer.DOM.VegasTarget
          .vegas('options', 'transitionDuration', '100')                                          // Speed-up
          .vegas('previous')                                                                      // Show previous screen
          .vegas('options', 'transitionDuration', PixboPlayer.Settings.Vegas.transitionDuration); // Restore speed
        PixboPlayer.Controls.Vegas.Pause();                                                       // Pause
      },

      Init : function (){
        // Create Elements
        this.Element = document.createElement("div");
          this.Buttons.Prev  = document.createElement("a");
          this.Buttons.Next  = document.createElement("a");
          this.Buttons.Play  = document.createElement("a");
          this.Buttons.Pause = document.createElement("a");

        // Add Classes
        this.Element.classList.add('vegas_control');
          this.Buttons.Prev.classList.add('fa');
          this.Buttons.Prev.classList.add('fa-step-backward');
          this.Buttons.Next.classList.add('fa');
          this.Buttons.Next.classList.add('fa-step-forward');
          this.Buttons.Play.classList.add('fa');
          this.Buttons.Play.classList.add('fa-play');
          this.Buttons.Pause.classList.add('fa');
          this.Buttons.Pause.classList.add('fa-pause');

        // Attach event-listeners
          this.Buttons.Prev.onclick  = this.Prev;
          this.Buttons.Next.onclick  = this.Next;
          this.Buttons.Play.onclick  = this.Play;
          this.Buttons.Pause.onclick = this.Pause;

        // CSS
          this.Buttons.Play.style.display = "none"; // Play-button hidden on start
        this.Element.style.display = "none"; // Hide Controls on start.

        // Append Elements
          this.Element.appendChild(this.Buttons.Prev);
          this.Element.appendChild(this.Buttons.Play);
          this.Element.appendChild(this.Buttons.Pause);
          this.Element.appendChild(this.Buttons.Next);
        PixboPlayer.DOM.VegasTarget.append(this.Element);
      },
    },
    Ticker : {
      Element : false,
      Buttons : {
             Prev : false,
             Next : false,
        PlayPause : false,
      },

      Init : function(){
        // This will go through the plugins output and hook into things
        this.DOM.TickerTarget.find('.ticker-controls').each(function(){
          // Got ticker-controls element
          PixboPlayer.Controls.Ticker.Element = this;
          this.classList.add('ticker_control');

          // Collect Buttons
          $(this).find('li').each(function(){
            if (this.classList.contains('jnt-prev'))
              PixboPlayer.Controls.Ticker.Buttons.Prev = this;
            if (this.classList.contains('jnt-next'))
              PixboPlayer.Controls.Ticker.Buttons.Next = this;
            if (this.classList.contains('jnt-play-pause'))
              PixboPlayer.Controls.Ticker.Buttons.PlayPause = this;

            // Attach event-listener
            this.onclick=function (e){
              // Timer (so that the plugin got some time to do its work before check)
              setTimeout(function(){
                // Check play/pause status when ticker-controls are being used and update icon
                if (PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.contains('paused')){
                  PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.remove('fa-pause');
                  PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.add('fa-play');
                } else {
                  PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.add('fa-pause');
                  PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.remove('fa-play');
                }
              }, 100);
            };
          });

          // Add Classes
          PixboPlayer.Controls.Ticker.Buttons.Prev.classList.add('fa');
          PixboPlayer.Controls.Ticker.Buttons.Prev.classList.add('fa-step-backward');
          PixboPlayer.Controls.Ticker.Buttons.Next.classList.add('fa');
          PixboPlayer.Controls.Ticker.Buttons.Next.classList.add('fa-step-forward');
          PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.add('fa');
          PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.add('fa-pause');

          // Order Buttons
          $( PixboPlayer.Controls.Ticker.Buttons.PlayPause ).insertBefore( PixboPlayer.Controls.Ticker.Buttons.Next );
        });
      },
    },
  },
};

/**
 * Forward function jQuery.live()
 * Wrapper for newer jQuery.on()
 * Uses optimized selector context
 * Only add if live() not already existing.
 * Information: The live() method was deprecated in jQuery version 1.7,
 *              and removed in version 1.9.
 *              Use the on() method instead.
 * Reason: Ticker-plugin uses live() @control, forward function instead of refactor plugin.
*/
if (typeof jQuery.fn.live == 'undefined' || !(jQuery.isFunction(jQuery.fn.live))) {
  jQuery.fn.extend({
      live: function (event, callback) {
         if (this.selector) {
              jQuery(document).on(event, this.selector, callback);
          }
      }
  });
}

<|endoftext|>


<commit_before>/* -----------------------------------------------------------------------------
 * PixboPlayer.js, written by Jonathan Timmerlid
 *
 *
 *
 * Documentation about used plugins:
 * Ticker : http://www.jquerynewsticker.com/
 * Vegas  : http://vegas.jaysalvat.com/documentation/
 * -----------------------------------------------------------------------------
 */

var PixboPlayer = {

  // Internal
  EnableControls : false,

  // DOM-connections
  DOM : {
    VegasTarget  : false,
    TickerTarget : false,
    ScreenBox    : false,
    $updated_at    : false,
    $client_id     : false,
    $preview       : false,
  },

  // Start PixboPlayer
  Start : function(){
    this.DOM.VegasTarget  = $('body');
    this.DOM.TickerTarget = $('#ticker-container');
    this.DOM.ScreenBox    = $('#screens');
    this.DOM.UpdatedAt    = $('#updated_at');
    this.DOM.ClientID     = $('#client_id');
    this.DOM.Preview      = $('#preview');

    this.Controls.Vegas.Init();
    setInterval( this.Sync, 1000*10 ); // Check for newer data each 10 sec
    this.Sync(true); // Run once with flag set
  },

  // DOM-Access
  Get_Screens : function(){
    var images = [];
    this.DOM.ScreenBox.find('li').each(function() {
        images.push(
            { src: '/' + $(this).attr('src') }
        );
    });
    return images;
  },

  // DOM-Replacement
  Put_Screens : function(screens){
    var new_content = '';
    for (i=0 ; i<screens.length ; i++){
        new_content += "

";
    }
    this.DOM.ScreenBox.html(new_content); // Replace content in ScreenBox Element
  },
  Put_Tickers : function(tickers){
    var new_content = "
";
    for (i=0 ; i<tickers.length ; i++){
        new_content += "
"+tickers[i].text+"
";
    }
    new_content += "
";
    this.DOM.TickerTarget.html(new_content); // Replace content in Ticker Element
  },

  // Settings & Defaults
  Settings : {
    Vegas : {
      preload: true,
      transitionDuration: 4000,
      delay: 10000,
      slides: false /* This property will be set in Start_Vegas()
        [
          { src: "/screens/images/$2y$10$HUabKjNqFsvOV3jsOp5ePuhUDIBag1zELC0BnMUx3OqwEgZ8SaZVa.png" },
          { src: "/screens/images/image_0.83359500 1455543895.png" }
        ]
      */
    },
    Ticker : {
      speed: 0.10,           // The speed of the reveal
      ajaxFeed: false,       // Populate jQuery News Ticker via a feed
      feedUrl: false,        // The URL of the feed
                             // MUST BE ON THE SAME DOMAIN AS THE TICKER
      feedType: 'xml',       // Currently only XML
      htmlFeed: true,        // Populate jQuery News Ticker via HTML
      debugMode: true,       // Show some helpful errors in the console or as alerts
                             // SHOULD BE SET TO FALSE FOR PRODUCTION SITES!
      controls: false,       // Whether or not to show the jQuery News Ticker controls
      titleText: '',         // To remove the title set this to an empty String
      displayType: 'fade',   // Animation type - current options are 'reveal' or 'fade'
      direction: 'ltr',      // Ticker direction - current options are 'ltr' or 'rtl'
      pauseOnItems: 10000,   // The pause on a news item before being replaced
      fadeInSpeed: 600,      // Speed of fade in animation
      fadeOutSpeed: 300      // Speed of fade out animation
    },
  },
  Apply_Settings : function(new_settings){
    // Enable Controls
    if (this.DOM.Preview.val())
      this.EnableControls = true;

    // Apply Vegas-settings
    //for (var property in this.Settings.Vegas)
    //    if (this.Settings.Vegas.hasOwnProperty(property))
    //      this.Settings.Vegas[property] = new_settings['vegas'][property];

    // Apply Ticker-settings
    //for (var property in this.Settings.Ticker)
    //    if (this.Settings.Ticker.hasOwnProperty(property))
    //      this.Settings.Ticker[property] = new_settings['ticker'][property];

    // Add Controls in Settings for Ticker
    if (this.EnableControls)
      this.Settings.Ticker.controls=true;

    // Add Controls for Vegas
    if (this.EnableControls)
      this.Controls.Vegas.Enable();
  },

  /* Start Ticker */
  Start_Ticker : function(){
    // Get Target
    var _target = this.DOM.TickerTarget.find('ul#ticker');

    // Check for tickers
    if (_target.children().length>0){
      // Run .ticker() Plugin
      _target.ticker(this.Settings.Ticker);

      // Start Controls
      if (this.EnableControls)
        this.Controls.Ticker.Init();
    }
    else{
      // No Tickers
      console.log('No tickers...');
    }
  },

  /* Start Vegas */
  Start_Vegas : function(mode){
    // Get current Screens
    this.Settings.Vegas.slides = this.Get_Screens();

    // Restart
    if (mode=='restart')
      this.DOM.VegasTarget          // Restart Player Chain
        .vegas('pause')             // Pause
        .vegas('destroy')           // Destroy
        .vegas(this.Settings.Vegas) // Start New
        .vegas('play');             // Play

    // First Run
    else
      this.DOM.VegasTarget
        .vegas(this.Settings.Vegas);
  },

  /* Update-check */
  Sync : function (first_run) {
      var _request = $.ajax({
          type: "get",
          url: "/play/" + PixboPlayer.DOM.ClientID.val(),
      });
      _request.done(function() {
          var _data = JSON.parse(_request.responseText); // Fetch JSON-output
          if(first_run || PixboPlayer.DOM.UpdatedAt.val() != _data['updated_at']) { // First run / Newer data found, update player:
              var _mode = first_run ? false : 'restart'; // Tell Update() if it´s a first run or restart (Vegas needs to know)
              if (!first_run) {
                console.log('Client Updated, fetching newer data: (site: '+ PixboPlayer.DOM.ClientID.val() +', updated: '+ _data['updated_at'] +')');
              }
              PixboPlayer.Update(
                  _data['photo_list'],
                  _data['tickers'],
                  _data['updated_at'],
                  _data['settings'],
                  _mode
              );
              /*
              JSON-object structure: {
                "photo_list":[ {"image":" "}, ... ],
                "tickers":[ {"text":" "}, ... ],
                "updated_at":" ",
                "settings":{
                  "ticker":{ "pauseOnItems":10000, ... },
                  "vegas":{ "delay":10000, ... }
                }
              }
              */
          }
      });
  },

  /* Perform update */
  Update : function(new_screens, new_tickers, new_updated_at, new_settings, mode) {

      // Apply settings
      this.Apply_Settings(new_settings);

      // Replace contents in DOM for screens-list
      this.Put_Screens( new_screens );

      // Start/Restart Vegas
      this.Start_Vegas(mode);

      // Replace contents in DOM for tickers
      this.Put_Tickers( new_tickers );

      // Load/Reload Ticker
      this.Start_Ticker();

      // Replace value of updated_at
      this.DOM.UpdatedAt.val(new_updated_at);

      // Done
  },

  // Controls in Preview-mode
  Controls : {
    Vegas : {
      Element : false,
      Buttons : {
        Prev  : false,
        Next  : false,
        Play  : false,
        Pause : false,
      },

      // Show Controls
      Enable : function(){
        this.Element.style.display='block';
      },

      // Functions
      Play : function(){
        PixboPlayer.Controls.Vegas.Buttons.Play.style.display = "none";
        PixboPlayer.Controls.Vegas.Buttons.Pause.style.display = "block";
        PixboPlayer.DOM.VegasTarget.vegas('play');
      },
      Pause : function(){
        PixboPlayer.Controls.Vegas.Buttons.Play.style.display = "block";
        PixboPlayer.Controls.Vegas.Buttons.Pause.style.display = "none";
        PixboPlayer.DOM.VegasTarget.vegas('pause');
      },
      Next : function(){
        PixboPlayer.DOM.VegasTarget
          .vegas('options', 'transitionDuration', '100')                                          // Speed-up
          .vegas('next')                                                                          // Show next screen
          .vegas('options', 'transitionDuration', PixboPlayer.Settings.Vegas.transitionDuration); // Restore speed
        PixboPlayer.Controls.Vegas.Pause();                                                       // Pause
      },
      Prev : function(){
        PixboPlayer.DOM.VegasTarget
          .vegas('options', 'transitionDuration', '100')                                          // Speed-up
          .vegas('previous')                                                                      // Show previous screen
          .vegas('options', 'transitionDuration', PixboPlayer.Settings.Vegas.transitionDuration); // Restore speed
        PixboPlayer.Controls.Vegas.Pause();                                                       // Pause
      },

      Init : function (){
        // Create Elements
        this.Element = document.createElement("div");
          this.Buttons.Prev  = document.createElement("a");
          this.Buttons.Next  = document.createElement("a");
          this.Buttons.Play  = document.createElement("a");
          this.Buttons.Pause = document.createElement("a");

        // Add Classes
        this.Element.classList.add('vegas_control');
          this.Buttons.Prev.classList.add('fa');
          this.Buttons.Prev.classList.add('fa-step-backward');
          this.Buttons.Next.classList.add('fa');
          this.Buttons.Next.classList.add('fa-step-forward');
          this.Buttons.Play.classList.add('fa');
          this.Buttons.Play.classList.add('fa-play');
          this.Buttons.Pause.classList.add('fa');
          this.Buttons.Pause.classList.add('fa-pause');

        // Attach event-listeners
          this.Buttons.Prev.onclick  = this.Prev;
          this.Buttons.Next.onclick  = this.Next;
          this.Buttons.Play.onclick  = this.Play;
          this.Buttons.Pause.onclick = this.Pause;

        // CSS
          this.Buttons.Play.style.display = "none"; // Play-button hidden on start
        this.Element.style.display = "none"; // Hide Controls on start.

        // Append Elements
          this.Element.appendChild(this.Buttons.Prev);
          this.Element.appendChild(this.Buttons.Play);
          this.Element.appendChild(this.Buttons.Pause);
          this.Element.appendChild(this.Buttons.Next);
        PixboPlayer.DOM.VegasTarget.append(this.Element);
      },
    },
    Ticker : {
      Element : false,
      Buttons : {
             Prev : false,
             Next : false,
        PlayPause : false,
      },

      Init : function(){
        // This will go through the plugins output and hook into things
        DOM.TickerTarget.find('.ticker-controls').each(function(){
          // Got ticker-controls element
          PixboPlayer.Controls.Ticker.Element = this;
          this.classList.add('ticker_control');

          // Collect Buttons
          $(this).find('li').each(function(){
            if (this.classList.contains('jnt-prev'))
              PixboPlayer.Controls.Ticker.Buttons.Prev = this;
            if (this.classList.contains('jnt-next'))
              PixboPlayer.Controls.Ticker.Buttons.Next = this;
            if (this.classList.contains('jnt-play-pause'))
              PixboPlayer.Controls.Ticker.Buttons.PlayPause = this;

            // Attach event-listener
            this.onclick=function (e){
              // Timer (so that the plugin got some time to do its work before check)
              setTimeout(function(){
                // Check play/pause status when ticker-controls are being used and update icon
                if (PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.contains('paused')){
                  PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.remove('fa-pause');
                  PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.add('fa-play');
                } else {
                  PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.add('fa-pause');
                  PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.remove('fa-play');
                }
              }, 100);
            };
          });

          // Add Classes
          PixboPlayer.Controls.Ticker.Buttons.Prev.classList.add('fa');
          PixboPlayer.Controls.Ticker.Buttons.Prev.classList.add('fa-step-backward');
          PixboPlayer.Controls.Ticker.Buttons.Next.classList.add('fa');
          PixboPlayer.Controls.Ticker.Buttons.Next.classList.add('fa-step-forward');
          PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.add('fa');
          PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.add('fa-pause');

          // Order Buttons
          $( PixboPlayer.Controls.Ticker.Buttons.PlayPause ).insertBefore( PixboPlayer.Controls.Ticker.Buttons.Next );
        });
      },
    },
  },
};

/**
 * Forward function jQuery.live()
 * Wrapper for newer jQuery.on()
 * Uses optimized selector context
 * Only add if live() not already existing.
 * Information: The live() method was deprecated in jQuery version 1.7,
 *              and removed in version 1.9.
 *              Use the on() method instead.
 * Reason: Ticker-plugin uses live() @control, forward function instead of refactor plugin.
*/
if (typeof jQuery.fn.live == 'undefined' || !(jQuery.isFunction(jQuery.fn.live))) {
  jQuery.fn.extend({
      live: function (event, callback) {
         if (this.selector) {
              jQuery(document).on(event, this.selector, callback);
          }
      }
  });
}

// Start Player!
PixboPlayer.Start();
<commit_msg>Updates to PixboPlayer.js<commit_after>/* -----------------------------------------------------------------------------
 * PixboPlayer.js, written by Jonathan Timmerlid
 *
 *
 *
 * Documentation about used plugins:
 * Ticker : http://www.jquerynewsticker.com/
 * Vegas  : http://vegas.jaysalvat.com/documentation/
 * -----------------------------------------------------------------------------
 */

var PixboPlayer = {

  // Internal
  EnableControls : false,

  // DOM-connections holder
  DOM : {},

  // Start PixboPlayer
  Start : function(){

    // Setup Dom-connections
    this.DOM.VegasTarget  = $('body');
    this.DOM.TickerTarget = $('#ticker-container');
    this.DOM.ScreenBox    = $('#screens');
    this.DOM.UpdatedAt    = $('#updated_at');
    this.DOM.ClientID     = $('#client_id');
    this.DOM.Preview      = $('#preview');

    this.Controls.Vegas.Init();

    // Check for updates every 10s
    setInterval( this.Sync, 1000*10 );

    // Run once with 'first start' set
    this.Sync(true);
  },

  // DOM-Access
  Get_Screens : function(){
    var images = [];
    this.DOM.ScreenBox.find('li').each(function() {
        images.push(
            { src: '/' + $(this).attr('src') }
        );
    });
    return images;
  },

  // DOM-Replacement
  Put_Screens : function(screens){
    var new_content = '';
    for (i=0 ; i<screens.length ; i++){
        new_content += "

";
    }
    this.DOM.ScreenBox.html(new_content); // Replace content in ScreenBox Element
  },
  Put_Tickers : function(tickers){
    var new_content = "
";
    for (i=0 ; i<tickers.length ; i++){
        new_content += "
"+tickers[i].text+"
";
    }
    new_content += "
";
    this.DOM.TickerTarget.html(new_content); // Replace content in Ticker Element
  },

  // Settings & Defaults
  Settings : {
    Vegas : {
      preload: true,
      transitionDuration: 4000,
      delay: 10000,
      slides: false /* This property will be set in Start_Vegas()
        [
          { src: "/screens/images/$2y$10$HUabKjNqFsvOV3jsOp5ePuhUDIBag1zELC0BnMUx3OqwEgZ8SaZVa.png" },
          { src: "/screens/images/image_0.83359500 1455543895.png" }
        ]
      */
    },
    Ticker : {
      speed: 0.10,           // The speed of the reveal
      ajaxFeed: false,       // Populate jQuery News Ticker via a feed
      feedUrl: false,        // The URL of the feed
                             // MUST BE ON THE SAME DOMAIN AS THE TICKER
      feedType: 'xml',       // Currently only XML
      htmlFeed: true,        // Populate jQuery News Ticker via HTML
      debugMode: true,       // Show some helpful errors in the console or as alerts
                             // SHOULD BE SET TO FALSE FOR PRODUCTION SITES!
      controls: false,       // Whether or not to show the jQuery News Ticker controls
      titleText: '',         // To remove the title set this to an empty String
      displayType: 'fade',   // Animation type - current options are 'reveal' or 'fade'
      direction: 'ltr',      // Ticker direction - current options are 'ltr' or 'rtl'
      pauseOnItems: 10000,   // The pause on a news item before being replaced
      fadeInSpeed: 600,      // Speed of fade in animation
      fadeOutSpeed: 300      // Speed of fade out animation
    },
  },
  Apply_Settings : function(new_settings){
    // Enable Controls
    if (this.DOM.Preview.val())
      this.EnableControls = true;

    // Apply Vegas-settings
    if (new_settings.vegas)
      for (var property in this.Settings.Vegas)
        if (this.Settings.Vegas.hasOwnProperty(property))
          this.Settings.Vegas[property] = new_settings.vegas[property];

    // Apply Ticker-settings
    if (new_settings.ticker)
      for (var property in this.Settings.Ticker)
        if (this.Settings.Ticker.hasOwnProperty(property))
          this.Settings.Ticker[property] = new_settings.ticker[property];

    // Add Controls in Settings for Ticker
    if (this.EnableControls)
      this.Settings.Ticker.controls=true;

    // Add Controls for Vegas
    if (this.EnableControls)
      this.Controls.Vegas.Enable();
  },

  /* Start Ticker */
  Start_Ticker : function(){
    // Get Target
    var _target = this.DOM.TickerTarget.find('ul#ticker');

    // Check for tickers
    if (_target.children().length>0){
      // Run .ticker() Plugin
      _target.ticker(this.Settings.Ticker);

      // Start Controls
      if (this.EnableControls)
        this.Controls.Ticker.Init();
    }
    else{
      // No Tickers
      console.log('No tickers...');
    }
  },

  /* Start Vegas */
  Start_Vegas : function(mode){
    // Get current Screens
    this.Settings.Vegas.slides = this.Get_Screens();

    // Check if there is any screens
    if (this.Settings.Vegas.slides)
      return console.error('No Screens...');

    // Restart
    if (mode=='restart')
      this.DOM.VegasTarget          // Restart Player Chain
        .vegas('pause')             // Pause
        .vegas('destroy')           // Destroy
        .vegas(this.Settings.Vegas) // Start New
        .vegas('play');             // Play

    // First Run
    else
      this.DOM.VegasTarget
        .vegas(this.Settings.Vegas);
  },

  /* Update-check */
  Sync : function (first_run) {
      var _request = $.ajax({
          type: "get",
          url: "/play/" + PixboPlayer.DOM.ClientID.val(),
      });
      _request.done(function() {
          var _data = JSON.parse(_request.responseText); // Fetch JSON-output
          if(first_run || PixboPlayer.DOM.UpdatedAt.val() != _data['updated_at']) { // First run / Newer data found, update player:
              var _mode = first_run ? false : 'restart'; // Tell Update() if it´s a first run or restart (Vegas needs to know)
              if (!first_run) {
                console.log('Client Updated, fetching newer data: (site: '+ PixboPlayer.DOM.ClientID.val() +', updated: '+ _data['updated_at'] +')');
              }
              PixboPlayer.Update(
                  _data['photo_list'],
                  _data['tickers'],
                  _data['updated_at'],
                  _data['settings'],
                  _mode
              );
              /*
              JSON-object structure: {
                "photo_list":[ {"image":" "}, ... ],
                "tickers":[ {"text":" "}, ... ],
                "updated_at":" ",
                "settings":{
                  "ticker":{ "pauseOnItems":10000, ... },
                  "vegas":{ "delay":10000, ... }
                }
              }
              */
          }
      });
  },

  /* Perform update */
  Update : function(new_screens, new_tickers, new_updated_at, new_settings, mode) {

      // Apply settings
      this.Apply_Settings(new_settings);

      // Replace contents in DOM for screens-list
      this.Put_Screens( new_screens );

      // Start/Restart Vegas
      this.Start_Vegas(mode);

      // Replace contents in DOM for tickers
      this.Put_Tickers( new_tickers );

      // Load/Reload Ticker
      this.Start_Ticker();

      // Replace value of updated_at
      this.DOM.UpdatedAt.val(new_updated_at);

      // Done
  },

  // Controls in Preview-mode
  Controls : {
    Vegas : {
      Element : false,
      Buttons : {
        Prev  : false,
        Next  : false,
        Play  : false,
        Pause : false,
      },

      // Show Controls
      Enable : function(){
        this.Element.style.display='block';
      },

      // Functions
      Play : function(){
        PixboPlayer.Controls.Vegas.Buttons.Play.style.display = "none";
        PixboPlayer.Controls.Vegas.Buttons.Pause.style.display = "block";
        PixboPlayer.DOM.VegasTarget.vegas('play');
      },
      Pause : function(){
        PixboPlayer.Controls.Vegas.Buttons.Play.style.display = "block";
        PixboPlayer.Controls.Vegas.Buttons.Pause.style.display = "none";
        PixboPlayer.DOM.VegasTarget.vegas('pause');
      },
      Next : function(){
        PixboPlayer.DOM.VegasTarget
          .vegas('options', 'transitionDuration', '100')                                          // Speed-up
          .vegas('next')                                                                          // Show next screen
          .vegas('options', 'transitionDuration', PixboPlayer.Settings.Vegas.transitionDuration); // Restore speed
        PixboPlayer.Controls.Vegas.Pause();                                                       // Pause
      },
      Prev : function(){
        PixboPlayer.DOM.VegasTarget
          .vegas('options', 'transitionDuration', '100')                                          // Speed-up
          .vegas('previous')                                                                      // Show previous screen
          .vegas('options', 'transitionDuration', PixboPlayer.Settings.Vegas.transitionDuration); // Restore speed
        PixboPlayer.Controls.Vegas.Pause();                                                       // Pause
      },

      Init : function (){
        // Create Elements
        this.Element = document.createElement("div");
          this.Buttons.Prev  = document.createElement("a");
          this.Buttons.Next  = document.createElement("a");
          this.Buttons.Play  = document.createElement("a");
          this.Buttons.Pause = document.createElement("a");

        // Add Classes
        this.Element.classList.add('vegas_control');
          this.Buttons.Prev.classList.add('fa');
          this.Buttons.Prev.classList.add('fa-step-backward');
          this.Buttons.Next.classList.add('fa');
          this.Buttons.Next.classList.add('fa-step-forward');
          this.Buttons.Play.classList.add('fa');
          this.Buttons.Play.classList.add('fa-play');
          this.Buttons.Pause.classList.add('fa');
          this.Buttons.Pause.classList.add('fa-pause');

        // Attach event-listeners
          this.Buttons.Prev.onclick  = this.Prev;
          this.Buttons.Next.onclick  = this.Next;
          this.Buttons.Play.onclick  = this.Play;
          this.Buttons.Pause.onclick = this.Pause;

        // CSS
          this.Buttons.Play.style.display = "none"; // Play-button hidden on start
        this.Element.style.display = "none"; // Hide Controls on start.

        // Append Elements
          this.Element.appendChild(this.Buttons.Prev);
          this.Element.appendChild(this.Buttons.Play);
          this.Element.appendChild(this.Buttons.Pause);
          this.Element.appendChild(this.Buttons.Next);
        PixboPlayer.DOM.VegasTarget.append(this.Element);
      },
    },
    Ticker : {
      Element : false,
      Buttons : {
             Prev : false,
             Next : false,
        PlayPause : false,
      },

      Init : function(){
        // This will go through the plugins output and hook into things
        DOM.TickerTarget.find('.ticker-controls').each(function(){
          // Got ticker-controls element
          PixboPlayer.Controls.Ticker.Element = this;
          this.classList.add('ticker_control');

          // Collect Buttons
          $(this).find('li').each(function(){
            if (this.classList.contains('jnt-prev'))
              PixboPlayer.Controls.Ticker.Buttons.Prev = this;
            if (this.classList.contains('jnt-next'))
              PixboPlayer.Controls.Ticker.Buttons.Next = this;
            if (this.classList.contains('jnt-play-pause'))
              PixboPlayer.Controls.Ticker.Buttons.PlayPause = this;

            // Attach event-listener
            this.onclick=function (e){
              // Timer (so that the plugin got some time to do its work before check)
              setTimeout(function(){
                // Check play/pause status when ticker-controls are being used and update icon
                if (PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.contains('paused')){
                  PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.remove('fa-pause');
                  PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.add('fa-play');
                } else {
                  PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.add('fa-pause');
                  PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.remove('fa-play');
                }
              }, 100);
            };
          });

          // Add Classes
          PixboPlayer.Controls.Ticker.Buttons.Prev.classList.add('fa');
          PixboPlayer.Controls.Ticker.Buttons.Prev.classList.add('fa-step-backward');
          PixboPlayer.Controls.Ticker.Buttons.Next.classList.add('fa');
          PixboPlayer.Controls.Ticker.Buttons.Next.classList.add('fa-step-forward');
          PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.add('fa');
          PixboPlayer.Controls.Ticker.Buttons.PlayPause.classList.add('fa-pause');

          // Order Buttons
          $( PixboPlayer.Controls.Ticker.Buttons.PlayPause ).insertBefore( PixboPlayer.Controls.Ticker.Buttons.Next );
        });
      },
    },
  },
};

/**
 * Forward function jQuery.live()
 * Wrapper for newer jQuery.on()
 * Uses optimized selector context
 * Only add if live() not already existing.
 * Information: The live() method was deprecated in jQuery version 1.7,
 *              and removed in version 1.9.
 *              Use the on() method instead.
 * Reason: Ticker-plugin uses live() @control, forward function instead of refactor plugin.
*/
if (typeof jQuery.fn.live == 'undefined' || !(jQuery.isFunction(jQuery.fn.live))) {
  jQuery.fn.extend({
      live: function (event, callback) {
         if (this.selector) {
              jQuery(document).on(event, this.selector, callback);
          }
      }
  });
}

// Start Player!
PixboPlayer.Start();
<|endoftext|>


<commit_before>var BASE_URL = "";

$(function() {
  function formatErrorMessage(resp) {
    return resp.error.message + "\n\n" + _(resp.error.exception.errors).map(function(message, code) { return message; }).join("\n");
  }

  function processErrorResponse(r) {
    try {
      var jsonResponse = $.parseJSON(r.responseText);
      alert(formatErrorMessage(jsonResponse));
    } catch (x) {
      console.log(x);
      alert(__("Unhandled server error") + x);
    }
  }

  $.ajaxSetup({
    headers: {
      accept: "application/json"
    }
  });

  $(".line .btn-add")
    .on("click",
        function() {
          var line = $(this).closest(".line");

          $("#reading-modal")
            .data("line-id",
                  line.data("line-id"))
            .find(":input[name='product_id']").val(line.data("product-id"));
          ;
        });

  $("#reading-modal")
    .on("click",
        " .btn-save:not(.disabled)",
        function() {
          var modal = $(this).closest(".modal");
          var line_id = modal.data("line-id");
          var buttons = modal.find(".modal-footer .btn");

          $.post(BASE_URL + "/mobile/line/" + line_id + "/readings",
                 {
                   reading: modal.find(":input[name='reading']").val(),
                   employees: modal.find(":input[name='employees']").val(),
                   notes: modal.find(":input[name='notes']").val(),
                   product_id: modal.find(":input[name='product_id']").val()
                 })
            .done(function() {
              document.location.reload();
            })
            .fail(function(r) {
              processErrorResponse(r);
            })
            .always(function() {
              buttons.removeClass("disabled");
            });
          ;

          buttons.addClass("disabled");
        });
});
<commit_msg>Added line name to the modal to enter a new reading<commit_after>var BASE_URL = "";

$(function() {
  function formatErrorMessage(resp) {
    return resp.error.message + "\n\n" + _(resp.error.exception.errors).map(function(message, code) { return message; }).join("\n");
  }

  function processErrorResponse(r) {
    try {
      var jsonResponse = $.parseJSON(r.responseText);
      alert(formatErrorMessage(jsonResponse));
    } catch (x) {
      console.log(x);
      alert(__("Unhandled server error") + x);
    }
  }

  $.ajaxSetup({
    headers: {
      accept: "application/json"
    }
  });

  $(".line .btn-add")
    .on("click",
        function() {
          var line = $(this).closest(".line");

		  $("#reading-modal #reading-modal-label").html("Add a new reading - Line " + line.data("line-name"));

          $("#reading-modal")
            .data("line-id",
                  line.data("line-id"))
            .find(":input[name='product_id']").val(line.data("product-id"));
          ;
        });

  $("#reading-modal")
    .on("click",
        " .btn-save:not(.disabled)",
        function() {
          var modal = $(this).closest(".modal");
          var line_id = modal.data("line-id");
          var buttons = modal.find(".modal-footer .btn");

          $.post(BASE_URL + "/mobile/line/" + line_id + "/readings",
                 {
                   reading: modal.find(":input[name='reading']").val(),
                   employees: modal.find(":input[name='employees']").val(),
                   notes: modal.find(":input[name='notes']").val(),
                   product_id: modal.find(":input[name='product_id']").val()
                 })
            .done(function() {
              document.location.reload();
            })
            .fail(function(r) {
              processErrorResponse(r);
            })
            .always(function() {
              buttons.removeClass("disabled");
            });
          ;

          buttons.addClass("disabled");
        });
});
<|endoftext|>