<commit_before>/**
 * @file 项目配置
 * @author zdying
 */

// console.log('---------- visit global variable -----------');
// console.log(__hiipack__.cwd);
// console.log(__hiipack__.root);
// console.log(__hiipack__.tmpdir);
// console.log('---------- visit global variable -----------');

module.exports = {
    /**
     * 需要单独打包的第三方库
     * 例如: {lib: ['react', 'react-dom']}
     * 会把'react'和'react-dom'打包到lib.js
     */
    library: {
        lib: ['jquery', 'backbone']
    },
    /**
     * loaders
     */
    loaders: {
        '*': [
            { test: /\.(mustache|html)$/, loader: 'mustache' }
        ],
        //TODO add next version
        // '*': [
        //     { test: /\.(mustache|html)$/, loader: 'mustache' }
        // ],
        // '*': {
        //     "handlebars-loader handlebars": function(handlebarsLoader, handlebars){
        //         return { test: /\.(handlebars|hbs)$/, loader: 'handlebars' }
        //     },
        //     "vue-loader": function(vueLoader){
        //
        //     }
        // }
    },
    //TODO add next version
    // plugins: {
    //     'webpack': function(webpack){
    //         return [
    //             new webpack.HotModuleReplacementPlugin(),
    //             new webpack.NoErrorsPlugin()
    //         ]
    //     },
    //     'other-plugin': function(otherPlugin){
    //         return new otherPlugin({
    //             option1: 'opt1',
    //             option2: 'opt2'
    //         })
    //     },
    //     'plugin1 plugin2': function(Plugin1, Plugin2){
    //         return [
    //              new Plugin1({
    //                  option1: 'opt1',
    //                  option2: 'opt2'
    //              }),
    //              new Plugin2({
    //                  option1: 'opt1',
    //                  option2: 'opt2'
    //              }),
    //     },
    //     'plugin@version': function(Plugin){
    //         return new Plugin()
    //     },
    // },
    /**
     * 业务代码入口
     */
    entry: {
        main: "src/main",
    },
    /**
     * 别名
     */
    alias: {
        'root': 'src/'
    },
    //TODO extend customConfig to webpack config, add next version
    // customConfig: {
    //     test: 1
    // },
    // customConfig: {
    //     test: 2
    // },
};
<commit_msg>修改tmpl/normal<commit_after>/**
 * @file 项目配置
 * @author zdying
 */

// console.log('---------- visit global variable -----------');
// console.log(__hiipack__.cwd);
// console.log(__hiipack__.root);
// console.log(__hiipack__.tmpdir);
// console.log(__hiipack__.resolve('webpack'));
// console.log('---------- visit global variable -----------');

module.exports = {
    /**
     * 需要单独打包的第三方库
     * 例如: {lib: ['react', 'react-dom']}
     * 会把'react'和'react-dom'打包到lib.js
     */
    library: {
        lib: ['jquery', 'backbone']
    },
    /**
     * loaders
     */
    loaders: {
        '*': [
            { test: /\.(mustache|html)$/, loader: 'mustache' }
        ],
        //TODO add next version
        // '*': [
        //     { test: /\.(mustache|html)$/, loader: 'mustache' }
        // ],
        // '*': {
        //     "handlebars-loader handlebars": function(handlebarsLoader, handlebars){
        //         return { test: /\.(handlebars|hbs)$/, loader: 'handlebars' }
        //     },
        //     "vue-loader": function(vueLoader){
        //
        //     }
        // }
    },
    //TODO add next version
    // plugins: {
    //     'webpack': function(webpack){
    //         return [
    //             new webpack.HotModuleReplacementPlugin(),
    //             new webpack.NoErrorsPlugin()
    //         ]
    //     },
    //     'other-plugin': function(otherPlugin){
    //         return new otherPlugin({
    //             option1: 'opt1',
    //             option2: 'opt2'
    //         })
    //     },
    //     'plugin1 plugin2': function(Plugin1, Plugin2){
    //         return [
    //              new Plugin1({
    //                  option1: 'opt1',
    //                  option2: 'opt2'
    //              }),
    //              new Plugin2({
    //                  option1: 'opt1',
    //                  option2: 'opt2'
    //              }),
    //     },
    //     'plugin@version': function(Plugin){
    //         return new Plugin()
    //     },
    // },
    /**
     * 业务代码入口
     */
    entry: {
        main: "src/main",
    },
    /**
     * 别名
     */
    alias: {
        'root': 'src/'
    },
    /**
     * 测试框架配置, 目前只支持mocha
     */
    autoTest: {
        framework: 'mocha',
        assertion: 'expect'
        // assertion: ['expect', 'assert']
    }
};
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

