<commit_before>/**
 * Handles the initial creation of the workspace screen.
 * @param path - path to iframe
 * @param collectionId
 * @param menu - opens a specific menu
 * @param stopEventListener - separates the link between editor and iframe
 * @returns {boolean}
 */

function createWorkspace(path, collectionId, menu, collectionData, stopEventListener) {
    var safePath = '';
    $("#working-on").on('click', function () {
    }); // add event listener to mainNav

    if (stopEventListener) {
        document.getElementById('iframe').onload = function () {
            var browserLocation = document.getElementById('iframe').contentWindow.location.href;
            $('.browser-location').val(browserLocation);
            var iframeEvent = document.getElementById('iframe').contentWindow;
            iframeEvent.removeEventListener('click', Florence.Handler, true);
        };
        return false;
    } else {
        var currentPath = '';
        if (path) {
            currentPath = path;
            safePath = checkPathSlashes(currentPath);
        }

        Florence.globalVars.pagePath = safePath;
        if (Florence.globalVars.welsh !== true) {
            document.cookie = "lang=" + "en;path=/";
        } else {
            document.cookie = "lang=" + "cy;path=/";
        }
        Florence.refreshAdminMenu();

        var workSpace = templates.workSpace(Florence.tredegarBaseUrl + safePath);
        $('.section').html(workSpace);

        document.getElementById('iframe').onload = function () {
            detectPreviewClick();
        };

        // Set browse panel to full height to show loading icon
        $('.loader').css('margin-top', '84px');
        $('.workspace-menu').height($('.workspace-nav').height());

        // Detect changes to preview and handle accordingly
        processPreviewLoad();

        // Update preview URL on initial load of workspace
        updateBrowserURL();

        if (Florence.globalVars.welsh !== true) {
            $('#nav--workspace__welsh').empty().append('Language: English');
        } else {
            $('#nav--workspace__welsh').empty().append('Language: Welsh');
        }

        //click handlers
        $('.nav--workspace > li').click(function () {
            menu = '';
            if (Florence.Editor.isDirty) {
                swal({
                    title: "Warning",
                    text: "You have unsaved changes. Are you sure you want to continue?",
                    type: "warning",
                    showCancelButton: true,
                    confirmButtonText: "Continue",
                    cancelButtonText: "Cancel"
                }, function (result) {
                    if (result === true) {
                        Florence.Editor.isDirty = false;
                        processMenuClick(this);
                    } else {
                        return false;
                    }
                });
            } else {
                processMenuClick(this);
            }
        });

        function processMenuClick(clicked) {
            var menuItem = $(clicked);

            $('.nav--workspace li').removeClass('selected');
            menuItem.addClass('selected');

            if (menuItem.is('#browse')) {
                loadBrowseScreen(collectionId, 'click', collectionData);
            } else if (menuItem.is('#create')) {
                Florence.globalVars.pagePath = getPathName();
                var type = false;
                loadCreateScreen(Florence.globalVars.pagePath, collectionId, type, collectionData);
            } else if (menuItem.is('#edit')) {
                Florence.globalVars.pagePath = getPathName();
                loadPageDataIntoEditor(Florence.globalVars.pagePath, Florence.collection.id);
            } else if (menuItem.is('#import')) {
                loadImportScreen(Florence.collection.id);
            } else {
                loadBrowseScreen(collectionId);
            }
        }

        $('#nav--workspace__welsh').on('click', function () {
            Florence.globalVars.welsh = Florence.globalVars.welsh === false ? true : false;
            createWorkspace(Florence.globalVars.pagePath, collectionId, 'browse');
        });

        $('.workspace-menu').on('click', '.btn-browse-create', function () {
            var dest = $('.tree-nav-holder ul').find('.selected').attr('data-url');
            var spanType = $(this).parent().prev('span');
            var typeClass = spanType[0].attributes[0].nodeValue;
            var typeGroup = typeClass.match(/--(\w*)$/);
            var type = typeGroup[1];
            Florence.globalVars.pagePath = dest;
            $('.nav--workspace li').removeClass('selected');
            $("#create").addClass('selected');
            loadCreateScreen(Florence.globalVars.pagePath, collectionId, type, collectionData);
        });

        $('.workspace-menu').on('click', '.btn-browse-create-datavis', function () {
            var dest = '/visualisations';
            var type = 'visualisation';
            Florence.globalVars.pagePath = dest;
            loadCreateScreen(Florence.globalVars.pagePath, collectionId, type, collectionData);
        });

        $('.workspace-menu').on('click', '.btn-browse-edit', function () {
            var dest = $('.tree-nav-holder ul').find('.selected').attr('data-url');
            Florence.globalVars.pagePath = dest;
            $('.nav--workspace li').removeClass('selected');
            $("#edit").addClass('selected');
            loadPageDataIntoEditor(Florence.globalVars.pagePath, collectionId);
        });

        if (menu === 'edit') {
            $('.nav--workspace li').removeClass('selected');
            $("#edit").addClass('selected');
            loadPageDataIntoEditor(Florence.globalVars.pagePath, collectionId);
        } else if (menu === 'browse') {
            $('.nav--workspace li').removeClass('selected');
            $("#browse").addClass('selected');
            loadBrowseScreen(collectionId, 'click', collectionData);
        }
        //};
    }
}

// Bind click event to iframe element and run global Florence.Handler
function detectPreviewClick() {
    var iframeEvent = document.getElementById('iframe').contentWindow;
    iframeEvent.addEventListener('click', Florence.Handler, true);
}

// Full collection of functions to run on iframe load
function processPreviewLoad() {
    onIframeLoad(function (event) {
        var $iframe = $('#iframe'), // iframe element in DOM, check length later to ensure it's on the page before continuing
            $browse = $('#browse'); // 'Browse' menu tab, check later if it's selected

        if (event.data == "load" && $iframe.length) {
            processPageChange(); // Update browse tree
            updateBrowserURL(); // Update browser preview URL

            if ($browse.hasClass('selected')) {
                browseScrollPos(); // Update browse tree scroll position
            }
        }
    });
}

// Reusable iframe startload event - uses message sent up form babbage on window.load
function onIframeLoad(runFunction) {
    window.addEventListener("message", function (event) {
        runFunction(event);
    });
}

// Check whether page URL is different and then load editor or update browse tree
function processPageChange() {
    checkForPageChanged(function (newUrl) {
        var safeUrl = checkPathSlashes(newUrl),
            selectedItem = $('.workspace-browse li.selected').attr('data-url'); // Get active node in the browse tree
        Florence.globalVars.pagePath = safeUrl;

        if ($('.workspace-edit').length) {
            loadPageDataIntoEditor(safeUrl, Florence.collection.id, 'click');
            return false;
        }
        else if ($('.workspace-browse').length && selectedItem != Florence.globalVars.pagePath) {
            // Only update browse tree of on 'browse' tab and preview and active node don't already match
            treeNodeSelect(safeUrl);
            return false;
        }
    });
}

// Update the scroll position of the browse tree if selected item off screen
function browseScrollPos() {
    var $selectedItem = $('.workspace-browse li.selected'),
        $browseTree = $('.workspace-browse');

    var selectedTop = $selectedItem.offset().top,
        selectedBottom = selectedTop + $selectedItem.height(),
        browseTop = $browseTree.offset().top,
        browseBottom = browseTop + $browseTree.height(),
        navHeight = $('.nav').height();

    if (selectedTop < browseTop || selectedBottom > browseBottom) {
        var newPos = selectedTop - navHeight;
        $browseTree.scrollTop(newPos);
    }
}

function updateBrowserURL() {
    $('.browser-location').val(Florence.tredegarBaseUrl + Florence.globalVars.pagePath);
}

<commit_msg>Use given uri if its defined instead of global.<commit_after>/**
 * Handles the initial creation of the workspace screen.
 * @param path - path to iframe
 * @param collectionId
 * @param menu - opens a specific menu
 * @param stopEventListener - separates the link between editor and iframe
 * @returns {boolean}
 */

function createWorkspace(path, collectionId, menu, collectionData, stopEventListener) {
    var safePath = '';
    $("#working-on").on('click', function () {
    }); // add event listener to mainNav

    if (stopEventListener) {
        document.getElementById('iframe').onload = function () {
            var browserLocation = document.getElementById('iframe').contentWindow.location.href;
            $('.browser-location').val(browserLocation);
            var iframeEvent = document.getElementById('iframe').contentWindow;
            iframeEvent.removeEventListener('click', Florence.Handler, true);
        };
        return false;
    } else {
        var currentPath = '';
        if (path) {
            currentPath = path;
            safePath = checkPathSlashes(currentPath);
        }

        Florence.globalVars.pagePath = safePath;
        if (Florence.globalVars.welsh !== true) {
            document.cookie = "lang=" + "en;path=/";
        } else {
            document.cookie = "lang=" + "cy;path=/";
        }
        Florence.refreshAdminMenu();

        var workSpace = templates.workSpace(Florence.tredegarBaseUrl + safePath);
        $('.section').html(workSpace);

        document.getElementById('iframe').onload = function () {
            detectPreviewClick();
        };

        // Set browse panel to full height to show loading icon
        $('.loader').css('margin-top', '84px');
        $('.workspace-menu').height($('.workspace-nav').height());

        // Detect changes to preview and handle accordingly
        processPreviewLoad();

        // Update preview URL on initial load of workspace
        updateBrowserURL(path);

        if (Florence.globalVars.welsh !== true) {
            $('#nav--workspace__welsh').empty().append('Language: English');
        } else {
            $('#nav--workspace__welsh').empty().append('Language: Welsh');
        }

        //click handlers
        $('.nav--workspace > li').click(function () {
            menu = '';
            if (Florence.Editor.isDirty) {
                swal({
                    title: "Warning",
                    text: "You have unsaved changes. Are you sure you want to continue?",
                    type: "warning",
                    showCancelButton: true,
                    confirmButtonText: "Continue",
                    cancelButtonText: "Cancel"
                }, function (result) {
                    if (result === true) {
                        Florence.Editor.isDirty = false;
                        processMenuClick(this);
                    } else {
                        return false;
                    }
                });
            } else {
                processMenuClick(this);
            }
        });

        function processMenuClick(clicked) {
            var menuItem = $(clicked);

            $('.nav--workspace li').removeClass('selected');
            menuItem.addClass('selected');

            if (menuItem.is('#browse')) {
                loadBrowseScreen(collectionId, 'click', collectionData);
            } else if (menuItem.is('#create')) {
                Florence.globalVars.pagePath = getPathName();
                var type = false;
                loadCreateScreen(Florence.globalVars.pagePath, collectionId, type, collectionData);
            } else if (menuItem.is('#edit')) {
                Florence.globalVars.pagePath = getPathName();
                loadPageDataIntoEditor(Florence.globalVars.pagePath, Florence.collection.id);
            } else if (menuItem.is('#import')) {
                loadImportScreen(Florence.collection.id);
            } else {
                loadBrowseScreen(collectionId);
            }
        }

        $('#nav--workspace__welsh').on('click', function () {
            Florence.globalVars.welsh = Florence.globalVars.welsh === false ? true : false;
            createWorkspace(Florence.globalVars.pagePath, collectionId, 'browse');
        });

        $('.workspace-menu').on('click', '.btn-browse-create', function () {
            var dest = $('.tree-nav-holder ul').find('.selected').attr('data-url');
            var spanType = $(this).parent().prev('span');
            var typeClass = spanType[0].attributes[0].nodeValue;
            var typeGroup = typeClass.match(/--(\w*)$/);
            var type = typeGroup[1];
            Florence.globalVars.pagePath = dest;
            $('.nav--workspace li').removeClass('selected');
            $("#create").addClass('selected');
            loadCreateScreen(Florence.globalVars.pagePath, collectionId, type, collectionData);
        });

        $('.workspace-menu').on('click', '.btn-browse-create-datavis', function () {
            var dest = '/visualisations';
            var type = 'visualisation';
            Florence.globalVars.pagePath = dest;
            loadCreateScreen(Florence.globalVars.pagePath, collectionId, type, collectionData);
        });

        $('.workspace-menu').on('click', '.btn-browse-edit', function () {
            var dest = $('.tree-nav-holder ul').find('.selected').attr('data-url');
            Florence.globalVars.pagePath = dest;
            $('.nav--workspace li').removeClass('selected');
            $("#edit").addClass('selected');
            loadPageDataIntoEditor(Florence.globalVars.pagePath, collectionId);
        });

        if (menu === 'edit') {
            $('.nav--workspace li').removeClass('selected');
            $("#edit").addClass('selected');
            loadPageDataIntoEditor(Florence.globalVars.pagePath, collectionId);
        } else if (menu === 'browse') {
            $('.nav--workspace li').removeClass('selected');
            $("#browse").addClass('selected');
            loadBrowseScreen(collectionId, 'click', collectionData);
        }
        //};
    }
}

// Bind click event to iframe element and run global Florence.Handler
function detectPreviewClick() {
    var iframeEvent = document.getElementById('iframe').contentWindow;
    iframeEvent.addEventListener('click', Florence.Handler, true);
}

// Full collection of functions to run on iframe load
function processPreviewLoad() {
    onIframeLoad(function (event) {
        var $iframe = $('#iframe'), // iframe element in DOM, check length later to ensure it's on the page before continuing
            $browse = $('#browse'); // 'Browse' menu tab, check later if it's selected

        if (event.data == "load" && $iframe.length) {
            processPageChange(); // Update browse tree
            updateBrowserURL(); // Update browser preview URL

            if ($browse.hasClass('selected')) {
                browseScrollPos(); // Update browse tree scroll position
            }
        }
    });
}

// Reusable iframe startload event - uses message sent up form babbage on window.load
function onIframeLoad(runFunction) {
    window.addEventListener("message", function (event) {
        runFunction(event);
    });
}

// Check whether page URL is different and then load editor or update browse tree
function processPageChange() {
    checkForPageChanged(function (newUrl) {
        var safeUrl = checkPathSlashes(newUrl),
            selectedItem = $('.workspace-browse li.selected').attr('data-url'); // Get active node in the browse tree
        Florence.globalVars.pagePath = safeUrl;

        if ($('.workspace-edit').length) {
            loadPageDataIntoEditor(safeUrl, Florence.collection.id, 'click');
            return false;
        }
        else if ($('.workspace-browse').length && selectedItem != Florence.globalVars.pagePath) {
            // Only update browse tree of on 'browse' tab and preview and active node don't already match
            treeNodeSelect(safeUrl);
            return false;
        }
    });
}

// Update the scroll position of the browse tree if selected item off screen
function browseScrollPos() {
    var $selectedItem = $('.workspace-browse li.selected'),
        $browseTree = $('.workspace-browse');

    var selectedTop = $selectedItem.offset().top,
        selectedBottom = selectedTop + $selectedItem.height(),
        browseTop = $browseTree.offset().top,
        browseBottom = browseTop + $browseTree.height(),
        navHeight = $('.nav').height();

    if (selectedTop < browseTop || selectedBottom > browseBottom) {
        var newPos = selectedTop - navHeight;
        $browseTree.scrollTop(newPos);
    }
}

function updateBrowserURL(url) {

    if(!url) {
        url =Florence.globalVars.pagePath;
    }

    $('.browser-location').val(Florence.tredegarBaseUrl + url);
}

<|endoftext|>


<commit_before>import path from "path"
import process from "process"
import webpack from "webpack"
import chokidar from "chokidar"

import HotClient from "./HotClient"
import HotServer from "./HotServer"
import ConfigFactory from "./ConfigFactory"
import { createNotification } from "./util"

const CWD = process.cwd()

class HotServers
{
  constructor(root = CWD)
  {
    this.root = root

    // Bind our functions to avoid any scope/closure issues.
    this.start = this.start.bind(this)
    this.restart = this.restart.bind(this)

    this._configureHotClient = this._configureHotClient.bind(this)
    this._configureHotServer = this._configureHotServer.bind(this)

    this.clientBundle = null
    this.clientCompiler = null
    this.serverBundle = null
    this.serverCompiler = null

    const configPath = path.resolve(__dirname, "ConfigFactory.js")

    // If we receive a kill cmd then we will first try to dispose our listeners.
    process.on("SIGTERM", () => this.dispose().then(() => process.exit(0)))
  }

  start()
  {
    try
    {
      console.log("Compiling client...")
      this.clientCompiler = webpack(ConfigFactory("client", "development"))

      console.log("Compiling server...")
      this.serverCompiler = webpack(ConfigFactory("server", "development"))
    }
    catch (err) {
      createNotification({
        title: "Webpack",
        message: "Config invalid, check console for error"
      })
      console.error(err)
      return
    }

    this._configureHotClient()
    this._configureHotServer()
  }

  dispose()
  {
    // We want to forcefully close our servers (passing true) which will hard
    // kill any existing connections.  We don't care about them running as we
    // need to restart both the client and server bundles.
    const safeDisposeClient = () =>
      (this.clientBundle ? this.clientBundle.dispose(true) : Promise.resolve())
    const safeDisposeServer = () =>
      (this.serverBundle ? this.serverBundle.dispose(true) : Promise.resolve())

    return safeDisposeClient().then(safeDisposeServer)
  }

  restart() {
    const clearWebpackConfigsCache = () => {
      Object.keys(require.cache).forEach((modulePath) => {
        if (~modulePath.indexOf("webpack")) {
          delete require.cache[modulePath]
        }
      })
    }

    this.dispose()
      .then(clearWebpackConfigsCache)
      .then(this.start, (err) => console.log(err))
      .catch((err) => console.log(err))
  }

  _configureHotClient() {
    this.clientCompiler.plugin("done", (stats) => {
      if (stats.hasErrors()) {
        createNotification({
          title: "Client",
          message: "Build failed, check console for error"
        })
        console.log(stats.toString())
      } else {
        createNotification({
          title: "Client",
          message: "Built"
        })
      }
    })

    this.clientBundle = new HotClient(this.clientCompiler)
  }

  _configureHotServer() {
    const compileHotServer = () => {
      console.log("compiling hot server")
      const runCompiler = () => this.serverCompiler.run(() => undefined)

      // Shut down any existing running server if necessary before starting the
      // compile, else just compile.
      if (this.serverBundle) {
        this.serverBundle.dispose(true).then(runCompiler)
      } else {
        runCompiler()
      }
    }

    this.clientCompiler.plugin("done", (stats) => {
      if (!stats.hasErrors()) {
        compileHotServer()
      }
    })

    this.serverCompiler.plugin("done", (stats) => {
      if (stats.hasErrors()) {
        createNotification({
          title: "Server",
          message: "Build failed, check console for error"
        })
        console.log(stats.toString())
        return
      }

      createNotification({
        title: "Server",
        message: "Built"
      })

      // Make sure our newly built server bundles aren't in the module cache.
      Object.keys(require.cache).forEach((modulePath) => {
        if (~modulePath.indexOf(this.serverCompiler.options.output.path)) {
          delete require.cache[modulePath]
        }
      })

      this.serverBundle = new HotServer(this.serverCompiler)
    })

    // Now we will configure `chokidar` to watch our server specific source folder.
    // Any changes will cause a rebuild of the server bundle.
    this.watcher = chokidar.watch([ path.resolve(this.root, "./src/server") ])
    this.watcher.on("ready", () => {
      this.watcher
        .on("add", compileHotServer)
        .on("addDir", compileHotServer)
        .on("change", compileHotServer)
        .on("unlink", compileHotServer)
        .on("unlinkDir", compileHotServer)
    })
  }
}