define(["require", "util", "channels", "jquery", "storage"], function (require, util, channels, $, storage) {

  var DEBUG = true;
  // This is the amount of time in which a hello-back must be received after a hello
  // for us to respect a URL change:
  var HELLO_BACK_CUTOFF = 1500;

  var session = util.mixinEvents(util.Module("session"));
  var assert = util.assert;

  // We will load this module later (there's a circular import):
  var peers;

  // This is the hub we connect to:
  session.shareId = null;
  // This is the ID that identifies this client:
  session.clientId = null;
  session.router = channels.Router();
  // Indicates if TogetherJS has just started (not continuing from a saved session):
  session.firstRun = false;

  // This is the key we use for localStorage:
  var localStoragePrefix = "togetherjs.";
  // This is the channel to the hub:
  var channel = null;

  // Setting, essentially global:
  session.AVATAR_SIZE = 90;

  var MAX_SESSION_AGE = 30*24*60*60*1000; // 30 days

  /****************************************
   * URLs
   */

  session.hubUrl = function (id) {
    id = id || session.shareId;
    assert(id, "URL cannot be resolved before TogetherJS.shareId has been initialized");
    return TogetherJS.getConfig("hubBase").replace(/\/*$/, "") + "/hub/" + id;
  };

  session.shareUrl = function () {
    assert(session.shareId, "Attempted to access shareUrl() before shareId is set");
    var hash = location.hash;
    var m = /\?[^#]*/.exec(location.href);
    var query = "";
    if (m) {
      query = m[0];
    }
    hash = hash.replace(/&?togetherjs-[a-zA-Z0-9]+/, "");
    hash = hash || "#";
    return location.protocol + "//" + location.host + location.pathname + query +
           hash + "&togetherjs=" + session.shareId;
  };

  session.recordUrl = function () {
    assert(session.shareId);
    var url = TogetherJS.baseUrl.replace(/\/*$/, "") + "/recorder.html";
    url += "#&togetherjs=" + session.shareId + "&hubBase=" + TogetherJS.getConfig("hubBase");
    return url;
  };

  /* location.href without the hash */
  session.currentUrl = function () {
    return location.href.replace(/#.*/, "");
  };

  session.siteName = function () {
    return TogetherJS.getConfig("siteName") || document.title;
  };

  /****************************************
   * Message handling/dispatching
   */

  session.hub = util.mixinEvents({});

  var IGNORE_MESSAGES = ["cursor-update", "keydown", "scroll-update"];
  // These are messages sent by clients who aren't "part" of the TogetherJS session:
  var MESSAGES_WITHOUT_CLIENTID = ["who", "invite", "init-connection"];

  // We ignore incoming messages from the channel until this is true:
  var readyForMessages = false;

  function openChannel() {
    assert(! channel, "Attempt to re-open channel");
    console.info("Connecting to", session.hubUrl(), location.href);
    var c = channels.WebSocketChannel(session.hubUrl());
    c.onmessage = function (msg) {
      if (! readyForMessages) {
        if (DEBUG) {
          console.info("In (but ignored for being early):", msg);
        }
        return;
      }
      if (DEBUG && IGNORE_MESSAGES.indexOf(msg.type) == -1) {
        console.info("In:", msg);
      }
      if (! peers) {
        // We're getting messages before everything is fully initialized
        console.warn("Message received before all modules loaded (ignoring):", msg);
        return;
      }
      if ((! msg.clientId) && MESSAGES_WITHOUT_CLIENTID.indexOf(msg.type) == -1) {
        console.warn("Got message without clientId, where clientId is required", msg);
        return;
      }
      if (msg.clientId) {
        msg.peer = peers.getPeer(msg.clientId, msg);
      }
      if (msg.type == "hello" || msg.type == "hello-back" || msg.type == "peer-update") {
        // We do this here to make sure this is run before any other
        // hello handlers:
        msg.peer.updateFromHello(msg);
      }
      if (msg.peer) {
        msg.sameUrl = msg.peer.url == currentUrl;
        if (!msg.peer.isSelf) {
          msg.peer.updateMessageDate(msg);
        }
      }
      session.hub.emit(msg.type, msg);
      TogetherJS._onmessage(msg);
    };
    channel = c;
    session.router.bindChannel(channel);
  }

  // FIXME: once we start looking at window.history we need to update this:
  var currentUrl = (location.href + "").replace(/\#.*$/, "");

  session.send = function (msg) {
    if (DEBUG && IGNORE_MESSAGES.indexOf(msg.type) == -1) {
      console.info("Send:", msg);
    }
    msg.clientId = session.clientId;
    channel.send(msg);
  };

  session.appSend = function (msg) {
    var type = msg.type;
    if (type.search(/^togetherjs\./) === 0) {
      type = type.substr("togetherjs.".length);
    } else if (type.search(/^app\./) === -1) {
      type = "app." + type;
    }
    msg.type = type;
    session.send(msg);
  };

  /****************************************
   * Standard message responses
   */

  /* Always say hello back, and keep track of peers: */
  session.hub.on("hello hello-back", function (msg) {
    if (msg.type == "hello") {
      sendHello(true);
    }
    if (session.isClient && (! msg.isClient) &&
        session.firstRun && session.timeHelloSent &&
        Date.now() - session.timeHelloSent < HELLO_BACK_CUTOFF) {
      processFirstHello(msg);
    }
  });

  session.hub.on("who", function (msg) {
    sendHello(true);
  });

  function processFirstHello(msg) {
    if (! msg.sameUrl) {
      var url = msg.url;
      if (msg.urlHash) {
        url += msg.urlHash;
      }
      require("ui").showUrlChangeMessage(msg.peer, url);
      location.href = url;
    }
  }

  session.timeHelloSent = null;

  function sendHello(helloBack) {
    var msg = session.makeHelloMessage(helloBack);
    if (! helloBack) {
      session.timeHelloSent = Date.now();
      peers.Self.url = msg.url;
    }
    session.send(msg);
  }

  session.makeHelloMessage = function (helloBack) {
    var msg = {
      name: peers.Self.name || peers.Self.defaultName,
      avatar: peers.Self.avatar,
      color: peers.Self.color,
      url: session.currentUrl(),
      urlHash: location.hash,
      // FIXME: titles update, we should track those changes:
      title: document.title,
      rtcSupported: session.RTCSupported,
      isClient: session.isClient
    };
    if (helloBack) {
      msg.type = "hello-back";
    } else {
      msg.type = "hello";
      msg.clientVersion = TogetherJS.version;
    }
    if (! TogetherJS.startup.continued) {
      msg.starting = true;
    }
    // This is a chance for other modules to effect the hello message:
    session.emit("prepare-hello", msg);
    return msg;
  };

  /****************************************
   * Lifecycle (start and end)
   */

  // These are Javascript files that implement features, and so must
  // be injected at runtime because they aren't pulled in naturally
  // via define().
  // ui must be the first item:
  var features = ["peers", "ui", "chat", "webrtc", "cursor", "startup","videos", "forms", "visibilityApi"];

  function getRoomName(prefix, maxSize) {
    var findRoom = TogetherJS.getConfig("hubBase").replace(/\/*$/, "") + "/findroom";
    return $.ajax({
      url: findRoom,
      dataType: "json",
      data: {prefix: prefix, max: maxSize}
    }).then(function (resp) {
      return resp.name;
    });
  }

  function initIdentityId() {
    return util.Deferred(function (def) {
      if (session.identityId) {
        def.resolve();
        return;
      }
      storage.get("identityId").then(function (identityId) {
        if (! identityId) {
          identityId = util.generateId();
          storage.set("identityId", identityId);
        }
        session.identityId = identityId;
        // We don't actually have to wait for the set to succede, so
        // long as session.identityId is set
        def.resolve();
      });
    });
  }

  initIdentityId.done = initIdentityId();

  function initShareId() {
    return util.Deferred(function (def) {
      var hash = location.hash;
      var shareId = session.shareId;
      var isClient = true;
      var set = true;
      var sessionId;
      session.firstRun = ! TogetherJS.startup.continued;
      if (! shareId) {
        if (TogetherJS.startup._joinShareId) {
          // Like, below, this *also* means we got the shareId from the hash
          // (in togetherjs.js):
          shareId = TogetherJS.startup._joinShareId;
        }
      }
      if (! shareId) {
        // FIXME: I'm not sure if this will ever happen, because togetherjs.js should
        // handle it
        var m = /&?togetherjs=([^&]*)/.exec(hash);
        if (m) {
          isClient = ! m[1];
          shareId = m[2];
          var newHash = hash.substr(0, m.index) + hash.substr(m.index + m[0].length);
          location.hash = newHash;
        }
      }
      return storage.tab.get("status").then(function (saved) {
        var findRoom = TogetherJS.getConfig("findRoom");
        if (findRoom && typeof findRoom == "string" && (! saved) && (! TogetherJS.startup._joinShareId)) {
          isClient = true;
          shareId = findRoom;
          sessionId = util.generateId();
        } else if (findRoom && (! saved) && (! TogetherJS.startup._joinShareId)) {
          assert(findRoom.prefix && typeof findRoom.prefix == "string", "Bad findRoom.prefix", findRoom);
          assert(findRoom.max && typeof findRoom.max == "number" && findRoom.max > 0,
                 "Bad findRoom.max", findRoom);
          sessionId = util.generateId();
          if (findRoom.prefix.search(/[^a-zA-Z0-9]/) != -1) {
            console.warn("Bad value for findRoom.prefix:", JSON.stringify(findRoom.prefix));
          }
          getRoomName(findRoom.prefix, findRoom.max).then(function (shareId) {
            // FIXME: duplicates code below:
            session.clientId = session.identityId + "." + sessionId;
            storage.tab.set("status", {reason: "joined", shareId: shareId, running: true, date: Date.now(), sessionId: sessionId});
            session.isClient = true;
            session.shareId = shareId;
            session.emit("shareId");
            def.resolve(session.shareId);
          });
          return;
        } else if (TogetherJS.startup._launch) {
          if (saved) {
            isClient = saved.reason == "joined";
            if (! shareId) {
              shareId = saved.shareId;
            }
            sessionId = saved.sessionId;
          } else {
            isClient = TogetherJS.startup.reason == "joined";
            assert(! sessionId);
            sessionId = util.generateId();
          }
          if (! shareId) {
            shareId = util.generateId();
          }
        } else if (saved) {
          isClient = saved.reason == "joined";
          TogetherJS.startup.reason = saved.reason;
          TogetherJS.startup.continued = true;
          shareId = saved.shareId;
          sessionId = saved.sessionId;
          // The only case when we don't need to set the storage status again is when
          // we're already set to be running
          set = ! saved.running;
        } else {
          throw new util.AssertionError("No saved status, and no startup._launch request; why did TogetherJS start?");
        }
        assert(session.identityId);
        session.clientId = session.identityId + "." + sessionId;
        if (set) {
          storage.tab.set("status", {reason: TogetherJS.startup.reason, shareId: shareId, running: true, date: Date.now(), sessionId: sessionId});
        }
        session.isClient = isClient;
        session.shareId = shareId;
        session.emit("shareId");
        def.resolve(session.shareId);
      });
    });
  }

  function initStartTarget() {
    var id;
    if (TogetherJS.startup.button) {
      id = TogetherJS.startup.button.id;
      if (id) {
        storage.set("startTarget", id);
      }
      return;
    }
    storage.get("startTarget").then(function (id) {
      var el = document.getElementById(id);
      if (el) {
        TogetherJS.startup.button = el;
      }
    });
  }

  session.start = function () {
    initStartTarget();
    initIdentityId().then(function () {
      initShareId().then(function () {
        readyForMessages = false;
        openChannel();
        require(["ui"], function (ui) {
          TogetherJS.running = true;
          ui.prepareUI();
          require(features, function () {
            $(function () {
              peers = require("peers");
              var startup = require("startup");
              session.emit("start");
              session.once("ui-ready", function () {
                readyForMessages = true;
                startup.start();
              });
              ui.activateUI();
              if (TogetherJS.getConfig("enableAnalytics")) {
                require(["analytics"], function (analytics) {
                  analytics.activate();
                });
              }
              peers._SelfLoaded.then(function () {
                sendHello(false);
              });
              TogetherJS.emit("ready");
            });
          });
        });
      });
    });
  };

  session.close = function (reason) {
    TogetherJS.running = false;
    var msg = {type: "bye"};
    if (reason) {
      msg.reason = reason;
    }
    session.send(msg);
    session.emit("close");
    var name = window.name;
    storage.tab.get("status").then(function (saved) {
      if (! saved) {
        console.warn("No session information saved in", "status." + name);
      } else {
        saved.running = false;
        saved.date = Date.now();
        storage.tab.set("status", saved);
      }
      channel.close();
      channel = null;
      session.shareId = null;
      session.emit("shareId");
      TogetherJS.emit("close");
      TogetherJS._teardown();
    });
  };

  session.on("start", function () {
    $(window).on("resize", resizeEvent);
  });
  session.on("close", function () {
    $(window).off("resize", resizeEvent);
  });
  function resizeEvent() {
    session.emit("resize");
  }

  if (TogetherJS.startup._launch) {
    setTimeout(session.start);
  }

  util.testExpose({
    getChannel: function () {
      return channel;
    }
  });

  return session;
});
<commit_msg>Added some clues about when findRoom doesn't work (which seems to be happening to people)<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

define(["require", "util", "channels", "jquery", "storage"], function (require, util, channels, $, storage) {

  var DEBUG = true;
  // This is the amount of time in which a hello-back must be received after a hello
  // for us to respect a URL change:
  var HELLO_BACK_CUTOFF = 1500;

  var session = util.mixinEvents(util.Module("session"));
  var assert = util.assert;

  // We will load this module later (there's a circular import):
  var peers;

  // This is the hub we connect to:
  session.shareId = null;
  // This is the ID that identifies this client:
  session.clientId = null;
  session.router = channels.Router();
  // Indicates if TogetherJS has just started (not continuing from a saved session):
  session.firstRun = false;

  // This is the key we use for localStorage:
  var localStoragePrefix = "togetherjs.";
  // This is the channel to the hub:
  var channel = null;

  // Setting, essentially global:
  session.AVATAR_SIZE = 90;

  var MAX_SESSION_AGE = 30*24*60*60*1000; // 30 days

  /****************************************
   * URLs
   */

  session.hubUrl = function (id) {
    id = id || session.shareId;
    assert(id, "URL cannot be resolved before TogetherJS.shareId has been initialized");
    return TogetherJS.getConfig("hubBase").replace(/\/*$/, "") + "/hub/" + id;
  };

  session.shareUrl = function () {
    assert(session.shareId, "Attempted to access shareUrl() before shareId is set");
    var hash = location.hash;
    var m = /\?[^#]*/.exec(location.href);
    var query = "";
    if (m) {
      query = m[0];
    }
    hash = hash.replace(/&?togetherjs-[a-zA-Z0-9]+/, "");
    hash = hash || "#";
    return location.protocol + "//" + location.host + location.pathname + query +
           hash + "&togetherjs=" + session.shareId;
  };

  session.recordUrl = function () {
    assert(session.shareId);
    var url = TogetherJS.baseUrl.replace(/\/*$/, "") + "/recorder.html";
    url += "#&togetherjs=" + session.shareId + "&hubBase=" + TogetherJS.getConfig("hubBase");
    return url;
  };

  /* location.href without the hash */
  session.currentUrl = function () {
    return location.href.replace(/#.*/, "");
  };

  session.siteName = function () {
    return TogetherJS.getConfig("siteName") || document.title;
  };

  /****************************************
   * Message handling/dispatching
   */

  session.hub = util.mixinEvents({});

  var IGNORE_MESSAGES = ["cursor-update", "keydown", "scroll-update"];
  // These are messages sent by clients who aren't "part" of the TogetherJS session:
  var MESSAGES_WITHOUT_CLIENTID = ["who", "invite", "init-connection"];

  // We ignore incoming messages from the channel until this is true:
  var readyForMessages = false;

  function openChannel() {
    assert(! channel, "Attempt to re-open channel");
    console.info("Connecting to", session.hubUrl(), location.href);
    var c = channels.WebSocketChannel(session.hubUrl());
    c.onmessage = function (msg) {
      if (! readyForMessages) {
        if (DEBUG) {
          console.info("In (but ignored for being early):", msg);
        }
        return;
      }
      if (DEBUG && IGNORE_MESSAGES.indexOf(msg.type) == -1) {
        console.info("In:", msg);
      }
      if (! peers) {
        // We're getting messages before everything is fully initialized
        console.warn("Message received before all modules loaded (ignoring):", msg);
        return;
      }
      if ((! msg.clientId) && MESSAGES_WITHOUT_CLIENTID.indexOf(msg.type) == -1) {
        console.warn("Got message without clientId, where clientId is required", msg);
        return;
      }
      if (msg.clientId) {
        msg.peer = peers.getPeer(msg.clientId, msg);
      }
      if (msg.type == "hello" || msg.type == "hello-back" || msg.type == "peer-update") {
        // We do this here to make sure this is run before any other
        // hello handlers:
        msg.peer.updateFromHello(msg);
      }
      if (msg.peer) {
        msg.sameUrl = msg.peer.url == currentUrl;
        if (!msg.peer.isSelf) {
          msg.peer.updateMessageDate(msg);
        }
      }
      session.hub.emit(msg.type, msg);
      TogetherJS._onmessage(msg);
    };
    channel = c;
    session.router.bindChannel(channel);
  }

  // FIXME: once we start looking at window.history we need to update this:
  var currentUrl = (location.href + "").replace(/\#.*$/, "");

  session.send = function (msg) {
    if (DEBUG && IGNORE_MESSAGES.indexOf(msg.type) == -1) {
      console.info("Send:", msg);
    }
    msg.clientId = session.clientId;
    channel.send(msg);
  };

  session.appSend = function (msg) {
    var type = msg.type;
    if (type.search(/^togetherjs\./) === 0) {
      type = type.substr("togetherjs.".length);
    } else if (type.search(/^app\./) === -1) {
      type = "app." + type;
    }
    msg.type = type;
    session.send(msg);
  };

  /****************************************
   * Standard message responses
   */

  /* Always say hello back, and keep track of peers: */
  session.hub.on("hello hello-back", function (msg) {
    if (msg.type == "hello") {
      sendHello(true);
    }
    if (session.isClient && (! msg.isClient) &&
        session.firstRun && session.timeHelloSent &&
        Date.now() - session.timeHelloSent < HELLO_BACK_CUTOFF) {
      processFirstHello(msg);
    }
  });

  session.hub.on("who", function (msg) {
    sendHello(true);
  });

  function processFirstHello(msg) {
    if (! msg.sameUrl) {
      var url = msg.url;
      if (msg.urlHash) {
        url += msg.urlHash;
      }
      require("ui").showUrlChangeMessage(msg.peer, url);
      location.href = url;
    }
  }

  session.timeHelloSent = null;

  function sendHello(helloBack) {
    var msg = session.makeHelloMessage(helloBack);
    if (! helloBack) {
      session.timeHelloSent = Date.now();
      peers.Self.url = msg.url;
    }
    session.send(msg);
  }

  session.makeHelloMessage = function (helloBack) {
    var msg = {
      name: peers.Self.name || peers.Self.defaultName,
      avatar: peers.Self.avatar,
      color: peers.Self.color,
      url: session.currentUrl(),
      urlHash: location.hash,
      // FIXME: titles update, we should track those changes:
      title: document.title,
      rtcSupported: session.RTCSupported,
      isClient: session.isClient
    };
    if (helloBack) {
      msg.type = "hello-back";
    } else {
      msg.type = "hello";
      msg.clientVersion = TogetherJS.version;
    }
    if (! TogetherJS.startup.continued) {
      msg.starting = true;
    }
    // This is a chance for other modules to effect the hello message:
    session.emit("prepare-hello", msg);
    return msg;
  };

  /****************************************
   * Lifecycle (start and end)
   */

  // These are Javascript files that implement features, and so must
  // be injected at runtime because they aren't pulled in naturally
  // via define().
  // ui must be the first item:
  var features = ["peers", "ui", "chat", "webrtc", "cursor", "startup","videos", "forms", "visibilityApi"];

  function getRoomName(prefix, maxSize) {
    var findRoom = TogetherJS.getConfig("hubBase").replace(/\/*$/, "") + "/findroom";
    return $.ajax({
      url: findRoom,
      dataType: "json",
      data: {prefix: prefix, max: maxSize}
    }).then(function (resp) {
      return resp.name;
    });
  }

  function initIdentityId() {
    return util.Deferred(function (def) {
      if (session.identityId) {
        def.resolve();
        return;
      }
      storage.get("identityId").then(function (identityId) {
        if (! identityId) {
          identityId = util.generateId();
          storage.set("identityId", identityId);
        }
        session.identityId = identityId;
        // We don't actually have to wait for the set to succede, so
        // long as session.identityId is set
        def.resolve();
      });
    });
  }

  initIdentityId.done = initIdentityId();

  function initShareId() {
    return util.Deferred(function (def) {
      var hash = location.hash;
      var shareId = session.shareId;
      var isClient = true;
      var set = true;
      var sessionId;
      session.firstRun = ! TogetherJS.startup.continued;
      if (! shareId) {
        if (TogetherJS.startup._joinShareId) {
          // Like, below, this *also* means we got the shareId from the hash
          // (in togetherjs.js):
          shareId = TogetherJS.startup._joinShareId;
        }
      }
      if (! shareId) {
        // FIXME: I'm not sure if this will ever happen, because togetherjs.js should
        // handle it
        var m = /&?togetherjs=([^&]*)/.exec(hash);
        if (m) {
          isClient = ! m[1];
          shareId = m[2];
          var newHash = hash.substr(0, m.index) + hash.substr(m.index + m[0].length);
          location.hash = newHash;
        }
      }
      return storage.tab.get("status").then(function (saved) {
        var findRoom = TogetherJS.getConfig("findRoom");
        if (findRoom && saved) {
          console.info("Ignoring findRoom in lieu of continued session");
        } else if (findRoom && TogetherJS.startup._joinShareId) {
          console.info("Ignoring findRoom in lieu of explicit invite to session");
        }
        if (findRoom && typeof findRoom == "string" && (! saved) && (! TogetherJS.startup._joinShareId)) {
          isClient = true;
          shareId = findRoom;
          sessionId = util.generateId();
        } else if (findRoom && (! saved) && (! TogetherJS.startup._joinShareId)) {
          assert(findRoom.prefix && typeof findRoom.prefix == "string", "Bad findRoom.prefix", findRoom);
          assert(findRoom.max && typeof findRoom.max == "number" && findRoom.max > 0,
                 "Bad findRoom.max", findRoom);
          sessionId = util.generateId();
          if (findRoom.prefix.search(/[^a-zA-Z0-9]/) != -1) {
            console.warn("Bad value for findRoom.prefix:", JSON.stringify(findRoom.prefix));
          }
          getRoomName(findRoom.prefix, findRoom.max).then(function (shareId) {
            // FIXME: duplicates code below:
            session.clientId = session.identityId + "." + sessionId;
            storage.tab.set("status", {reason: "joined", shareId: shareId, running: true, date: Date.now(), sessionId: sessionId});
            session.isClient = true;
            session.shareId = shareId;
            session.emit("shareId");
            def.resolve(session.shareId);
          });
          return;
        } else if (TogetherJS.startup._launch) {
          if (saved) {
            isClient = saved.reason == "joined";
            if (! shareId) {
              shareId = saved.shareId;
            }
            sessionId = saved.sessionId;
          } else {
            isClient = TogetherJS.startup.reason == "joined";
            assert(! sessionId);
            sessionId = util.generateId();
          }
          if (! shareId) {
            shareId = util.generateId();
          }
        } else if (saved) {
          isClient = saved.reason == "joined";
          TogetherJS.startup.reason = saved.reason;
          TogetherJS.startup.continued = true;
          shareId = saved.shareId;
          sessionId = saved.sessionId;
          // The only case when we don't need to set the storage status again is when
          // we're already set to be running
          set = ! saved.running;
        } else {
          throw new util.AssertionError("No saved status, and no startup._launch request; why did TogetherJS start?");
        }
        assert(session.identityId);
        session.clientId = session.identityId + "." + sessionId;
        if (set) {
          storage.tab.set("status", {reason: TogetherJS.startup.reason, shareId: shareId, running: true, date: Date.now(), sessionId: sessionId});
        }
        session.isClient = isClient;
        session.shareId = shareId;
        session.emit("shareId");
        def.resolve(session.shareId);
      });
    });
  }

  function initStartTarget() {
    var id;
    if (TogetherJS.startup.button) {
      id = TogetherJS.startup.button.id;
      if (id) {
        storage.set("startTarget", id);
      }
      return;
    }
    storage.get("startTarget").then(function (id) {
      var el = document.getElementById(id);
      if (el) {
        TogetherJS.startup.button = el;
      }
    });
  }

  session.start = function () {
    initStartTarget();
    initIdentityId().then(function () {
      initShareId().then(function () {
        readyForMessages = false;
        openChannel();
        require(["ui"], function (ui) {
          TogetherJS.running = true;
          ui.prepareUI();
          require(features, function () {
            $(function () {
              peers = require("peers");
              var startup = require("startup");
              session.emit("start");
              session.once("ui-ready", function () {
                readyForMessages = true;
                startup.start();
              });
              ui.activateUI();
              if (TogetherJS.getConfig("enableAnalytics")) {
                require(["analytics"], function (analytics) {
                  analytics.activate();
                });
              }
              peers._SelfLoaded.then(function () {
                sendHello(false);
              });
              TogetherJS.emit("ready");
            });
          });
        });
      });
    });
  };

  session.close = function (reason) {
    TogetherJS.running = false;
    var msg = {type: "bye"};
    if (reason) {
      msg.reason = reason;
    }
    session.send(msg);
    session.emit("close");
    var name = window.name;
    storage.tab.get("status").then(function (saved) {
      if (! saved) {
        console.warn("No session information saved in", "status." + name);
      } else {
        saved.running = false;
        saved.date = Date.now();
        storage.tab.set("status", saved);
      }
      channel.close();
      channel = null;
      session.shareId = null;
      session.emit("shareId");
      TogetherJS.emit("close");
      TogetherJS._teardown();
    });
  };

  session.on("start", function () {
    $(window).on("resize", resizeEvent);
  });
  session.on("close", function () {
    $(window).off("resize", resizeEvent);
  });
  function resizeEvent() {
    session.emit("resize");
  }

  if (TogetherJS.startup._launch) {
    setTimeout(session.start);
  }

  util.testExpose({
    getChannel: function () {
      return channel;
    }
  });

  return session;
});
<|endoftext|>


<commit_before>(function ($) {
    $(function() {
        $.modeltranslation = (function() {
            var mt = {
                languages: [],
                options: {
                    fieldTypes: 'input[type=text], input[type=file], textarea'
                },

                init: function(opts) {
                    $self = this;
                    $self.options = $.extend(this.options, opts)

                    if ($('body').hasClass('change-form')) {
                        var tabs, group,
                            fields = $self._getTranslatedFields();

                        // Discover languages
                        $.each(fields, function (name, languages) {
                            $.each(languages, function (lang, el) {
                                if ($.inArray(lang, mt.languages) < 0) {
                                    mt.languages.push(lang);
                                }
                            });
                        });

                        tabs = $self._createInlineTabs();

                        $self._createMainSwitch(tabs, fields);

                        // Adding new inlines, rebinding events
                        $('.add-item .add-handler').bind('click.modeltranslation', function(){
                            group = $($self).parents('.group');
                            setTimeout(function(){
                                $self._createInlineTabs(group.find('.items > .module:last').prev());
                            }, 200);
                        });
                    }
                    else if ($('body').hasClass('change-list')) {
                        tabs = this._createChangelistTabs();
                    }
                },

                // Inserts a select box to select language globally
                _createMainSwitch: function(tabs, fields) {
                    var $self = this,
                        grouped_translations = fields,
                        select = $('<select>');

                    $.each(mt.languages, function (i, language) {
                        select.append($('<option value="' + i + '">' + language + '</option>'));
                    });
                    select.change(function (e) {
                        $.each(tabs, function (i, tab) {
                            tab.tabs('select', parseInt(select.val()));
                        });
                    });
                    $('#content h1').append(' ').append(select);
                },

                // Create change list tabbing
                _createChangelistTabs: function() {
                    var translations = this._getTranslatedFields()
                    var tabs = []
                    var container = $('

').css('margin-bottom', 6)
                    var tabs = $('

').appendTo(container)
                    $.each(this.options.languages, function(i, lang) {
                        $('

')
                            .css({float: 'left'}).appendTo(tabs)
                            .find('a').bind('click.modeltranslation', function(){
                                var l = $(this).attr('href').replace('#', '')
                                $('.translated-field:not(.translation-'+ l +')').hide()
                                $('.translation-'+ l).show()
                                $(this).parent().addClass('ui-tabs-selected').siblings().removeClass('ui-tabs-selected')
                            }).attr('href', '#'+lang).text(lang)
                    })

                    // Insert toolbar only if there is translated fields
                    if (tabs.find('li').length) {
                        // Tweak table header
                        $('.changelist-results').find('thead th').each(function(i, t){
                            var th    = $(t)
                            var label = $.trim(th.find('a').text())
                            if (/\[\w{2}\]/.test(label)) {
                                match = label.match(/\[(\w{2})\]/)
                                if (match.length > 1) {
                                    th.addClass('translated-field translation-'+ match[1])
                                        .find('a').text(label.replace(/\ \[.+\]/, ''))
                                }
                            }
                        })

                        // Tweak rows
                        var fields = $('.modeltranslation')
                            .filter(this.options.fieldTypes).each(function(i, f){
                                var field = $(f)
                                $(f).parent().addClass('translated-field translation-'+ $(f).attr('id').slice(-2))
                            })

                        // hide innactive translations
                        $('.translated-field:not(.translation-'+ this.options.languages[0] +')').hide()

                        tabs.find('li:first').addClass('ui-tabs-selected')
                        return container.insertBefore('#changelist-form')
                    }
                },

                _createTabularInlineTabs: function($parent) {
                    var container, tabs_container, tabs_list, default_lang,
                        translations = $self._getTranslatedFields($parent);
                    if ($parent) {
                        container = $($parent).find('.group.tabular');
                    }
                    else {
                        container = $('.group.tabular');
                    }
                    tabs_container = $('

');
                    tabs_list = $('

').appendTo(tabs_container);
                    tabs_shim = $('
').appendTo(tabs_container); // can't use real tabs, so we fake them

                    tabs_container.insertAfter(container.find('> .tools'));
                    //tabs_container.appendTo(container.find('.thead'));

                    $.each(mt.languages, function (i, lang) {
                        if (i == 0) {
                            default_lang = lang;
                        }
                        var id = 'tab_'+ container.attr('id').replace('set-group', lang);
                        $('
' + lang + '
')
                            .appendTo(tabs_list)
                            .find('a').bind('click', function() {
                                var id = $(this).attr('href').replace('#', '');
                                $.each(mt.languages, function (i, lang) {
                                    var el = $('.'+ id.slice(0, -2) + lang)
                                    if (id.slice(-2) != lang) {
                                        el.hide();
                                    }
                                    else {
                                        el.show();
                                    }
                                });
                                return false;
                            });
                        $('
').appendTo(tabs_shim);
                    });

                    container.find('.thead .th').each(function(i, th){
                        th = $(th);
                        var classes = th.attr('class').split(' ');
                        // Remove language and brackets from field label, they are
                        // displayed in the tab already.
                        if (th.html()) {
                            th.html(th.html().replace(/\ \[.+\]/, ''));
                        }
                        $.each(classes, function(x, classname){
                            if ($.inArray(classname.slice(-2), mt.languages) > -1 && classname.slice(-3, -2) == '-') {
                                var c = "tab_"+ container.attr('id').replace('set-group', classname.slice(-2));
                                th.addClass(c);
                                if (classname.slice(-2) != default_lang) {
                                    th.hide();
                                }
                            }
                        });

                    });

                    tabs_container.tabs();
                   //tabs.push(tabs_container);
                    $.each(translations, function (name, languages) {
                        $.each(languages, function(lang, el){
                            var p = $(el).parent();
                            var classname = container.attr('id').replace('set-group', lang);
                            if (p.hasClass('td')) {
                                p.addClass('tab_'+classname);
                            }
                            if (lang != default_lang) {
                                p.hide();
                            }
                        });

                    });

                    /*
                    var tabs = [],
                        translations = this._getTranslatedFields($parent);
                    $.each(translations, function (name, languages) {
                        console.log(name, languages)
                        var tabs_container = $('

'),
                          tabs_list = $('

'),
                          insertion_point;
                        tabs_container.append(tabs_list);
                        $.each(languages, function (lang, el) {
                            if (!$(el).parent().hasClass('.td')) {
                                var container = $(el).closest('.row'),
                                  label = $('label', container),
                                  field_label = container.find('label'),
                                  id = 'tab_' + [name, lang].join('_'),
                                  panel, tab;
                                // Remove language and brackets from field label, they are
                                // displayed in the tab already.
                                if (field_label.html()) {
                                    field_label.html(field_label.html().replace(/\ \[.+\]/, ''));
                                }
                                if (!insertion_point) {
                                    insertion_point = {
                                        'insert': container.prev().length ? 'after' : container.next().length ? 'prepend' : 'append',
                                        'el': container.prev().length ? container.prev() : container.parent()
                                    };
                                }
                                container.find('script').remove();
                                panel = $('

').append(container);
                                tab = $('
' + lang + '
');
                                tabs_list.append(tab);
                                tabs_container.append(panel);
                            }
                        });
                        insertion_point.el[insertion_point.insert](tabs_container);
                        tabs_container.tabs();
                        tabs.push(tabs_container);
                    });
                    return tabs;
                    */
                },

                // Create change form tabbing
                _createInlineTabs: function($parent) {
                    var tabs = [],
                        translations = this._getTranslatedFields($parent);
                    $.each(translations, function (name, languages) {
                        var tabs_container = $('

'),
                          tabs_list = $('

'),
                          insertion_point;
                        tabs_container.append(tabs_list);
                        $.each(languages, function (lang, el) {
                            if (!$(el).parent().hasClass('.td')) {
                                var container = $(el).closest('.row'),
                                  label = $('label', container),
                                  field_label = container.find('label'),
                                  id = 'tab_' + [name, lang].join('_'),
                                  panel, tab;
                                // Remove language and brackets from field label, they are
                                // displayed in the tab already.
                                if (field_label.html()) {
                                    field_label.html(field_label.html().replace(/\ \[.+\]/, ''));
                                }
                                if (!insertion_point) {
                                    insertion_point = {
                                        'insert': container.prev().length ? 'after' : container.next().length ? 'prepend' : 'append',
                                        'el': container.prev().length ? container.prev() : container.parent()
                                    };
                                }
                                container.find('script').remove();
                                panel = $('

').append(container);
                                tab = $('
' + lang + '
');
                                tabs_list.append(tab);
                                tabs_container.append(panel);
                            }
                        });
                        insertion_point.el[insertion_point.insert](tabs_container);
                        tabs_container.tabs();
                        tabs.push(tabs_container);
                    });
                    this._createTabularInlineTabs($parent);
                    return tabs;
                },

                _getTranslatedFields: function($parent) {
                    /** Returns a grouped set of all text based model translation fields.
                     * The returned datastructure will look something like this:
                     * {
                     *   'title': {
                     *     'en': HTMLInputElement,
                     *     'fr': HTMLInputElement
                     *   },
                     *   'body': {
                     *     'en': HTMLTextAreaElement,
                     *     'fr': HTMLTextAreaElement
                     *   }
                     * }
                     */
                    var fields,
                        out    = {},
                        langs  = [];
                    if ($parent) {
                        fields = $($parent).find('.modeltranslation').filter(this.options.fieldTypes);
                    }
                    else {
                        fields = $('.modeltranslation').filter(this.options.fieldTypes);
                    }
                    //onAfterAdded

                    fields.each(function (i, el) {
                        var name = $(el).attr('name').split('_'),
                            lang = name.pop();
                        name = name.join('_');
                        langs.push(lang);
                        if (!/__prefix__/.test(name)) {
                            if (!out[name]) { out[name] = {} }
                            out[name][lang] = el;
                        }
                    })
                    this.options.languages = $.unique(langs.sort());
                    return out;
                }
            }
            return mt;
        }());
        $.modeltranslation.init();
    })
}(django.jQuery || jQuery || $));
<commit_msg>Removed main switch for modeltranslation<commit_after>(function ($) {
    $(function() {
        $.modeltranslation = (function() {
            var mt = {
                languages: [],
                options: {
                    fieldTypes: 'input[type=text], input[type=file], textarea'
                },

                init: function(opts) {
                    $self = this;
                    $self.options = $.extend(this.options, opts)

                    if ($('body').hasClass('change-form')) {
                        var tabs, group,
                            fields = $self._getTranslatedFields();

                        // Discover languages
                        $.each(fields, function (name, languages) {
                            $.each(languages, function (lang, el) {
                                if ($.inArray(lang, mt.languages) < 0) {
                                    mt.languages.push(lang);
                                }
                            });
                        });

                        tabs = $self._createInlineTabs();

                        //$self._createMainSwitch(tabs, fields);

                        // Adding new inlines, rebinding events
                        $('.add-item .add-handler').bind('click.modeltranslation', function(){
                            group = $($self).parents('.group');
                            setTimeout(function(){
                                $self._createInlineTabs(group.find('.items > .module:last').prev());
                            }, 200);
                        });
                    }
                    else if ($('body').hasClass('change-list')) {
                        tabs = this._createChangelistTabs();
                    }
                },

                // Inserts a select box to select language globally
                _createMainSwitch: function(tabs, fields) {
                    var $self = this,
                        grouped_translations = fields,
                        select = $('<select>');

                    $.each(mt.languages, function (i, language) {
                        select.append($('<option value="' + i + '">' + language + '</option>'));
                    });
                    select.change(function (e) {
                        $.each(tabs, function (i, tab) {
                            tab.tabs('select', parseInt(select.val()));
                        });
                    });
                    $('#content h1').append(' ').append(select);
                },

                // Create change list tabbing
                _createChangelistTabs: function() {
                    var translations = this._getTranslatedFields()
                    var tabs = []
                    var container = $('

').css('margin-bottom', 6)
                    var tabs = $('

').appendTo(container)
                    $.each(this.options.languages, function(i, lang) {
                        $('

')
                            .css({float: 'left'}).appendTo(tabs)
                            .find('a').bind('click.modeltranslation', function(){
                                var l = $(this).attr('href').replace('#', '')
                                $('.translated-field:not(.translation-'+ l +')').hide()
                                $('.translation-'+ l).show()
                                $(this).parent().addClass('ui-tabs-selected').siblings().removeClass('ui-tabs-selected')
                            }).attr('href', '#'+lang).text(lang)
                    })

                    // Insert toolbar only if there is translated fields
                    if (tabs.find('li').length) {
                        // Tweak table header
                        $('.changelist-results').find('thead th').each(function(i, t){
                            var th    = $(t)
                            var label = $.trim(th.find('a').text())
                            if (/\[\w{2}\]/.test(label)) {
                                match = label.match(/\[(\w{2})\]/)
                                if (match.length > 1) {
                                    th.addClass('translated-field translation-'+ match[1])
                                        .find('a').text(label.replace(/\ \[.+\]/, ''))
                                }
                            }
                        })

                        // Tweak rows
                        var fields = $('.modeltranslation')
                            .filter(this.options.fieldTypes).each(function(i, f){
                                var field = $(f)
                                $(f).parent().addClass('translated-field translation-'+ $(f).attr('id').slice(-2))
                            })

                        // hide innactive translations
                        $('.translated-field:not(.translation-'+ this.options.languages[0] +')').hide()

                        tabs.find('li:first').addClass('ui-tabs-selected')
                        return container.insertBefore('#changelist-form')
                    }
                },

                _createTabularInlineTabs: function($parent) {
                    var container, tabs_container, tabs_list, default_lang,
                        translations = $self._getTranslatedFields($parent),
                        tabs = [];
                    if ($parent) {
                        container = $($parent).find('.group.tabular');
                    }
                    else {
                        container = $('.group.tabular');
                    }
                    tabs_container = $('

');
                    tabs_list = $('

').appendTo(tabs_container);
                    tabs_shim = $('
').appendTo(tabs_container); // can't use real tabs, so we fake them

                    tabs_container.insertAfter(container.find('> .tools'));
                    //tabs_container.appendTo(container.find('.thead'));

                    $.each(mt.languages, function (i, lang) {
                        if (i == 0) {
                            default_lang = lang;
                        }
                        var id = 'tab_'+ container.attr('id').replace('set-group', lang);
                        $('
' + lang + '
')
                            .appendTo(tabs_list)
                            .find('a').bind('click', function() {
                                var id = $(this).attr('href').replace('#', '');
                                $.each(mt.languages, function (i, lang) {
                                    var el = $('.'+ id.slice(0, -2) + lang)
                                    if (id.slice(-2) != lang) {
                                        el.hide();
                                    }
                                    else {
                                        el.show();
                                    }
                                });
                                return false;
                            });
                        $('
').appendTo(tabs_shim);
                    });

                    container.find('.thead .th').each(function(i, th){
                        th = $(th);
                        var classes = th.attr('class').split(' ');
                        // Remove language and brackets from field label, they are
                        // displayed in the tab already.
                        if (th.html()) {
                            th.html(th.html().replace(/\ \[.+\]/, ''));
                        }
                        $.each(classes, function(x, classname){
                            if ($.inArray(classname.slice(-2), mt.languages) > -1 && classname.slice(-3, -2) == '-') {
                                var c = "tab_"+ container.attr('id').replace('set-group', classname.slice(-2));
                                th.addClass(c);
                                if (classname.slice(-2) != default_lang) {
                                    th.hide();
                                }
                            }
                        });

                    });

                    tabs_container.tabs();
                    tabs.push(tabs_container);
                    $.each(translations, function (name, languages) {
                        $.each(languages, function(lang, el){
                            var p = $(el).parent();
                            var classname = container.attr('id').replace('set-group', lang);
                            if (p.hasClass('td')) {
                                p.addClass('tab_'+classname);
                            }
                            if (lang != default_lang) {
                                p.hide();
                            }
                        });

                    });
                    return tabs;
                },

                // Create change form tabbing
                _createInlineTabs: function($parent) {
                    var tabs = [],
                        translations = this._getTranslatedFields($parent);
                    $.each(translations, function (name, languages) {
                        var tabs_container = $('

'),
                          tabs_list = $('

'),
                          insertion_point;
                        tabs_container.append(tabs_list);
                        $.each(languages, function (lang, el) {
                            if (!$(el).parent().hasClass('.td')) {
                                var container = $(el).closest('.row'),
                                  label = $('label', container),
                                  field_label = container.find('label'),
                                  id = 'tab_' + [name, lang].join('_'),
                                  panel, tab;
                                // Remove language and brackets from field label, they are
                                // displayed in the tab already.
                                if (field_label.html()) {
                                    field_label.html(field_label.html().replace(/\ \[.+\]/, ''));
                                }
                                if (!insertion_point) {
                                    insertion_point = {
                                        'insert': container.prev().length ? 'after' : container.next().length ? 'prepend' : 'append',
                                        'el': container.prev().length ? container.prev() : container.parent()
                                    };
                                }
                                container.find('script').remove();
                                panel = $('

').append(container);
                                tab = $('
' + lang + '
');
                                tabs_list.append(tab);
                                tabs_container.append(panel);
                            }
                        });
                        insertion_point.el[insertion_point.insert](tabs_container);
                        tabs_container.tabs();
                        tabs.push(tabs_container);
                    });
                    this._createTabularInlineTabs($parent);
                    return tabs;
                },

                _getTranslatedFields: function($parent) {
                    /** Returns a grouped set of all text based model translation fields.
                     * The returned datastructure will look something like this:
                     * {
                     *   'title': {
                     *     'en': HTMLInputElement,
                     *     'fr': HTMLInputElement
                     *   },
                     *   'body': {
                     *     'en': HTMLTextAreaElement,
                     *     'fr': HTMLTextAreaElement
                     *   }
                     * }
                     */
                    var fields,
                        out    = {},
                        langs  = [];
                    if ($parent) {
                        fields = $($parent).find('.modeltranslation').filter(this.options.fieldTypes);
                    }
                    else {
                        fields = $('.modeltranslation').filter(this.options.fieldTypes);
                    }
                    //onAfterAdded

                    fields.each(function (i, el) {
                        var name = $(el).attr('name').split('_'),
                            lang = name.pop();
                        name = name.join('_');
                        langs.push(lang);
                        if (!/__prefix__/.test(name)) {
                            if (!out[name]) { out[name] = {} }
                            out[name][lang] = el;
                        }
                    })
                    this.options.languages = $.unique(langs.sort());
                    return out;
                }
            }
            return mt;
        }());
        $.modeltranslation.init();
    })
}(django.jQuery || jQuery || $));
<|endoftext|>


<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * A service for generating all the important pages a user can visit.
 */
angular.module('navigation').factory('userPageService', ['$injector',
        function userPageService($injector) {

    // Get required types
    var ClientIdentifier = $injector.get('ClientIdentifier');
    var ConnectionGroup  = $injector.get('ConnectionGroup');
    var PageDefinition   = $injector.get('PageDefinition');
    var PermissionSet    = $injector.get('PermissionSet');

    // Get required services
    var $q                       = $injector.get('$q');
    var authenticationService    = $injector.get('authenticationService');
    var connectionGroupService   = $injector.get('connectionGroupService');
    var dataSourceService        = $injector.get('dataSourceService');
    var permissionService        = $injector.get('permissionService');
    var translationStringService = $injector.get('translationStringService');

    var service = {};

    /**
     * The home page to assign to a user if they can navigate to more than one
     * page.
     *
     * @type PageDefinition
     */
    var SYSTEM_HOME_PAGE = new PageDefinition({
        name : 'USER_MENU.ACTION_NAVIGATE_HOME',
        url  : '/'
    });

    /**
     * Returns an appropriate home page for the current user.
     *
     * @param {Object.<String, ConnectionGroup>} rootGroups
     *     A map of all root connection groups visible to the current user,
     *     where each key is the identifier of the corresponding data source.
     *
     * @param {Object.<String, PermissionSet>} permissions
     *     A map of all permissions granted to the current user, where each
     *     key is the identifier of the corresponding data source.
     *
     * @param {Object.<String,
     *
     * @returns {PageDefinition}
     *     The user's home page.
     */
    var generateHomePage = function generateHomePage(rootGroups, permissions) {

        var homePage = null;
        var settingsPages = generateSettingsPages(permissions);

        // If we have more than one setting page, return the main home page
        // and don't worry about checking for a single connection.
        if (settingsPages.length > 1)
            return SYSTEM_HOME_PAGE;

        // Determine whether a connection or balancing group should serve as
        // the home page
        for (var dataSource in rootGroups) {

            // Get corresponding root group
            var rootGroup = rootGroups[dataSource];

            // Get children
            var connections      = rootGroup.childConnections      || [];
            var connectionGroups = rootGroup.childConnectionGroups || [];

            // Calculate total number of root-level objects
            var totalRootObjects = connections.length + connectionGroups.length;

            // If exactly one connection or balancing group is available, use
            // that as the home page
            if (homePage === null && totalRootObjects === 1) {

                var connection      = connections[0];
                var connectionGroup = connectionGroups[0];

                // Only one connection present, use as home page
                if (connection) {
                    homePage = new PageDefinition({
                        name : connection.name,
                        url  : '/client/' + ClientIdentifier.toString({
                            dataSource : dataSource,
                            type       : ClientIdentifier.Types.CONNECTION,
                            id         : connection.identifier
                        })
                    });
                }

                // Only one balancing group present, use as home page
                if (connectionGroup
                        && connectionGroup.type === ConnectionGroup.Type.BALANCING
                        && _.isEmpty(connectionGroup.childConnections)
                        && _.isEmpty(connectionGroup.childConnectionGroups)) {
                    homePage = new PageDefinition({
                        name : connectionGroup.name,
                        url  : '/client/' + ClientIdentifier.toString({
                            dataSource : dataSource,
                            type       : ClientIdentifier.Types.CONNECTION_GROUP,
                            id         : connectionGroup.identifier
                        })
                    });
                }

            }

            // Otherwise, a connection or balancing group cannot serve as the
            // home page
            else if (totalRootObjects >= 1) {
                homePage = null;
                break;
            }

        } // end for each data source

        // Use default home page if no other is available
        return homePage || SYSTEM_HOME_PAGE;

    };

    /**
     * Returns a promise which resolves with an appropriate home page for the
     * current user.
     *
     * @returns {Promise.<Page>}
     *     A promise which resolves with the user's default home page.
     */
    service.getHomePage = function getHomePage() {

        var deferred = $q.defer();

        // Resolve promise using home page derived from root connection groups
        var getRootGroups = dataSourceService.apply(
            connectionGroupService.getConnectionGroupTree,
            authenticationService.getAvailableDataSources(),
            ConnectionGroup.ROOT_IDENTIFIER
        );
        var getPermissionSets = dataSourceService.apply(
            permissionService.getPermissions,
            authenticationService.getAvailableDataSources(),
            authenticationService.getCurrentUsername()
        );

        $q.all({
            rootGroups : getRootGroups,
            permissionsSets : getPermissionSets
        })
        .then(function rootConnectionGroupsPermissionsRetrieved(data) {
            deferred.resolve(generateHomePage(data.rootGroups,data.permissionsSets));
        });

        return deferred.promise;

    };

    /**
     * Returns all settings pages that the current user can visit. This can
     * include any of the various manage pages.
     *
     * @param {Object.<String, PermissionSet>} permissionSets
     *     A map of all permissions granted to the current user, where each
     *     key is the identifier of the corresponding data source.
     *
     * @returns {Page[]}
     *     An array of all settings pages that the current user can visit.
     */
    var generateSettingsPages = function generateSettingsPages(permissionSets) {

        var pages = [];

        var canManageUsers = [];
        var canManageConnections = [];
        var canViewConnectionRecords = [];
        var canManageSessions = [];

        // Inspect the contents of each provided permission set
        angular.forEach(authenticationService.getAvailableDataSources(), function inspectPermissions(dataSource) {

            // Get permissions for current data source, skipping if non-existent
            var permissions = permissionSets[dataSource];
            if (!permissions)
                return;

            // Do not modify original object
            permissions = angular.copy(permissions);

            // Ignore permission to update root group
            PermissionSet.removeConnectionGroupPermission(permissions,
                PermissionSet.ObjectPermissionType.UPDATE,
                ConnectionGroup.ROOT_IDENTIFIER);

            // Ignore permission to update self
            PermissionSet.removeUserPermission(permissions,
                PermissionSet.ObjectPermissionType.UPDATE,
                authenticationService.getCurrentUsername());

            // Determine whether the current user needs access to the user management UI
            if (
                    // System permissions
                       PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
                    || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_USER)

                    // Permission to update users
                    || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)

                    // Permission to delete users
                    || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.DELETE)

                    // Permission to administer users
                    || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER)
            ) {
                canManageUsers.push(dataSource);
            }

            // Determine whether the current user needs access to the connection management UI
            if (
                    // System permissions
                       PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
                    || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION)
                    || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP)

                    // Permission to update connections or connection groups
                    || PermissionSet.hasConnectionPermission(permissions,      PermissionSet.ObjectPermissionType.UPDATE)
                    || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)

                    // Permission to delete connections or connection groups
                    || PermissionSet.hasConnectionPermission(permissions,      PermissionSet.ObjectPermissionType.DELETE)
                    || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE)

                    // Permission to administer connections or connection groups
                    || PermissionSet.hasConnectionPermission(permissions,      PermissionSet.ObjectPermissionType.ADMINISTER)
                    || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER)
            ) {
                canManageConnections.push(dataSource);
            }

            // Determine whether the current user needs access to the session management UI or view connection history
            if (
                    // A user must be a system administrator to manage sessions
                    PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
            ) {
                canManageSessions.push(dataSource);
                canViewConnectionRecords.push(dataSource);
            }

        });

        // If user can manage sessions, add link to sessions management page
        if (canManageSessions.length) {
            pages.push(new PageDefinition({
                name : 'USER_MENU.ACTION_MANAGE_SESSIONS',
                url  : '/settings/sessions'
            }));
        }

        // If user can manage connections, add links for connection management pages
        angular.forEach(canViewConnectionRecords, function addConnectionHistoryLink(dataSource) {
            pages.push(new PageDefinition({
                name : [
                    'USER_MENU.ACTION_VIEW_HISTORY',
                    translationStringService.canonicalize('DATA_SOURCE_' + dataSource) + '.NAME'
                ],
                url  : '/settings/' + encodeURIComponent(dataSource) + '/history'
            }));
        });

        // If user can manage users, add link to user management page
        if (canManageUsers.length) {
            pages.push(new PageDefinition({
                name : 'USER_MENU.ACTION_MANAGE_USERS',
                url  : '/settings/users'
            }));
        }

        // If user can manage connections, add links for connection management pages
        angular.forEach(canManageConnections, function addConnectionManagementLink(dataSource) {
            pages.push(new PageDefinition({
                name : [
                    'USER_MENU.ACTION_MANAGE_CONNECTIONS',
                    translationStringService.canonicalize('DATA_SOURCE_' + dataSource) + '.NAME'
                ],
                url  : '/settings/' + encodeURIComponent(dataSource) + '/connections'
            }));
        });

        // Add link to user preferences (always accessible)
        pages.push(new PageDefinition({
            name : 'USER_MENU.ACTION_MANAGE_PREFERENCES',
            url  : '/settings/preferences'
        }));

        return pages;
    };

    /**
     * Returns a promise which resolves to an array of all settings pages that
     * the current user can visit. This can include any of the various manage
     * pages.
     *
     * @returns {Promise.<Page[]>}
     *     A promise which resolves to an array of all settings pages that the
     *     current user can visit.
     */
    service.getSettingsPages = function getSettingsPages() {

        var deferred = $q.defer();

        // Retrieve current permissions
        dataSourceService.apply(
            permissionService.getPermissions,
            authenticationService.getAvailableDataSources(),
            authenticationService.getCurrentUsername()
        )

        // Resolve promise using settings pages derived from permissions
        .then(function permissionsRetrieved(permissions) {
            deferred.resolve(generateSettingsPages(permissions));
        });

        return deferred.promise;

    };

    /**
     * Returns all the main pages that the current user can visit. This can
     * include the home page, manage pages, etc. In the case that there are no
     * applicable pages of this sort, it may return a client page.
     *
     * @param {Object.<String, ConnectionGroup>} rootGroups
     *     A map of all root connection groups visible to the current user,
     *     where each key is the identifier of the corresponding data source.
     *
     * @param {Object.<String, PermissionSet>} permissions
     *     A map of all permissions granted to the current user, where each
     *     key is the identifier of the corresponding data source.
     *
     * @returns {Page[]}
     *     An array of all main pages that the current user can visit.
     */
    var generateMainPages = function generateMainPages(rootGroups, permissions) {

        var pages = [];

        // Get home page and settings pages
        var homePage = generateHomePage(rootGroups, permissions);
        var settingsPages = generateSettingsPages(permissions);

        // Only include the home page in the list of main pages if the user
        // can navigate elsewhere.
        if (homePage === SYSTEM_HOME_PAGE || settingsPages.length)
            pages.push(homePage);

        // Add generic link to the first-available settings page
        if (settingsPages.length) {
            pages.push(new PageDefinition({
                name : 'USER_MENU.ACTION_MANAGE_SETTINGS',
                url  : settingsPages[0].url
            }));
        }

        return pages;
    };

    /**
     * Returns a promise which resolves to an array of all main pages that the
     * current user can visit. This can include the home page, manage pages,
     * etc. In the case that there are no applicable pages of this sort, it may
     * return a client page.
     *
     * @returns {Promise.<Page[]>}
     *     A promise which resolves to an array of all main pages that the
     *     current user can visit.
     */
    service.getMainPages = function getMainPages() {

        var deferred = $q.defer();

        var rootGroups  = null;
        var permissions = null;

        /**
         * Resolves the main pages retrieval promise, if possible. If
         * insufficient data is available, this function does nothing.
         */
        var resolveMainPages = function resolveMainPages() {
            if (rootGroups && permissions)
                deferred.resolve(generateMainPages(rootGroups, permissions));
        };

        // Retrieve root group, resolving main pages if possible
        dataSourceService.apply(
            connectionGroupService.getConnectionGroupTree,
            authenticationService.getAvailableDataSources(),
            ConnectionGroup.ROOT_IDENTIFIER
        )
        .then(function rootConnectionGroupsRetrieved(retrievedRootGroups) {
            rootGroups = retrievedRootGroups;
            resolveMainPages();
        });

        // Retrieve current permissions
        dataSourceService.apply(
            permissionService.getPermissions,
            authenticationService.getAvailableDataSources(),
            authenticationService.getCurrentUsername()
        )

        // Resolving main pages if possible
        .then(function permissionsRetrieved(retrievedPermissions) {
            permissions = retrievedPermissions;
            resolveMainPages();
        });

        return deferred.promise;

    };

    return service;

}]);
<commit_msg>GUACAMOLE-508: More comment clean up.<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * A service for generating all the important pages a user can visit.
 */
angular.module('navigation').factory('userPageService', ['$injector',
        function userPageService($injector) {

    // Get required types
    var ClientIdentifier = $injector.get('ClientIdentifier');
    var ConnectionGroup  = $injector.get('ConnectionGroup');
    var PageDefinition   = $injector.get('PageDefinition');
    var PermissionSet    = $injector.get('PermissionSet');

    // Get required services
    var $q                       = $injector.get('$q');
    var authenticationService    = $injector.get('authenticationService');
    var connectionGroupService   = $injector.get('connectionGroupService');
    var dataSourceService        = $injector.get('dataSourceService');
    var permissionService        = $injector.get('permissionService');
    var translationStringService = $injector.get('translationStringService');

    var service = {};

    /**
     * The home page to assign to a user if they can navigate to more than one
     * page.
     *
     * @type PageDefinition
     */
    var SYSTEM_HOME_PAGE = new PageDefinition({
        name : 'USER_MENU.ACTION_NAVIGATE_HOME',
        url  : '/'
    });

    /**
     * Returns an appropriate home page for the current user.
     *
     * @param {Object.<String, ConnectionGroup>} rootGroups
     *     A map of all root connection groups visible to the current user,
     *     where each key is the identifier of the corresponding data source.
     *
     * @param {Object.<String, PermissionSet>} permissions
     *     A map of all permissions granted to the current user, where each
     *     key is the identifier of the corresponding data source.
     *
     * @returns {PageDefinition}
     *     The user's home page.
     */
    var generateHomePage = function generateHomePage(rootGroups, permissions) {

        var homePage = null;
        var settingsPages = generateSettingsPages(permissions);

        // If user has access to settings pages, return home page and skip
        // evaluation for automatic connections.  The Preferences page is
        // a Settings page and is always visible, so we look for more than
        // one to indicate access to administrative pages.
        if (settingsPages.length > 1)
            return SYSTEM_HOME_PAGE;

        // Determine whether a connection or balancing group should serve as
        // the home page
        for (var dataSource in rootGroups) {

            // Get corresponding root group
            var rootGroup = rootGroups[dataSource];

            // Get children
            var connections      = rootGroup.childConnections      || [];
            var connectionGroups = rootGroup.childConnectionGroups || [];

            // Calculate total number of root-level objects
            var totalRootObjects = connections.length + connectionGroups.length;

            // If exactly one connection or balancing group is available, use
            // that as the home page
            if (homePage === null && totalRootObjects === 1) {

                var connection      = connections[0];
                var connectionGroup = connectionGroups[0];

                // Only one connection present, use as home page
                if (connection) {
                    homePage = new PageDefinition({
                        name : connection.name,
                        url  : '/client/' + ClientIdentifier.toString({
                            dataSource : dataSource,
                            type       : ClientIdentifier.Types.CONNECTION,
                            id         : connection.identifier
                        })
                    });
                }

                // Only one balancing group present, use as home page
                if (connectionGroup
                        && connectionGroup.type === ConnectionGroup.Type.BALANCING
                        && _.isEmpty(connectionGroup.childConnections)
                        && _.isEmpty(connectionGroup.childConnectionGroups)) {
                    homePage = new PageDefinition({
                        name : connectionGroup.name,
                        url  : '/client/' + ClientIdentifier.toString({
                            dataSource : dataSource,
                            type       : ClientIdentifier.Types.CONNECTION_GROUP,
                            id         : connectionGroup.identifier
                        })
                    });
                }

            }

            // Otherwise, a connection or balancing group cannot serve as the
            // home page
            else if (totalRootObjects >= 1) {
                homePage = null;
                break;
            }

        } // end for each data source

        // Use default home page if no other is available
        return homePage || SYSTEM_HOME_PAGE;

    };

    /**
     * Returns a promise which resolves with an appropriate home page for the
     * current user.
     *
     * @returns {Promise.<Page>}
     *     A promise which resolves with the user's default home page.
     */
    service.getHomePage = function getHomePage() {

        var deferred = $q.defer();

        // Resolve promise using home page derived from root connection groups
        var getRootGroups = dataSourceService.apply(
            connectionGroupService.getConnectionGroupTree,
            authenticationService.getAvailableDataSources(),
            ConnectionGroup.ROOT_IDENTIFIER
        );
        var getPermissionSets = dataSourceService.apply(
            permissionService.getPermissions,
            authenticationService.getAvailableDataSources(),
            authenticationService.getCurrentUsername()
        );

        $q.all({
            rootGroups : getRootGroups,
            permissionsSets : getPermissionSets
        })
        .then(function rootConnectionGroupsPermissionsRetrieved(data) {
            deferred.resolve(generateHomePage(data.rootGroups,data.permissionsSets));
        });

        return deferred.promise;

    };

    /**
     * Returns all settings pages that the current user can visit. This can
     * include any of the various manage pages.
     *
     * @param {Object.<String, PermissionSet>} permissionSets
     *     A map of all permissions granted to the current user, where each
     *     key is the identifier of the corresponding data source.
     *
     * @returns {Page[]}
     *     An array of all settings pages that the current user can visit.
     */
    var generateSettingsPages = function generateSettingsPages(permissionSets) {

        var pages = [];

        var canManageUsers = [];
        var canManageConnections = [];
        var canViewConnectionRecords = [];
        var canManageSessions = [];

        // Inspect the contents of each provided permission set
        angular.forEach(authenticationService.getAvailableDataSources(), function inspectPermissions(dataSource) {

            // Get permissions for current data source, skipping if non-existent
            var permissions = permissionSets[dataSource];
            if (!permissions)
                return;

            // Do not modify original object
            permissions = angular.copy(permissions);

            // Ignore permission to update root group
            PermissionSet.removeConnectionGroupPermission(permissions,
                PermissionSet.ObjectPermissionType.UPDATE,
                ConnectionGroup.ROOT_IDENTIFIER);

            // Ignore permission to update self
            PermissionSet.removeUserPermission(permissions,
                PermissionSet.ObjectPermissionType.UPDATE,
                authenticationService.getCurrentUsername());

            // Determine whether the current user needs access to the user management UI
            if (
                    // System permissions
                       PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
                    || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_USER)

                    // Permission to update users
                    || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)

                    // Permission to delete users
                    || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.DELETE)

                    // Permission to administer users
                    || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER)
            ) {
                canManageUsers.push(dataSource);
            }

            // Determine whether the current user needs access to the connection management UI
            if (
                    // System permissions
                       PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
                    || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION)
                    || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP)

                    // Permission to update connections or connection groups
                    || PermissionSet.hasConnectionPermission(permissions,      PermissionSet.ObjectPermissionType.UPDATE)
                    || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE)

                    // Permission to delete connections or connection groups
                    || PermissionSet.hasConnectionPermission(permissions,      PermissionSet.ObjectPermissionType.DELETE)
                    || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE)

                    // Permission to administer connections or connection groups
                    || PermissionSet.hasConnectionPermission(permissions,      PermissionSet.ObjectPermissionType.ADMINISTER)
                    || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER)
            ) {
                canManageConnections.push(dataSource);
            }

            // Determine whether the current user needs access to the session management UI or view connection history
            if (
                    // A user must be a system administrator to manage sessions
                    PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER)
            ) {
                canManageSessions.push(dataSource);
                canViewConnectionRecords.push(dataSource);
            }

        });

        // If user can manage sessions, add link to sessions management page
        if (canManageSessions.length) {
            pages.push(new PageDefinition({
                name : 'USER_MENU.ACTION_MANAGE_SESSIONS',
                url  : '/settings/sessions'
            }));
        }

        // If user can manage connections, add links for connection management pages
        angular.forEach(canViewConnectionRecords, function addConnectionHistoryLink(dataSource) {
            pages.push(new PageDefinition({
                name : [
                    'USER_MENU.ACTION_VIEW_HISTORY',
                    translationStringService.canonicalize('DATA_SOURCE_' + dataSource) + '.NAME'
                ],
                url  : '/settings/' + encodeURIComponent(dataSource) + '/history'
            }));
        });

        // If user can manage users, add link to user management page
        if (canManageUsers.length) {
            pages.push(new PageDefinition({
                name : 'USER_MENU.ACTION_MANAGE_USERS',
                url  : '/settings/users'
            }));
        }

        // If user can manage connections, add links for connection management pages
        angular.forEach(canManageConnections, function addConnectionManagementLink(dataSource) {
            pages.push(new PageDefinition({
                name : [
                    'USER_MENU.ACTION_MANAGE_CONNECTIONS',
                    translationStringService.canonicalize('DATA_SOURCE_' + dataSource) + '.NAME'
                ],
                url  : '/settings/' + encodeURIComponent(dataSource) + '/connections'
            }));
        });

        // Add link to user preferences (always accessible)
        pages.push(new PageDefinition({
            name : 'USER_MENU.ACTION_MANAGE_PREFERENCES',
            url  : '/settings/preferences'
        }));

        return pages;
    };

    /**
     * Returns a promise which resolves to an array of all settings pages that
     * the current user can visit. This can include any of the various manage
     * pages.
     *
     * @returns {Promise.<Page[]>}
     *     A promise which resolves to an array of all settings pages that the
     *     current user can visit.
     */
    service.getSettingsPages = function getSettingsPages() {

        var deferred = $q.defer();

        // Retrieve current permissions
        dataSourceService.apply(
            permissionService.getPermissions,
            authenticationService.getAvailableDataSources(),
            authenticationService.getCurrentUsername()
        )

        // Resolve promise using settings pages derived from permissions
        .then(function permissionsRetrieved(permissions) {
            deferred.resolve(generateSettingsPages(permissions));
        });

        return deferred.promise;

    };

    /**
     * Returns all the main pages that the current user can visit. This can
     * include the home page, manage pages, etc. In the case that there are no
     * applicable pages of this sort, it may return a client page.
     *
     * @param {Object.<String, ConnectionGroup>} rootGroups
     *     A map of all root connection groups visible to the current user,
     *     where each key is the identifier of the corresponding data source.
     *
     * @param {Object.<String, PermissionSet>} permissions
     *     A map of all permissions granted to the current user, where each
     *     key is the identifier of the corresponding data source.
     *
     * @returns {Page[]}
     *     An array of all main pages that the current user can visit.
     */
    var generateMainPages = function generateMainPages(rootGroups, permissions) {

        var pages = [];

        // Get home page and settings pages
        var homePage = generateHomePage(rootGroups, permissions);
        var settingsPages = generateSettingsPages(permissions);

        // Only include the home page in the list of main pages if the user
        // can navigate elsewhere.
        if (homePage === SYSTEM_HOME_PAGE || settingsPages.length)
            pages.push(homePage);

        // Add generic link to the first-available settings page
        if (settingsPages.length) {
            pages.push(new PageDefinition({
                name : 'USER_MENU.ACTION_MANAGE_SETTINGS',
                url  : settingsPages[0].url
            }));
        }

        return pages;
    };

    /**
     * Returns a promise which resolves to an array of all main pages that the
     * current user can visit. This can include the home page, manage pages,
     * etc. In the case that there are no applicable pages of this sort, it may
     * return a client page.
     *
     * @returns {Promise.<Page[]>}
     *     A promise which resolves to an array of all main pages that the
     *     current user can visit.
     */
    service.getMainPages = function getMainPages() {

        var deferred = $q.defer();

        var rootGroups  = null;
        var permissions = null;

        /**
         * Resolves the main pages retrieval promise, if possible. If
         * insufficient data is available, this function does nothing.
         */
        var resolveMainPages = function resolveMainPages() {
            if (rootGroups && permissions)
                deferred.resolve(generateMainPages(rootGroups, permissions));
        };

        // Retrieve root group, resolving main pages if possible
        dataSourceService.apply(
            connectionGroupService.getConnectionGroupTree,
            authenticationService.getAvailableDataSources(),
            ConnectionGroup.ROOT_IDENTIFIER
        )
        .then(function rootConnectionGroupsRetrieved(retrievedRootGroups) {
            rootGroups = retrievedRootGroups;
            resolveMainPages();
        });

        // Retrieve current permissions
        dataSourceService.apply(
            permissionService.getPermissions,
            authenticationService.getAvailableDataSources(),
            authenticationService.getCurrentUsername()
        )

        // Resolving main pages if possible
        .then(function permissionsRetrieved(retrievedPermissions) {
            permissions = retrievedPermissions;
            resolveMainPages();
        });

        return deferred.promise;

    };

    return service;

}]);
<|endoftext|>