export default HotServers
<commit_msg>Cleanup<commit_after>import { resolve } from "path"
import webpack from "webpack"
import chokidar from "chokidar"

import HotClient from "./HotClient"
import HotServer from "./HotServer"
import ConfigFactory from "./ConfigFactory"
import { createNotification } from "./util"

const CWD = process.cwd()

class HotServers
{
  constructor(root = CWD)
  {
    this.root = root

    // Bind our functions to avoid any scope/closure issues.
    this.start = this.start.bind(this)
    this.restart = this.restart.bind(this)

    this._configureHotClient = this._configureHotClient.bind(this)
    this._configureHotServer = this._configureHotServer.bind(this)

    this.clientBundle = null
    this.clientCompiler = null
    this.serverBundle = null
    this.serverCompiler = null

    // If we receive a kill cmd then we will first try to dispose our listeners.
    process.on("SIGTERM", () => this.dispose().then(() => process.exit(0)))
  }

  start()
  {
    try
    {
      console.log("Compiling client...")
      this.clientCompiler = webpack(ConfigFactory("client", "development"))

      console.log("Compiling server...")
      this.serverCompiler = webpack(ConfigFactory("server", "development"))
    }
    catch (err) {
      createNotification({
        title: "Webpack",
        message: "Config invalid, check console for error"
      })
      console.error(err)
      return
    }

    this._configureHotClient()
    this._configureHotServer()
  }

  dispose()
  {
    // We want to forcefully close our servers (passing true) which will hard
    // kill any existing connections.  We don't care about them running as we
    // need to restart both the client and server bundles.
    const safeDisposeClient = () =>
      (this.clientBundle ? this.clientBundle.dispose(true) : Promise.resolve())
    const safeDisposeServer = () =>
      (this.serverBundle ? this.serverBundle.dispose(true) : Promise.resolve())

    return safeDisposeClient().then(safeDisposeServer)
  }

  restart() {
    const clearWebpackConfigsCache = () => {
      Object.keys(require.cache).forEach((modulePath) => {
        if (~modulePath.indexOf("webpack")) {
          delete require.cache[modulePath]
        }
      })
    }

    this.dispose()
      .then(clearWebpackConfigsCache)
      .then(this.start, (err) => console.log(err))
      .catch((err) => console.log(err))
  }

  _configureHotClient() {
    this.clientCompiler.plugin("done", (stats) => {
      if (stats.hasErrors()) {
        createNotification({
          title: "Client",
          message: "Build failed, check console for error"
        })
        console.log(stats.toString())
      }
    })

    this.clientBundle = new HotClient(this.clientCompiler)
  }

  _configureHotServer() {
    const compileHotServer = () => {
      const runCompiler = () => this.serverCompiler.run(() => undefined)

      // Shut down any existing running server if necessary before starting the
      // compile, else just compile.
      if (this.serverBundle) {
        this.serverBundle.dispose(true).then(runCompiler)
      } else {
        runCompiler()
      }
    }

    this.clientCompiler.plugin("done", (stats) => {
      if (!stats.hasErrors()) {
        compileHotServer()
      }
    })

    this.serverCompiler.plugin("done", (stats) => {
      if (stats.hasErrors()) {
        createNotification({
          title: "Server",
          message: "Build failed, check console for error"
        })
        console.log(stats.toString())
        return
      }

      // Make sure our newly built server bundles aren't in the module cache.
      Object.keys(require.cache).forEach((modulePath) => {
        if (~modulePath.indexOf(this.serverCompiler.options.output.path)) {
          delete require.cache[modulePath]
        }
      })

      this.serverBundle = new HotServer(this.serverCompiler)
    })

    // Now we will configure `chokidar` to watch our server specific source folder.
    // Any changes will cause a rebuild of the server bundle.
    this.watcher = chokidar.watch([ resolve(this.root, "./src/server") ])
    this.watcher.on("ready", () => {
      this.watcher
        .on("add", compileHotServer)
        .on("addDir", compileHotServer)
        .on("change", compileHotServer)
        .on("unlink", compileHotServer)
        .on("unlinkDir", compileHotServer)
    })
  }
}

export default HotServers
<|endoftext|>


<commit_before>(function (window, PUBNUB) {
  //"use strict";

  // Remove vendor prefixes
  var IS_CHROME = !!window.webkitRTCPeerConnection,
      RTCPeerConnection,
      RTCIceCandidate,
      RTCSessionDescription;

  if (IS_CHROME) {
    RTCPeerConnection = webkitRTCPeerConnection;
    RTCIceCandidate = window.RTCIceCandidate;
    RTCSessionDescription = window.RTCSessionDescription;
  } else {
    RTCPeerConnection = mozRTCPeerConnection;
    RTCIceCandidate = mozRTCIceCandidate;
    RTCSessionDescription = mozRTCSessionDescription;
  }

  // Global error handling function
  function error() {
    console['error'].apply(console, arguments);
  }

  // Global info logging
  var isDebug = true;
  function debug() {
    if (isDebug === true) {
      console['log'].apply(console, arguments);
    }
  }

  // Grabs an attribute from a node.
  function attr( node, attribute, value ) {
      if (value) {
        node.setAttribute( attribute, value );
      }
      else {
        return node && node.getAttribute && node.getAttribute(attribute);
      }
  }

  // Extend function for adding to existing objects
  function extend(obj, other) {
    for(var key in other) {
      obj[key] = other[key];
    }
    return obj;
  }

  // Putting UUID function here to work around non-exposed ID issues.
  function generateUUID() {
    var u = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,
    function(c) {
        var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);
        return v.toString(16);
    });
    return u;
  }

  function transformOutgoingSdp(sdp) {
    //var splitted = sdp.split("b=AS:30");
    //var newSDP = splitted[0] + "b=AS:1638400" + splitted[1];
    //return newSDP;
    return sdp;
  }

  function extendAPI(PUBNUB, uuid) {
    // Store out API so we can extend it on all instances.
    var API = {},
        PREFIX = "pn_",               // Prefix for subscribe channels
        PEER_CONNECTIONS = {},        // Connection storage by uuid
        RTC_CONFIGURATION = {
          iceServers: [
            { 'url': (IS_CHROME ? 'stun:stun.l.google.com:19302' : 'stun:23.21.150.121') }
          ]
        },     // Global config for RTC's
        PC_OPTIONS = (IS_CHROME ? {
          optional: [
          { RtpDataChannels: true }
          ]
        } : {}),
        UUID = uuid,                  // The current user's UUID
        PUBLISH_QUEUE = {},           // The queue of messages to send by UUID
        CONNECTED = false,            // If we have connected to the personal channel yet
        CONNECTION_QUEUE = [],        // Any createP2PConnection calls we get before we connect
        PUBLISH_TYPE = {              // Publish type enum
          STREAM: 1,
          MESSAGE: 2
        };

    // Expose PUBNUB UUID (Need to fix this in core)
    PUBNUB['UUID'] = uuid;

    // SignalingChannel
    // The signaling channel handles sending data to and from a specific user channel.
    function SignalingChannel(pubnub, selfUuid, otherUuid) {
      this.selfUuid = selfUuid;
      this.otherUuid = otherUuid;
      // The send function is here so we do not count a reference to PubNub preventing its destruction.
      this.send = function (message) {
        message.uuid = selfUuid;
        message = JSON.stringify(message);
        debug("Sending", message, otherUuid);
        pubnub.publish({
          channel: PREFIX + otherUuid,
          message: message
        });
      };
    }

    function personalChannelCallback(message) {
      message = JSON.parse(message);
      debug("Got message", message);

      if (message.uuid != null) {
        if (message.uuid === UUID) {
          return;
        }

        var connected = PEER_CONNECTIONS[message.uuid] != null;

        // Setup the connection if we do not have one already.
        if (connected === false) {
          PUBNUB.createP2PConnection(message.uuid, false);
        }

        var connection = PEER_CONNECTIONS[message.uuid];

        if (message.sdp != null) {
          if (connection.createdOffer === true && message.sdp.type === 'offer') {
            connection.rand = Math.random() * 10000;
            connection.signalingChannel.send({
              rand: connection.rand
            });
            return;
          }

          connection.connection.setRemoteDescription(new RTCSessionDescription(message.sdp), function () {
            // Add ice candidates we might have gotten early.
            for (var i = 0; i < connection.candidates; i++) {
              connection.connection.addIceCandidate(new RTCIceCandidate(connection.candidates[i]));
              connection.candidates = [];
            }

            // If we did not create the offer then create the answer.
            if (connection.connection.signalingState === 'have-remote-offer') {
              connection.connection.createAnswer(function (description) {
                PUBNUB.gotDescription(description, connection);
              }, function (err) {
                // Connection failed, so delete it from the table
                delete PEER_CONNECTIONS[message.uuid];
                error("Error creating answer: ", err);
              });
            }
          }, function (err) {
            // Maybe notify the peer that we can't communicate
            error("Error setting remote description: ", err);
          });
        } else if (message.rand) {
          if (parseInt(message.rand, 10) < connection.rand) {
            PEER_CONNECTIONS[message.uuid] = null;
            PUBNUB.createP2PConnection(message.uuid);
          }
        } else {
          if (connection.connection.remoteDescription != null && connection.connection.iceConnectionState !== "connected") {
            connection.connection.addIceCandidate(new RTCIceCandidate(message.candidate));
          } else {
            // This is to prevent adding ice candidates before the remote description
            connection.candidates.push(message.candidate);
          }
        }
      }
    }

    // Subscribe to our own personal channel to listen for data.
    PUBNUB.subscribe({
      channel: PREFIX + uuid,
      connect: function () {
        CONNECTED = true;

        for (var i = 0; i < CONNECTION_QUEUE.length; i++) {
          var args = CONNECTION_QUEUE[i];
          PUBNUB.gotDescription.apply(PUBNUB, args);
        }

        CONNECTION_QUEUE = [];
      },
      callback: personalChannelCallback
    });

    // PUBNUB._gotDescription
    // This is the handler for when we get a SDP description from the WebRTC API.
    API['gotDescription'] = function (description, connection) {
      /***
       * CHROME HACK TO GET AROUND BANDWIDTH LIMITATION ISSUES
       ***/
      if (IS_CHROME) {
        description.sdp = transformOutgoingSdp(description.sdp);
      }
      connection.connection.setLocalDescription(description);

      if (CONNECTED === false) {
        CONNECTION_QUEUE.push([description, connection]);
      } else {
        connection.signalingChannel.send({
          "sdp": description
        });
      }
    };

    // PUBNUB.createP2PConnection
    // Signals and creates a P2P connection between two users.
    API['createP2PConnection'] = function (uuid, offer) {
      if (PEER_CONNECTIONS[uuid] == null) {
        var pc = new RTCPeerConnection(RTC_CONFIGURATION, PC_OPTIONS),
            signalingChannel = new SignalingChannel(this, UUID, uuid),
            self = this;

        var onDataChannelCreated = function (event) {
          PEER_CONNECTIONS[uuid].dataChannel = event.channel;

          PEER_CONNECTIONS[uuid].dataChannel.onmessage = function (event) {
            var data = event.data;

            // Try to automagically parse JSON data
            try {
              data = JSON.parse(event.data);
            } catch (exception) {
              // Do nothing
            }

            if (PEER_CONNECTIONS[uuid].callback) {
              PEER_CONNECTIONS[uuid].callback(data, event);
            } else {
              // Store it in the history so the user can still get to it
              PEER_CONNECTIONS[uuid].history.push(data);
            }
          };

          PEER_CONNECTIONS[uuid].connection.onaddstream = function (event) {
            if (PEER_CONNECTIONS[uuid].stream) {
              PEER_CONNECTIONS[uuid].stream(event.data, event);
            } else {
              // Store it in the history so the user can still get to it
              PEER_CONNECTIONS[uuid].history.push(event.data);
            }
          };

          event.channel.onopen = function () {
            PEER_CONNECTIONS[uuid].connected = true;
            self._peerPublish(uuid);
          };
        };
        pc.ondatachannel = onDataChannelCreated;

        pc.onicecandidate = function (event) {
          // TODO: Figure out why we get a null candidate
          if (event.candidate != null) {
            signalingChannel.send({ "candidate": event.candidate });
          }
        };

        pc.oniceconnectionstatechange = function () {
          if (pc.iceConnectionState === "connected") {
            // Nothing for now
          }
        };

        PUBLISH_QUEUE[uuid] = [];

        PEER_CONNECTIONS[uuid] = {
          connection: pc,
          candidates: [],
          connected: false,
          createdOffer: offer !== false,
          history: [],
          signalingChannel: signalingChannel
        };

        if (offer !== false) {
          var dc = pc.createDataChannel("pubnub", (IS_CHROME ? { reliable: false } : {}));
          onDataChannelCreated({
            channel: dc
          });

          pc.createOffer(function (description) {
            self.gotDescription(description, PEER_CONNECTIONS[uuid]);
          }, function (err) {
            // Connection failed, so delete it from the table
            delete PEER_CONNECTIONS[uuid];
            error(err);
          });
        }
      } else {
        debug("Trying to connect to already connected user: " + uuid);
      }
    };

    // Helper function for sending messages with different types.
    function handleMessage(connection, message) {
      if (message.type === PUBLISH_TYPE.STREAM) {
        connection.connection.addStream(message.stream);
      } else if (message.type === PUBLISH_TYPE.MESSAGE) {
        // Convert to JSON automagically
        if (typeof message.message === "object") {
          message.message = JSON.stringify(message.message);
        }

        connection.dataChannel.send(message.message);
      } else {
        error("Unrecognized RTC message type: " + message.type);
      }
    }

    // PUBNUB._peerPublish
    // Handles requesting a peer connection and emptying the queue when connected.
    API['_peerPublish'] = function (uuid) {
      if (PUBLISH_QUEUE[uuid] && PUBLISH_QUEUE[uuid].length > 0) {
        if (PEER_CONNECTIONS[uuid].connected === true) {
          handleMessage(PEER_CONNECTIONS[uuid], PUBLISH_QUEUE[uuid].shift());
          this._peerPublish(uuid);
        } else {
          // Not connected yet so just sit tight!
        }
      } else {
        // Nothing to publish
        return;
      }
    };

    // PUBNUB.publish overload
    API['publish'] = (function (_super) {
      return function (options) {
        if (options == null) {
          error("You must send an object when using PUBNUB.publish!");
        }

        if (options.user != null) {
          // Setup the connection if it does not exist
          if (PEER_CONNECTIONS[options.user] == null) {
            PUBNUB.createP2PConnection(options.user);
          }

          if (options.stream != null) {
            PUBLISH_QUEUE[options.user].push({
              type: PUBLISH_TYPE.STREAM,
              stream: options.stream
            });
            //PEER_CONNECTIONS[options.user].connection.addStream(options.stream);
          } else if (options.message != null) {
            PUBLISH_QUEUE[options.user].push({
              type: PUBLISH_TYPE.MESSAGE,
              message: options.message
            });
            //PEER_CONNECTIONS[options.user].dataChannel.send(options.message);
          } else {
            error("Stream or message key not found in argument object. One or the other must be provided for RTC publish calls!");
          }

          this._peerPublish(options.user);
        } else {
          _super.apply(this, arguments);
        }
      };
    })(PUBNUB['publish']);

    // PUBNUB.subscribe overload
    API['subscribe'] = (function (_super) {
      return function (options) {
        if (options == null) {
          error("You must send an object when using PUBNUB.subscribe!");
        }

        if (options.user != null) {
          // Setup the connection if it does not exist
          if (PEER_CONNECTIONS[options.user] == null) {
            PUBNUB.createP2PConnection(options.user);
          }

          var connection = PEER_CONNECTIONS[options.user];
          debug(PEER_CONNECTIONS, options.user, connection);

          if (options.stream) {
            // Setup the stream added listener
            connection.stream = options.stream;
          }

          if (options.callback) {
            // Setup the data channel callback listener
            connection.callback = options.callback;
          }

          // Replay the backfilled messages if they exist
          debug("Subscribing!", connection.history);
          if (connection.history.length > 0) {
            for (var i = 0; i < connection.history.length; i++) {
              var message = connection.history[i];

              if (options.callback) {
                options.callback(message);
              }
            }
          }
        } else {
          _super.apply(this, arguments);
        }
      };
    })(PUBNUB['subscribe']);

    // PUBNUB.history overload
    API['history'] = (function (_super) {
      return function (options) {
        if (options == null) {
          error("You must send an object when using PUBNUB.history!");
        }

        if (options.user != null) {
          if (options.callback) {
            var history = PEER_CONNECTIONS[options.user].history || [[]];

            options.callback([history]);
          } else {
            error("No callback provided for PUBNUB.history");
          }
        } else {
          return _super.apply(this, arguments);
        }
      };
    })(PUBNUB['history']);

    return extend(PUBNUB, API);
  }

  // PUBNUB init overload
  PUBNUB['init'] = (function (_super) {
    return function (options) {
      // Grab the UUID
      var uuid = options.uuid || generateUUID();
      options.uuid = uuid;

      // Create pubnub object
      debug(options);
      var pubnub = _super.call(this, options);

      // Extend the WebRTC API
      pubnub = extendAPI(pubnub, uuid);
      return pubnub;
    };
  })(PUBNUB['init']);

  var pdiv = document.querySelector("#pubnub") || {};

  // CREATE A PUBNUB GLOBAL OBJECT
  window.PUBNUB = PUBNUB.init({
      'notest'        : 1,
      'publish_key'   : attr( pdiv, 'pub-key' ),
      'subscribe_key' : attr( pdiv, 'sub-key' ),
      'ssl'           : !document.location.href.indexOf('https') ||
                        attr( pdiv, 'ssl' ) === 'on',
      'origin'        : attr( pdiv, 'origin' ),
      'uuid'          : attr( pdiv, 'uuid' )
  });

})(window, PUBNUB);
<commit_msg>Add back SDP hack and create more robust connection negotiation<commit_after>(function (window, PUBNUB) {
  //"use strict";

  // Remove vendor prefixes
  var IS_CHROME = !!window.webkitRTCPeerConnection,
      RTCPeerConnection,
      RTCIceCandidate,
      RTCSessionDescription;

  if (IS_CHROME) {
    RTCPeerConnection = webkitRTCPeerConnection;
    RTCIceCandidate = window.RTCIceCandidate;
    RTCSessionDescription = window.RTCSessionDescription;
  } else {
    RTCPeerConnection = mozRTCPeerConnection;
    RTCIceCandidate = mozRTCIceCandidate;
    RTCSessionDescription = mozRTCSessionDescription;
  }

  // Global error handling function
  function error() {
    console['error'].apply(console, arguments);
  }

  // Global info logging
  var isDebug = true;
  function debug() {
    if (isDebug === true) {
      console['log'].apply(console, arguments);
    }
  }

  // Grabs an attribute from a node.
  function attr( node, attribute, value ) {
      if (value) {
        node.setAttribute( attribute, value );
      }
      else {
        return node && node.getAttribute && node.getAttribute(attribute);
      }
  }

  // Extend function for adding to existing objects
  function extend(obj, other) {
    for(var key in other) {
      obj[key] = other[key];
    }
    return obj;
  }

  // Putting UUID function here to work around non-exposed ID issues.
  function generateUUID() {
    var u = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,
    function(c) {
        var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);
        return v.toString(16);
    });
    return u;
  }

  function transformOutgoingSdp(sdp) {
    var splitted = sdp.split("b=AS:30");
    var newSDP = splitted[0] + "b=AS:1638400" + splitted[1];
    return newSDP;
  }

  function extendAPI(PUBNUB, uuid) {
    // Store out API so we can extend it on all instances.
    var API = {},
        PREFIX = "pn_",               // Prefix for subscribe channels
        PEER_CONNECTIONS = {},        // Connection storage by uuid
        RTC_CONFIGURATION = {
          iceServers: [
            { 'url': (IS_CHROME ? 'stun:stun.l.google.com:19302' : 'stun:23.21.150.121') }
          ]
        },     // Global config for RTC's
        PC_OPTIONS = (IS_CHROME ? {
          optional: [
          { RtpDataChannels: true }
          ]
        } : {}),
        UUID = uuid,                  // The current user's UUID
        PUBLISH_QUEUE = {},           // The queue of messages to send by UUID
        CONNECTED = false,            // If we have connected to the personal channel yet
        CONNECTION_QUEUE = [],        // Any createP2PConnection calls we get before we connect
        PUBLISH_TYPE = {              // Publish type enum
          STREAM: 1,
          MESSAGE: 2
        };

    // Expose PUBNUB UUID (Need to fix this in core)
    PUBNUB['UUID'] = uuid;

    // SignalingChannel
    // The signaling channel handles sending data to and from a specific user channel.
    function SignalingChannel(pubnub, selfUuid, otherUuid) {
      this.selfUuid = selfUuid;
      this.otherUuid = otherUuid;
      // The send function is here so we do not count a reference to PubNub preventing its destruction.
      this.send = function (message) {
        message.uuid = selfUuid;
        message = JSON.stringify(message);
        debug("Sending", message, otherUuid);
        pubnub.publish({
          channel: PREFIX + otherUuid,
          message: message
        });
      };
    }

    function personalChannelCallback(message) {
      message = JSON.parse(message);
      debug("Got message", message);

      if (message.uuid != null) {
        if (message.uuid === UUID) {
          return;
        }

        var connected = PEER_CONNECTIONS[message.uuid] != null;

        //// Setup the connection if we do not have one already.
        if (connected === false) {
          PUBNUB.createP2PConnection(message.uuid, false);
        }

        var connection = PEER_CONNECTIONS[message.uuid];

        if (message.sdp != null) {
          connection.connection.setRemoteDescription(new RTCSessionDescription(message.sdp), function () {
            // Add ice candidates we might have gotten early.
            for (var i = 0; i < connection.candidates; i++) {
              connection.connection.addIceCandidate(new RTCIceCandidate(connection.candidates[i]));
              connection.candidates = [];
            }

            // If we did not create the offer then create the answer.
            if (connection.connection.signalingState === 'have-remote-offer') {
              connection.connection.createAnswer(function (description) {
                PUBNUB.gotDescription(description, connection);
              }, function (err) {
                // Connection failed, so delete it from the table
                delete PEER_CONNECTIONS[message.uuid];
                error("Error creating answer: ", err);
              });
            }
          }, function (err) {
            // Maybe notify the peer that we can't communicate
            error("Error setting remote description: ", err);
          });
        } else if (message.initiation === true) {
          PUBNUB.createP2PConnection(message.uuid, true);
        } else {
          if (connection.connection.remoteDescription != null && connection.connection.iceConnectionState !== "connected") {
            connection.connection.addIceCandidate(new RTCIceCandidate(message.candidate));
          }
          else {
            // This is to prevent adding ice candidates before the remote description
            connection.candidates.push(message.candidate);
          }
        }
      }
    }

    // Subscribe to our own personal channel to listen for data.
    PUBNUB.subscribe({
      channel: PREFIX + uuid,
      connect: function () {
        CONNECTED = true;

        for (var i = 0; i < CONNECTION_QUEUE.length; i++) {
          var args = CONNECTION_QUEUE[i];
          PUBNUB.gotDescription.apply(PUBNUB, args);
        }

        CONNECTION_QUEUE = [];
      },
      callback: personalChannelCallback
    });

    // PUBNUB._gotDescription
    // This is the handler for when we get a SDP description from the WebRTC API.
    API['gotDescription'] = function (description, connection) {
      /***
       * CHROME HACK TO GET AROUND BANDWIDTH LIMITATION ISSUES
       ***/
      if (IS_CHROME) {
        description.sdp = transformOutgoingSdp(description.sdp);
      }
      connection.connection.setLocalDescription(description);

      if (CONNECTED === false) {
        CONNECTION_QUEUE.push([description, connection]);
      } else {
        connection.signalingChannel.send({
          "sdp": description
        });
      }
    };

    // PUBNUB.createP2PConnection
    // Signals and creates a P2P connection between two users.
    API['createP2PConnection'] = function (uuid, offer) {
      if (PEER_CONNECTIONS[uuid] == null) {
        var pc = new RTCPeerConnection(RTC_CONFIGURATION, PC_OPTIONS),
            signalingChannel = new SignalingChannel(this, UUID, uuid),
            self = this;

        var onDataChannelCreated = function (event) {
          PEER_CONNECTIONS[uuid].dataChannel = event.channel;

          PEER_CONNECTIONS[uuid].dataChannel.onmessage = function (event) {
            var data = event.data;

            // Try to automagically parse JSON data
            try {
              data = JSON.parse(event.data);
            } catch (exception) {
              // Do nothing
            }

            if (PEER_CONNECTIONS[uuid].callback) {
              PEER_CONNECTIONS[uuid].callback(data, event);
            } else {
              // Store it in the history so the user can still get to it
              PEER_CONNECTIONS[uuid].history.push(data);
            }
          };

          PEER_CONNECTIONS[uuid].connection.onaddstream = function (event) {
            if (PEER_CONNECTIONS[uuid].stream) {
              PEER_CONNECTIONS[uuid].stream(event.data, event);
            } else {
              // Store it in the history so the user can still get to it
              PEER_CONNECTIONS[uuid].history.push(event.data);
            }
          };

          event.channel.onopen = function () {
            PEER_CONNECTIONS[uuid].connected = true;
            self._peerPublish(uuid);
          };
        };
        pc.ondatachannel = onDataChannelCreated;

        pc.onicecandidate = function (event) {
          // TODO: Figure out why we get a null candidate
          if (event.candidate != null) {
            signalingChannel.send({ "candidate": event.candidate });
          }
        };

        pc.oniceconnectionstatechange = function () {
          if (pc.iceConnectionState === "connected") {
            // Nothing for now
          }
        };

        PUBLISH_QUEUE[uuid] = [];

        PEER_CONNECTIONS[uuid] = {
          connection: pc,
          candidates: [],
          connected: false,
          createdOffer: offer !== false,
          history: [],
          signalingChannel: signalingChannel
        };

        //if (offer !== false) {
        if ((UUID > uuid)) {
          var dc = pc.createDataChannel("pubnub", (IS_CHROME ? { reliable: false } : {}));
          onDataChannelCreated({
            channel: dc
          });

          pc.createOffer(function (description) {
            self.gotDescription(description, PEER_CONNECTIONS[uuid]);
          }, function (err) {
            // Connection failed, so delete it from the table
            delete PEER_CONNECTIONS[uuid];
            error(err);
          });
        }
        else {
          signalingChannel.send({
            initiation: true
          });
        }
      } else {
        debug("Trying to connect to already connected user: " + uuid);
      }
    };

    // Helper function for sending messages with different types.
    function handleMessage(connection, message) {
      if (message.type === PUBLISH_TYPE.STREAM) {
        connection.connection.addStream(message.stream);
      } else if (message.type === PUBLISH_TYPE.MESSAGE) {
        // Convert to JSON automagically
        if (typeof message.message === "object") {
          message.message = JSON.stringify(message.message);
        }

        connection.dataChannel.send(message.message);
      } else {
        error("Unrecognized RTC message type: " + message.type);
      }
    }

    // PUBNUB._peerPublish
    // Handles requesting a peer connection and emptying the queue when connected.
    API['_peerPublish'] = function (uuid) {
      if (PUBLISH_QUEUE[uuid] && PUBLISH_QUEUE[uuid].length > 0) {
        if (PEER_CONNECTIONS[uuid].connected === true) {
          handleMessage(PEER_CONNECTIONS[uuid], PUBLISH_QUEUE[uuid].shift());
          this._peerPublish(uuid);
        } else {
          // Not connected yet so just sit tight!
        }
      } else {
        // Nothing to publish
        return;
      }
    };

    // PUBNUB.publish overload
    API['publish'] = (function (_super) {
      return function (options) {
        if (options == null) {
          error("You must send an object when using PUBNUB.publish!");
        }

        if (options.user != null) {
          // Setup the connection if it does not exist
          if (PEER_CONNECTIONS[options.user] == null) {
            PUBNUB.createP2PConnection(options.user);
          }

          if (options.stream != null) {
            PUBLISH_QUEUE[options.user].push({
              type: PUBLISH_TYPE.STREAM,
              stream: options.stream
            });
            //PEER_CONNECTIONS[options.user].connection.addStream(options.stream);
          } else if (options.message != null) {
            PUBLISH_QUEUE[options.user].push({
              type: PUBLISH_TYPE.MESSAGE,
              message: options.message
            });
            //PEER_CONNECTIONS[options.user].dataChannel.send(options.message);
          } else {
            error("Stream or message key not found in argument object. One or the other must be provided for RTC publish calls!");
          }

          this._peerPublish(options.user);
        } else {
          _super.apply(this, arguments);
        }
      };
    })(PUBNUB['publish']);

    // PUBNUB.subscribe overload
    API['subscribe'] = (function (_super) {
      return function (options) {
        if (options == null) {
          error("You must send an object when using PUBNUB.subscribe!");
        }

        if (options.user != null) {
          // Setup the connection if it does not exist
          if (PEER_CONNECTIONS[options.user] == null) {
            PUBNUB.createP2PConnection(options.user);
          }

          var connection = PEER_CONNECTIONS[options.user];
          debug(PEER_CONNECTIONS, options.user, connection);

          if (options.stream) {
            // Setup the stream added listener
            connection.stream = options.stream;
          }

          if (options.callback) {
            // Setup the data channel callback listener
            connection.callback = options.callback;
          }

          // Replay the backfilled messages if they exist
          debug("Subscribing!", connection.history);
          if (connection.history.length > 0) {
            for (var i = 0; i < connection.history.length; i++) {
              var message = connection.history[i];

              if (options.callback) {
                options.callback(message);
              }
            }
          }
        } else {
          _super.apply(this, arguments);
        }
      };
    })(PUBNUB['subscribe']);

    // PUBNUB.history overload
    API['history'] = (function (_super) {
      return function (options) {
        if (options == null) {
          error("You must send an object when using PUBNUB.history!");
        }

        if (options.user != null) {
          if (options.callback) {
            var history = PEER_CONNECTIONS[options.user].history || [[]];

            options.callback([history]);
          } else {
            error("No callback provided for PUBNUB.history");
          }
        } else {
          return _super.apply(this, arguments);
        }
      };
    })(PUBNUB['history']);

    return extend(PUBNUB, API);
  }

  // PUBNUB init overload
  PUBNUB['init'] = (function (_super) {
    return function (options) {
      // Grab the UUID
      var uuid = options.uuid || generateUUID();
      options.uuid = uuid;

      // Create pubnub object
      debug(options);
      var pubnub = _super.call(this, options);

      // Extend the WebRTC API
      pubnub = extendAPI(pubnub, uuid);
      return pubnub;
    };
  })(PUBNUB['init']);

  var pdiv = document.querySelector("#pubnub");

  if (pdiv) {
    // CREATE A PUBNUB GLOBAL OBJECT
    window.PUBNUB = PUBNUB.init({
      'notest': 1,
      'publish_key': attr(pdiv, 'pub-key'),
      'subscribe_key': attr(pdiv, 'sub-key'),
      'ssl': !document.location.href.indexOf('https') ||
                        attr(pdiv, 'ssl') === 'on',
      'origin': attr(pdiv, 'origin'),
      'uuid': attr(pdiv, 'uuid')
    });
  }

})(window, PUBNUB);
<|endoftext|>


<commit_before>/** @jsx React.DOM */
(function(Perseus) {

var parse = Perseus.ExpressionTools.parse;
var compare = Perseus.ExpressionTools.compare;

var Expression = React.createClass({
    getInitialState: function() {
        return {value: ""};
    },

    errorTimeout: null,
    lastParsedTex: "",

    render: function() {
        var MJ = Perseus.MJ;  // MathJax
        var result = parse(this.state.value);

        if (result.parsed) {
            this.lastParsedTex = result.expr.tex();

            // We set a timeout of zero so that this runs after rendering -- at
            // this point, the error div isn't necessarily in the DOM yet
            this.errorTimeout = setTimeout(this.hideError, 0);
        } else {
            this.errorTimeout = setTimeout(this.showError, 2000);
        }

        return
            <input ref="input" type="text"
                onKeyDown={this.handleKeyDown}
                onKeyPress={this.handleKeyPress} />

                    <MJ>{this.lastParsedTex}</MJ>

                            Sorry, I don't understand that!

        ;
    },

    componentWillUnmount: function() {
        clearTimeout(this.errorTimeout);
    },

    showError: React.autoBind(function() {
        var $error = $(this.refs.error.getDOMNode());
        if (!$error.is(":visible")) {
            $error.css({ top: 50, opacity: 0.1 }).show()
                .animate({ top: 0, opacity: 1.0 }, 300);
        }
    }),

    hideError: React.autoBind(function() {
        var $error = $(this.refs.error.getDOMNode());
        if ($error.is(":visible")) {
            $error.animate({ top: 50, opacity: 0.1 }, 300, function() {
                $(this).hide();
            });
        }
    }),

    /**
     * The keydown handler handles clearing the error timeout, updating
     * state.value, and intercepting the backspace key when appropriate...
     */
    handleKeyDown: React.autoBind(function(event) {
        clearTimeout(this.errorTimeout);

        var input = this.refs.input.getDOMNode();
        var text = input.value;

        var start = input.selectionStart;
        var end = input.selectionEnd;
        var supported = start !== undefined;

        var which = event.nativeEvent.keyCode;

        if (supported && which === 8 /* backspace */) {
            if (start === end && text.slice(start - 1, start + 1) === "()") {
                event.preventDefault();
                input.value = text.slice(0, start - 1) + text.slice(start + 1);
                input.selectionStart = start - 1;
                input.selectionEnd = end - 1;
            }
        }

        setTimeout(function() {
            this.setState({value: input.value});
        }.bind(this), 0);
    }),

    /**
     * ...whereas the keypress handler handles the parentheses because keyCode
     * is more useful for actual character insertions (keypress gives 40 for an
     * open paren '(' instead of keydown which gives 57, the code for '9').
     */
    handleKeyPress: React.autoBind(function(event) {
        var input = this.refs.input.getDOMNode();
        var text = input.value;

        var start = input.selectionStart;
        var end = input.selectionEnd;
        var supported = start !== undefined;

        var which = event.nativeEvent.keyCode;

        if (supported && which === 40 /* left paren */) {
            event.preventDefault();

            if (start === end) {
                var insertMatched = _.any([" ", ")", ""], function(val) {
                    return text.charAt(start) === val;
                });

                input.value = text.slice(0, start) +
                        (insertMatched ? "()" : "(") + text.slice(end);
            } else {
                input.value = text.slice(0, start) +
                        "(" + text.slice(start, end) + ")" + text.slice(end);
            }

            input.selectionStart = start + 1;
            input.selectionEnd = end + 1;

        } else if (supported && which === 41 /* right paren */) {
            if (start === end && text.charAt(start) === ")") {
                event.preventDefault();
                input.selectionStart = start + 1;
                input.selectionEnd = end + 1;
            }
        }
    }),

    focus: function() {
        this.refs.input.getDOMNode().focus();
        return true;
    },

    toJSON: function(skipValidation) {
        return this.state;
    },

    setValue: function(value) {
        var input = this.refs.input.getDOMNode();
        input.value = value;
        this.setState({value: value});
    },

    simpleValidate: function(rubric) {
        return Expression.validate(this.toJSON(), rubric);
    }
});

_.extend(Expression, {
    validate: function(state, rubric) {
        var answer = parse(state.value);
        var expected = parse(rubric.value);

        if (!state.value || !answer.parsed) {
            return {
                type: "invalid",
                message: null
            };
        }

        var result = compare(answer.expr, expected.expr, rubric);

        return {
            type: "points",
            earned: result.equal ? 1 : 0,
            total: 1,
            message: result.message
        };
    }
});

var ExpressionEditor = React.createClass({
    defaultState: {
        eval: true,
        form: false,
        simplify: false
    },

    mixins: [Perseus.Util.PropsToState],

    optionLabels: {
        eval: "Answer expression must evaluate the same.",
        form: "Answer expression must have the same form.",
        simplify: "Answer expression must be fully expanded and simplified."
    },

    render: function() {
        return

                Correct answer:
                {Expression(_.extend({
                    ref: "expression",
                    onChange: this.props.onChange
                }, this.state))}

            {_.map(this.optionLabels, function(labelText, option) {
                return
                    <input type="checkbox" name={option}
                        checked={this.state[option]}
                        onChange={this.handleCheck} />
                    {labelText}
                ;
            }, this)}

;
    },

    componentDidMount: function() {
        this.refs.expression.setValue(this.props.value || "");
    },

    handleCheck: React.autoBind(function(e) {
        var state = {};
        state[e.target.name] = e.target.checked;
        this.setState(state);
    }),

    focus: function() {
        this.refs.expression.focus();
        return true;
    },

    toJSON: function(skipValidation) {
        var value = this.refs.expression.toJSON(skipValidation).value;

        if (!skipValidation) {
            if (value === "") {
                alert("Warning: No expression has been entered.");
            } else if (!parse(value).parsed) {
                alert("Warning: Entered expression didn't parse.");
            }

            if (!this.state.eval && !this.state.form) {
                alert("Warning: Neither semantic nor syntactic checking " +
                        "is enabled.");
            }
        }

        var options = _.pick(this.state, [
            "eval", "form", "simplify"
        ]);
        options.value = value;
        return options;
    }
});

Perseus.Widgets.register("expression", Expression);
Perseus.Widgets.register("expression-editor", ExpressionEditor);

})(Perseus);
<commit_msg>Make expression getInitialState(), render() pure<commit_after>/** @jsx React.DOM */
(function(Perseus) {

var parse = Perseus.ExpressionTools.parse;
var compare = Perseus.ExpressionTools.compare;

var Expression = React.createClass({
    getInitialState: function() {
        return {
            value: "",
            lastParsedTex: ""
        };
    },

    render: function() {
        var MJ = Perseus.MJ;  // MathJax
        var result = parse(this.state.value);

        return
            <input ref="input" type="text"
                onKeyDown={this.handleKeyDown}
                onKeyPress={this.handleKeyPress} />

                    <MJ>{this.state.lastParsedTex}</MJ>

                            Sorry, I don't understand that!

        ;
    },

    errorTimeout: null,

    componentDidMount: function() {
        this.componentDidUpdate();
    },

    componentDidUpdate: function() {
        clearTimeout(this.errorTimeout);
        if (parse(this.state.value).parsed) {
            this.hideError();
        } else {
            this.errorTimeout = setTimeout(this.showError, 2000);
        }
    },

    componentWillUnmount: function() {
        clearTimeout(this.errorTimeout);
    },

    showError: React.autoBind(function() {
        var $error = $(this.refs.error.getDOMNode());
        if (!$error.is(":visible")) {
            $error.css({ top: 50, opacity: 0.1 }).show()
                .animate({ top: 0, opacity: 1.0 }, 300);
        }
    }),

    hideError: React.autoBind(function() {
        var $error = $(this.refs.error.getDOMNode());
        if ($error.is(":visible")) {
            $error.animate({ top: 50, opacity: 0.1 }, 300, function() {
                $(this).hide();
            });
        }
    }),

    /**
     * The keydown handler handles clearing the error timeout, updating
     * state.value, and intercepting the backspace key when appropriate...
     */
    handleKeyDown: React.autoBind(function(event) {
        var input = this.refs.input.getDOMNode();
        var text = input.value;

        var start = input.selectionStart;
        var end = input.selectionEnd;
        var supported = start !== undefined;

        var which = event.nativeEvent.keyCode;

        if (supported && which === 8 /* backspace */) {
            if (start === end && text.slice(start - 1, start + 1) === "()") {
                event.preventDefault();
                input.value = text.slice(0, start - 1) + text.slice(start + 1);
                input.selectionStart = start - 1;
                input.selectionEnd = end - 1;
            }
        }

        setTimeout(function() {
            this.setValue(input.value);
        }.bind(this), 0);
    }),

    /**
     * ...whereas the keypress handler handles the parentheses because keyCode
     * is more useful for actual character insertions (keypress gives 40 for an
     * open paren '(' instead of keydown which gives 57, the code for '9').
     */
    handleKeyPress: React.autoBind(function(event) {
        var input = this.refs.input.getDOMNode();
        var text = input.value;

        var start = input.selectionStart;
        var end = input.selectionEnd;
        var supported = start !== undefined;

        var which = event.nativeEvent.keyCode;

        if (supported && which === 40 /* left paren */) {
            event.preventDefault();

            if (start === end) {
                var insertMatched = _.any([" ", ")", ""], function(val) {
                    return text.charAt(start) === val;
                });

                input.value = text.slice(0, start) +
                        (insertMatched ? "()" : "(") + text.slice(end);
            } else {
                input.value = text.slice(0, start) +
                        "(" + text.slice(start, end) + ")" + text.slice(end);
            }

            input.selectionStart = start + 1;
            input.selectionEnd = end + 1;

        } else if (supported && which === 41 /* right paren */) {
            if (start === end && text.charAt(start) === ")") {
                event.preventDefault();
                input.selectionStart = start + 1;
                input.selectionEnd = end + 1;
            }
        }
    }),

    focus: function() {
        this.refs.input.getDOMNode().focus();
        return true;
    },

    toJSON: function(skipValidation) {
        return {value: this.state.value};
    },

    setValue: function(value) {
        var input = this.refs.input.getDOMNode();
        input.value = value;

        var state = {value: value};
        var result = parse(value);
        if (result.parsed) {
            state.lastParsedTex = result.expr.tex();
        }
        this.setState(state);
    },

    simpleValidate: function(rubric) {
        return Expression.validate(this.toJSON(), rubric);
    }
});

_.extend(Expression, {
    validate: function(state, rubric) {
        var answer = parse(state.value);
        var expected = parse(rubric.value);

        if (!state.value || !answer.parsed) {
            return {
                type: "invalid",
                message: null
            };
        }

        var result = compare(answer.expr, expected.expr, rubric);

        return {
            type: "points",
            earned: result.equal ? 1 : 0,
            total: 1,
            message: result.message
        };
    }
});

var ExpressionEditor = React.createClass({
    defaultState: {
        eval: true,
        form: false,
        simplify: false
    },

    mixins: [Perseus.Util.PropsToState],

    optionLabels: {
        eval: "Answer expression must evaluate the same.",
        form: "Answer expression must have the same form.",
        simplify: "Answer expression must be fully expanded and simplified."
    },

    render: function() {
        return

                Correct answer:
                {Expression(_.extend({
                    ref: "expression",
                    onChange: this.props.onChange
                }, this.state))}

            {_.map(this.optionLabels, function(labelText, option) {
                return
                    <input type="checkbox" name={option}
                        checked={this.state[option]}
                        onChange={this.handleCheck} />
                    {labelText}
                ;
            }, this)}

;
    },

    componentDidMount: function() {
        this.refs.expression.setValue(this.props.value || "");
    },

    handleCheck: React.autoBind(function(e) {
        var state = {};
        state[e.target.name] = e.target.checked;
        this.setState(state);
    }),

    focus: function() {
        this.refs.expression.focus();
        return true;
    },

    toJSON: function(skipValidation) {
        var value = this.refs.expression.toJSON(skipValidation).value;

        if (!skipValidation) {
            if (value === "") {
                alert("Warning: No expression has been entered.");
            } else if (!parse(value).parsed) {
                alert("Warning: Entered expression didn't parse.");
            }

            if (!this.state.eval && !this.state.form) {
                alert("Warning: Neither semantic nor syntactic checking " +
                        "is enabled.");
            }
        }

        var options = _.pick(this.state, [
            "eval", "form", "simplify"
        ]);
        options.value = value;
        return options;
    }
});

Perseus.Widgets.register("expression", Expression);
Perseus.Widgets.register("expression-editor", ExpressionEditor);

})(Perseus);
<|endoftext|>


<commit_before>define([
    "hr/hr",
    "hr/utils",
    "hr/promise",
    "core/rpc",
    "core/commands",
    "core/events",
    "utils/hash"
], function(hr, _, Q, rpc, commands, events, hash) {
    var File = hr.Model.extend({
        defaults: {
            path: null,
            name: null,
            directory: false,
            size: 0,
            mtime: 0,
            atime: 0,
            buffer: null
        },
        idAttribute: "name",

        // Initialize
        initialize: function() {
            File.__super__.initialize.apply(this, arguments);

            this.listenTo(events, "e:fs:modified", _.partial(this._dispatchFsEvent, "modified"));
            this.listenTo(events, "e:fs:deleted", _.partial(this._dispatchFsEvent, "deleted"));
            this.listenTo(events, "e:fs:created", _.partial(this._dispatchFsEvent, "created"));
        },

        // Dispatch fs event
        _dispatchFsEvent: function(type, paths) {
            var path, childs;

            if (this.isBuffer()) return;

            path = this.get("path");
            if (_.contains(paths,  path)) {
                if (type == "deleted") return this.destroy();
                this.trigger("fs:modified");
            }
            if (this.isDirectory()) {
                childs = _.filter(paths, this.isChild, this);
                if (childs.length > 0) {
                    this.trigger("fs:files:"+type);
                }
            }
        },

        // Open this file
        open: function() {
            return commands.run("file.open", {
                path: this.get("path")
            });
        },

        // Check if is a directory
        isDirectory: function() {
            return this.get("directory");
        },

        // Check if a file is a buffer or exists
        isBuffer: function() {
            return this.get("buffer") != null;
        },

        // Test if a path is child
        isChild: function(path) {
            var parts1 = _.filter(path.split("/"), function(p) { return p.length > 0; });
            var parts2 = _.filter(this.get("path").split("/"), function(p) { return p.length > 0; });
            return (parts1.length == (parts2.length+1));
        },

        // List files in this directory
        list: function() {
            return rpc.execute("fs/list", {
                'path': this.get("path")
            })
            .then(function(files) {
                return _.map(files, function(file) {
                    return new File({}, file);
                });
            });
        },

        // Get a specific file
        stat: function(path) {
            var that = this;

            return rpc.execute("fs/stat", {
                'path': path
            })
            .then(function(file) {
                return that.set(file);
            })
            .thenResolve(that);
        },

        // Read file content
        read: function() {
            if (this.isBuffer()) return Q(this.get("buffer"));

            return rpc.execute("fs/read", {
                'path': this.get("path")
            })
            .get("content")
            .then(hash.atob);
        },

        // Write file content
        write: function(content) {
            var that = this;

            return Q()
            .then(function() {
                if (that.isBuffer()) return Q(that.set("buffer", content));

                return rpc.execute("fs/write", {
                    'path': that.get("path"),
                    'content': hash.btoa(content)
                });
            })
            .then(function() {
                that.trigger("write", content);
            });
        },

        // Rename
        rename: function(name) {
            var that = this;
            if (this.isBuffer()) return Q();

            return rpc.execute("fs/rename", {
                'from': this.get("path"),
                'name': name
            })
            .then(function(f) {
                that.set(f);
            });
        },

        // Remove this file
        remove: function() {
            if (this.isBuffer()) return Q(this.destroy());

            return rpc.execute("fs/remove", {
                'path': this.get("path")
            })
            .then(this.destroy.bind(this));
        },

        // Create a file in this folder
        create: function(name) {
            return File.create(this.get("path"), name);
        },

        // Create a folder in this folder
        mkdir: function(name) {
            return File.mkdir(this.get("path"), name);
        },

        // Get by extension
        getExtension: function() {
            return "."+this.get("name").split('.').pop();
        }
    }, {
        // Get a specific file
        get: function(path) {
            var f = new File();

            return f.stat(path);
        },

        // Create a file buffer
        buffer: function(name, content, id) {
            var f = new File({}, {
                'name': name,
                'buffer': content,
                'path': "buffer://"+(id || _.uniqueId("tmp")),
                'directory': false
            });

            return f;
        },

        // Create a new file
        create: function(path, name) {
            return rpc.execute("fs/create", {
                'path': path,
                'name': name
            })
            .then(function(f) {
                return new File({}, f);
            });
        },

        // Create a new folder
        mkdir: function(path, name) {
            return rpc.execute("fs/mkdir", {
                'path': path,
                'name': name
            })
            .then(function(f) {
                return new File({}, f);
            });
        }
    });

    return File;
});<commit_msg>Don't destroy file when receive event "deleted" from watcher<commit_after>define([
    "hr/hr",
    "hr/utils",
    "hr/promise",
    "core/rpc",
    "core/commands",
    "core/events",
    "utils/hash"
], function(hr, _, Q, rpc, commands, events, hash) {
    var File = hr.Model.extend({
        defaults: {
            path: null,
            name: null,
            directory: false,
            size: 0,
            mtime: 0,
            atime: 0,
            buffer: null
        },
        idAttribute: "name",

        // Initialize
        initialize: function() {
            File.__super__.initialize.apply(this, arguments);

            this.listenTo(events, "e:fs:modified", _.partial(this._dispatchFsEvent, "modified"));
            this.listenTo(events, "e:fs:deleted", _.partial(this._dispatchFsEvent, "deleted"));
            this.listenTo(events, "e:fs:created", _.partial(this._dispatchFsEvent, "created"));
        },

        // Dispatch fs event
        _dispatchFsEvent: function(type, paths) {
            var path, childs;

            if (this.isBuffer()) return;

            path = this.get("path");
            if (_.contains(paths,  path)) {
                this.trigger("fs:"+type);
            }
            if (this.isDirectory()) {
                childs = _.filter(paths, this.isChild, this);
                if (childs.length > 0) {
                    this.trigger("fs:files:"+type);
                }
            }
        },

        // Open this file
        open: function() {
            return commands.run("file.open", {
                path: this.get("path")
            });
        },

        // Check if is a directory
        isDirectory: function() {
            return this.get("directory");
        },

        // Check if a file is a buffer or exists
        isBuffer: function() {
            return this.get("buffer") != null;
        },

        // Test if a path is child
        isChild: function(path) {
            var parts1 = _.filter(path.split("/"), function(p) { return p.length > 0; });
            var parts2 = _.filter(this.get("path").split("/"), function(p) { return p.length > 0; });
            return (parts1.length == (parts2.length+1));
        },

        // List files in this directory
        list: function() {
            return rpc.execute("fs/list", {
                'path': this.get("path")
            })
            .then(function(files) {
                return _.map(files, function(file) {
                    return new File({}, file);
                });
            });
        },

        // Get a specific file
        stat: function(path) {
            var that = this;

            return rpc.execute("fs/stat", {
                'path': path
            })
            .then(function(file) {
                return that.set(file);
            })
            .thenResolve(that);
        },

        // Read file content
        read: function() {
            if (this.isBuffer()) return Q(this.get("buffer"));

            return rpc.execute("fs/read", {
                'path': this.get("path")
            })
            .get("content")
            .then(hash.atob);
        },

        // Write file content
        write: function(content) {
            var that = this;

            return Q()
            .then(function() {
                if (that.isBuffer()) return Q(that.set("buffer", content));

                return rpc.execute("fs/write", {
                    'path': that.get("path"),
                    'content': hash.btoa(content)
                });
            })
            .then(function() {
                that.trigger("write", content);
            });
        },

        // Rename
        rename: function(name) {
            var that = this;
            if (this.isBuffer()) return Q();

            return rpc.execute("fs/rename", {
                'from': this.get("path"),
                'name': name
            })
            .then(function(f) {
                that.set(f);
            });
        },

        // Remove this file
        remove: function() {
            if (this.isBuffer()) return Q(this.destroy());

            return rpc.execute("fs/remove", {
                'path': this.get("path")
            })
            .then(this.destroy.bind(this));
        },

        // Create a file in this folder
        create: function(name) {
            return File.create(this.get("path"), name);
        },

        // Create a folder in this folder
        mkdir: function(name) {
            return File.mkdir(this.get("path"), name);
        },

        // Get by extension
        getExtension: function() {
            return "."+this.get("name").split('.').pop();
        }
    }, {
        // Get a specific file
        get: function(path) {
            var f = new File();

            return f.stat(path);
        },

        // Create a file buffer
        buffer: function(name, content, id) {
            var f = new File({}, {
                'name': name,
                'buffer': content,
                'path': "buffer://"+(id || _.uniqueId("tmp")),
                'directory': false
            });

            return f;
        },

        // Create a new file
        create: function(path, name) {
            return rpc.execute("fs/create", {
                'path': path,
                'name': name
            })
            .then(function(f) {
                return new File({}, f);
            });
        },

        // Create a new folder
        mkdir: function(path, name) {
            return rpc.execute("fs/mkdir", {
                'path': path,
                'name': name
            })
            .then(function(f) {
                return new File({}, f);
            });
        }
    });

    return File;
});<|endoftext|>


<commit_before>'use strict'

import test from 'tape'
import Grid from '../../'

const WIDTH = 1600
const HEIGHT = 900

function StubWindow (id, width, height) {
  this.id = id
  this.width = width,
  this.height = height
}

test('can add windows to grid', t => {
  t.plan(4)
  try {
    const grid = new Grid(WIDTH, HEIGHT)
    const stubWindow1 = new StubWindow(1, 400, 600)
    const stubWindow2 = new StubWindow(2, 400, 600)
    grid.add(stubWindow1)
    t.equals(grid.windows.length, 1, 'grid has one window')
    t.deepEquals(grid.getWindow(1), Object.assign({}, stubWindow1, {
      x: 0,
      y: 0
    }), 'window added in default location')
    grid.add(stubWindow2, 950, 0)
    t.equals(grid.windows.length, 2, 'grid has two windows')
    t.deepEquals(grid.getWindow(2), Object.assign({}, stubWindow2, {
      x: 950,
      y: 0
    }), 'window added in custom location')
  } catch (e) {
    t.fail(e.toString())
  }
})

test('windows should not be created over each other', t => {
  t.plan(3)
  try {
    const grid = new Grid(WIDTH, HEIGHT)
    const stubWindow1 = new StubWindow(1, 200, 100)
    const stubWindow2 = new StubWindow(2, 200, 100)
    grid.add(stubWindow1)
    t.throws(
      () => grid.add(stubWindow2, 199, 0),
      Error,
      'window cannot be created over existing window on x axis'
    )
    t.throws(
      () => grid.add(stubWindow2, 0, 99),
      Error,
      'window cannot be created over existing window on y axis'
    )
    t.throws(
      () => grid.add(stubWindow2, 199, 99),
      Error,
      'window cannot be created over existing window on multiple axes'
    )
  } catch (e) {
    t.fail(e)
    t.end()
  }
})

test('windows should not be created outside grid', t => {
  t.plan(4)
  try {
    const grid = new Grid(WIDTH, HEIGHT)
    const stubWindow = new StubWindow(1, 200, 100)
    const fatStubWindow = new StubWindow(2, WIDTH + 1, HEIGHT)
    const tallStubWindow = new StubWindow(2, WIDTH, HEIGHT + 1)
    t.throws(
      () => grid.add(stubWindow, 1401, 0),
      Error,
      'window cannot exceed grid horizontal bounds'
    )
    t.throws(
      () => grid.add(stubWindow, 0, 801),
      Error,
      'window canonot exceed grid vertical bounds'
    )
    t.throws(
      () => grid.add(fatStubWindow),
      Error,
      'cannot add window wider than grid'
    )
    t.throws(
      () => grid.add(tallStubWindow),
      Error,
      'cannot add window taller than grid'
    )
  } catch (e) {
    t.fail(e.toString())
    t.end()
  }
})

test.skip('can change window bounds', async t => {
  // TBD
})

test.skip('cannot change window bounds over an existing window', async t => {
  // TBD
  // fully
  // partially
})
<commit_msg>cleanup<commit_after>'use strict'

import test from 'tape'
import Grid from '../../'

const WIDTH = 1600
const HEIGHT = 900

function StubWindow (id, width, height) {
  this.id = id
  this.width = width,
  this.height = height
}

test('can add windows to grid', t => {
  t.plan(4)
  try {
    const grid = new Grid(WIDTH, HEIGHT)
    const stubWindow1 = new StubWindow(1, 400, 600)
    const stubWindow2 = new StubWindow(2, 400, 600)
    grid.add(stubWindow1)
    t.equals(grid.windows.length, 1, 'grid has one window')
    t.deepEquals(grid.getWindow(1), Object.assign({}, stubWindow1, {
      x: 0,
      y: 0
    }), 'window added in default location')
    grid.add(stubWindow2, 950, 0)
    t.equals(grid.windows.length, 2, 'grid has two windows')
    t.deepEquals(grid.getWindow(2), Object.assign({}, stubWindow2, {
      x: 950,
      y: 0
    }), 'window added in custom location')
  } catch (e) {
    t.fail(e.toString())
  }
})

test('windows should not be created over each other', t => {
  t.plan(3)
  try {
    const grid = new Grid(WIDTH, HEIGHT)
    const stubWindow1 = new StubWindow(1, 200, 100)
    const stubWindow2 = new StubWindow(2, 200, 100)
    grid.add(stubWindow1)
    t.throws(
      () => grid.add(stubWindow2, 199, 0),
      Error,
      'window cannot be created over existing window on x axis'
    )
    t.throws(
      () => grid.add(stubWindow2, 0, 99),
      Error,
      'window cannot be created over existing window on y axis'
    )
    t.throws(
      () => grid.add(stubWindow2, 199, 99),
      Error,
      'window cannot be created over existing window on multiple axes'
    )
  } catch (e) {
    t.fail(e)
    t.end()
  }
})

test('windows should not be created outside grid', t => {
  t.plan(4)
  try {
    const grid = new Grid(WIDTH, HEIGHT)
    const stubWindow = new StubWindow(1, 200, 100)
    const fatStubWindow = new StubWindow(2, WIDTH + 1, HEIGHT)
    const tallStubWindow = new StubWindow(2, WIDTH, HEIGHT + 1)
    t.throws(
      () => grid.add(stubWindow, 1401, 0),
      Error,
      'window cannot exceed grid horizontal bounds'
    )
    t.throws(
      () => grid.add(stubWindow, 0, 801),
      Error,
      'window canonot exceed grid vertical bounds'
    )
    t.throws(
      () => grid.add(fatStubWindow),
      Error,
      'cannot add window wider than grid'
    )
    t.throws(
      () => grid.add(tallStubWindow),
      Error,
      'cannot add window taller than grid'
    )
  } catch (e) {
    t.fail(e.toString())
    t.end()
  }
})
<|endoftext|>