<commit_before>const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "blocklists",
  "resource://tracking-protection-study/BlockLists.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebRequest",
  "resource://gre/modules/WebRequest.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
  "resource://gre/modules/MatchPattern.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
  "@mozilla.org/content/style-sheet-service;1", "nsIStyleSheetService");

Cu.importGlobalProperties(["URL"]);

// TODO disable built-in tracking protection
// const TRACKING_PROTECTION_PREF = "privacy.trackingprotection.enabled";
// const TRACKING_PROTECTION_UI_PREF = "privacy.trackingprotection.ui.enabled";
const DOORHANGER_ID = "onboarding-trackingprotection-notification";
const DOORHANGER_ICON = "chrome://browser/skin/tracking-protection-16.svg#enabled";

this.TrackingProtectionStudy = {
  /**
   * Open doorhanger-style notification on desired chrome window.
   *
   * @param {ChromeWindow} win
   * @param {String} message
   * @param {String} url
   */
  openDoorhanger(win, message, url) {
    const options = {
      popupIconURL: DOORHANGER_ICON,
      learnMoreURL: url,
      persistent: true,
      persistWhileVisible: true,
    };

    const action = {
      label: "Got it!",
      accessKey: "G",
      callback: () => {},
    };

    win.PopupNotifications.show(win.gBrowser.selectedBrowser, DOORHANGER_ID, message,
      null, action, [], options);
  },

  onOpenWindow(xulWindow) {
    var win = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor)
              .getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
    win.addEventListener("load", () => this.addEventListeners(win.gBrowser), {once: true});
  },

  onLocationChange(browser, progress, request, uri, flags) {
    if (this.state.blockedResources.has(browser)) {
      this.showPageAction(browser.getRootNode());
      this.setPageActionCounter(browser.getRootNode(), 0);
      this.state.blockedResources.set(browser, 0);
    }
    if (browser.currentURI.spec == "about:newtab") {
      let doc = browser.contentDocument;
      if (doc.getElementById("tracking-protection-message")) {
        return;
      }
      let minutes = this.state.timeSaved / 1000 / 60;
      // FIXME commented out for testing
      // if (minutes >= 1 && this.blockedRequests) {
      if (this.state.totalBlockedResources) {
        let message = this.newtab_message;
        message = message.replace("${blockedRequests}", this.state.totalBlockedResources);
        message = message.replace("${blockedEntities}", this.state.totalBlockedEntities);
        message = message.replace("${blockedSites}", this.state.totalBlockedSites);
        message = message.replace("${minutes}", minutes.toPrecision(3));

        let logo = doc.createElement("img");
        logo.src = "chrome://browser/skin/controlcenter/tracking-protection.svg#enabled";
        logo.style.height = 48;
        logo.style.width = 48;
        logo.style.float = "left";
        logo.style.padding = "5px";

        let span = doc.createElement("span");
        span.style.fontSize = "24px";
        span.style.fontWeight = "lighter";
        span.style.float = "right";
        span.style.padding = "5px";
        span.innerHTML = message;

        let newContainer = doc.createElement("div");
        newContainer.id = "tracking-protection-message";
        newContainer.style.padding = "24px";
        newContainer.append(logo);
        newContainer.append(span);

        console.log("rhelmer debug", doc);
        let container = doc.getElementById("newtab-margin-top");
        container.append(newContainer);
      }
    }
  },

  onBeforeRequest(details) {
    if (details && details.url && details.browser) {
      let browser = details.browser;
      let currentURI = browser.currentURI;

      if (!currentURI) {
        return;
      }

      if (!details.originUrl) {
        return;
      }

      let currentHost = currentURI.host;
      let host = new URL(details.originUrl).host;

      // Block third-party requests only.
      if (currentHost != host && blocklists.hostInBlocklist(this.state.blocklist, host)) {
        let counter;
        if (this.state.blockedResources.has(details.browser)) {
          counter = this.state.blockedResources.get(details.browser);
          counter++;
        } else {
          counter = 1;
        }

        // TODO enable allowed hosts.
        if (this.state.allowedHosts.has(currentHost)) {
          this.state.totalAllowedResources += 1;
        } else {
          this.state.totalBlockedResources += 1;
        }

        this.state.blockedResources.set(details.browser, counter);

        let enumerator = Services.wm.getEnumerator("navigator:browser");
        while (enumerator.hasMoreElements()) {
          let win = enumerator.getNext();
          if (win === Services.appShell.hiddenDOMWindow) {
            continue;
          }

          if (details.browser == win.gBrowser.selectedBrowser) {
            this.showPageAction(browser.getRootNode());
            this.setPageActionCounter(browser.getRootNode(), counter);
          }
        }
        return {cancel: true};
      }
    }
  },

  /**
   * Shows the page action button for the current window.
   */
  showPageAction(doc) {
    let win = Services.wm.getMostRecentWindow("navigator:browser");
    let currentHost = win.gBrowser.currentURI.host;

    let button = doc.getElementById("tracking-protection-study-button");
    if (button) {
      button.parentElement.removeChild(button);
    }
    doc.getElementById("tracking")
    let urlbar = doc.getElementById("urlbar-icons");

    let panel = doc.createElement("panel");
    panel.setAttribute("id", "tracking-protection-study-panel");
    panel.setAttribute("type", "arrow");
    panel.setAttribute("level", "parent");
    let panelHbox = doc.createElement("hbox");

    let controls = doc.createElement("vbox");

    let group = doc.createElement("radiogroup");
    let enabled = doc.createElement("radio");
    enabled.setAttribute("label", "Enable on this site");
    enabled.addEventListener("click", () => {
      if (this.state.allowedHosts.has(currentHost)) {
        this.state.allowedHosts.delete(currentHost);
      }
      win.gBrowser.reload();
    });
    let disabled = doc.createElement("radio");
    disabled.setAttribute("label", "Disable on this site");
    disabled.addEventListener("click", () => {
      this.state.allowedHosts.add(currentHost);
      win.gBrowser.reload();
    });
    if (this.state.allowedHosts.has(currentHost)) {
      disabled.setAttribute("selected", true);
    } else {
      enabled.setAttribute("selected", true);
    }
    group.append(enabled);
    group.append(disabled);
    controls.append(group);

    let footer = doc.createElement("vbox");
    footer.setAttribute("value", "If the website appears broken, consider disabling" +
                                  "tracking protection and refreshing the page.");

    panelHbox.append(controls);
    panelHbox.append(footer);
    panel.append(panelHbox);

    button = doc.createElement("toolbarbutton");
    if (this.state.allowedHosts.has(currentHost)) {
      button.style.backgroundColor = "yellow";
    } else {
      button.style.backgroundColor = "green";
    }
    button.setAttribute("id", "tracking-protection-study-button");
    button.setAttribute("image", "chrome://browser/skin/controlcenter/tracking-protection.svg#enabled");
    button.append(panel);
    button.addEventListener("command", event => {
      doc.getElementById("panel");
      panel.openPopup(button);
    });

    urlbar.append(button);
  },

  setPageActionCounter(doc, counter) {
    let toolbarButton = doc.getElementById("tracking-protection-study-button");
    if (toolbarButton) {
      toolbarButton.setAttribute("label", counter);
    }
  },

  hidePageAction(doc) {
    let button = doc.getElementById("tracking-protection-study-button");
    if (button) {
      button.parentElement.removeChild(button);
    }
  },

  onTabChange(evt) {
    let win = evt.target.ownerGlobal;
    let currentURI = win.gBrowser.currentURI;
    if (currentURI.scheme != "http" && currentURI.scheme != "https") {
      this.hidePageAction(win.document);
      return;
    }

    let currentWin = Services.wm.getMostRecentWindow("navigator:browser");

    if (win == currentWin) {
      this.hidePageAction(win.document);
      let counter = this.state.blockedResources.get(win.gBrowser.selectedBrowser);

      if (counter) {
        this.showPageAction(win.document);
        this.setPageActionCounter(win.document, counter);
      }
    }
  },

  /**
   * Open URL in new tab on desired chrome window.
   *
   * @param {ChromeWindow} win
   * @param {String} message
   * @param {String} url
   * @param {bool} foreground - true if this tab should open in the foreground.
   */
  openURL(win, message, url, foreground = true) {
    const tab = win.gBrowser.addTab(url);
    if (foreground) {
      win.gBrowser.selectedTab = tab;
    }
  },

  async init() {
    // define treatments as STRING: fn(browserWindow, url)
    this.TREATMENTS = {
      doorhanger: this.openDoorhanger,
      opentab: this.openURL,
    }

    // TODO hardcode for the moment, but get from distribution ID instead
    this.treatment = "opentab";
    this.distribution_id = "test123";
    let newtab_messages = [
      "Firefox blocked ${blockedRequests} trackers today
 from ${blockedEntities} companies that track your browsing",
      "Firefox blocked ${blockedRequests} trackers today
 and saved you ${minutes} minutes",
      "Firefox blocked ${blockedRequests} ads today from
 ${blockedSites} different websites"
    ];
    this.newtab_message = newtab_messages[0];

    // run once now on the most recent window.
    let win = Services.wm.getMostRecentWindow("navigator:browser");

    if (this.treatment === "ALL") {
      Object.keys(this.TREATMENTS).forEach((key, index) => {
        if (Object.prototype.hasOwnProperty.call(this.TREATMENTS, key)) {
          this.TREATMENTS[key](win, this.message, this.url);
        }
      });
    } else if (this.treatment in this.TREATMENTS) {
      this.TREATMENTS[this.treatment](win, this.message, this.url);
    }

    this.state = {
      timeSave: 0,
      blocklist: new Map(),
      allowedHosts: new Set(),
      reportedHosts: {},
      entityList: {},
      blockedResources: new Map(),
      totalBlockedResources: 0,
      totalAllowedResources: 0
    }

    await blocklists.loadLists(this.state);

    let filter = {urls: new MatchPattern("*://*/*")};
    this.onBeforeRequest = this.onBeforeRequest.bind(this);

    WebRequest.onBeforeRequest.addListener(this.onBeforeRequest, filter, ["blocking"]);

    let url = "resource://tracking-protection-study/tracking-protection-study.css";
    let uri = Services.io.newURI(url);
    styleSheetService.loadAndRegisterSheet(uri, styleSheetService.AGENT_SHEET);

    // Add listeners to all open windows.
    let enumerator = Services.wm.getEnumerator("navigator:browser");
    while (enumerator.hasMoreElements()) {
      let win = enumerator.getNext();
      if (win === Services.appShell.hiddenDOMWindow) {
        continue;
      }

      let gBrowser = win.gBrowser;
      this.addEventListeners(gBrowser);
    }

    // Attach to any new windows.
    Services.wm.addListener(this);
  },

  addEventListeners(gBrowser) {
    this.onTabChange = this.onTabChange.bind(this);

    gBrowser.addTabsProgressListener(this);
    gBrowser.tabContainer.addEventListener("TabSelect", this.onTabChange);
  },

  uninit() {
    // Remove listeners from all open windows.
    let enumerator = Services.wm.getEnumerator("navigator:browser");
    while (enumerator.hasMoreElements()) {
      let win = enumerator.getNext();
      if (win === Services.appShell.hiddenDOMWindow) {
        continue;
      }

      let button = win.document.getElementById("tracking-protection-study")
      if (button) {
        button.parentElement.removeChild(button);
      }

      WebRequest.onBeforeRequest.removeListener(this.onBeforeRequest);
      win.gBrowser.removeTabsProgressListener(this);
      win.gBrowser.tabContainer.removeEventListener("TabSelect", this.onTabChange);

      Services.wm.removeListener(this);
    }

    let url = "chrome://tracking-protection-study/content/tracking-protection-study.css";
    let uri = Services.io.newURI(url);
    styleSheetService.unregisterSheet(uri, styleSheetService.AGENT_SHEET);
  }
}

this.shutdown = function() {
  TrackingProtectionStudy.uninit();
  Cu.unload("resource://tracking-protection-study/Blocklists.jsm");
};

this.install = function(data, reason) {};

this.startup = async function(data, reason) {
  TrackingProtectionStudy.init();
};

this.shutdown = this.uninstall = function(data, reason) {

  TrackingProtectionStudy.uninit();
}
<commit_msg>unregister stylesheet correctly, and try waiting for about:newtab load<commit_after>const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "blocklists",
  "resource://tracking-protection-study/BlockLists.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebRequest",
  "resource://gre/modules/WebRequest.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
  "resource://gre/modules/MatchPattern.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
  "@mozilla.org/content/style-sheet-service;1", "nsIStyleSheetService");

Cu.importGlobalProperties(["URL"]);

// TODO disable built-in tracking protection
// const TRACKING_PROTECTION_PREF = "privacy.trackingprotection.enabled";
// const TRACKING_PROTECTION_UI_PREF = "privacy.trackingprotection.ui.enabled";
const DOORHANGER_ID = "onboarding-trackingprotection-notification";
const DOORHANGER_ICON = "chrome://browser/skin/tracking-protection-16.svg#enabled";
const STYLESHEET_URL = "resource://tracking-protection-study/tracking-protection-study.css";

this.TrackingProtectionStudy = {
  /**
   * Open doorhanger-style notification on desired chrome window.
   *
   * @param {ChromeWindow} win
   * @param {String} message
   * @param {String} url
   */
  openDoorhanger(win, message, url) {
    const options = {
      popupIconURL: DOORHANGER_ICON,
      learnMoreURL: url,
      persistent: true,
      persistWhileVisible: true,
    };

    const action = {
      label: "Got it!",
      accessKey: "G",
      callback: () => {},
    };

    win.PopupNotifications.show(win.gBrowser.selectedBrowser, DOORHANGER_ID, message,
      null, action, [], options);
  },

  onOpenWindow(xulWindow) {
    var win = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor)
              .getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
    win.addEventListener("load", () => this.addEventListeners(win.gBrowser), {once: true});
  },

  onLocationChange(browser, progress, request, uri, flags) {
    if (this.state.blockedResources.has(browser)) {
      this.showPageAction(browser.getRootNode());
      this.setPageActionCounter(browser.getRootNode(), 0);
      this.state.blockedResources.set(browser, 0);
    }
    let win = browser.ownerGlobal;
    win.addEventListener("load", () => {
      console.log("rhelmer debug");
      if (browser.currentURI.spec == "about:newtab") {
        let doc = browser.contentDocument;
        if (doc.getElementById("tracking-protection-message")) {
          return;
        }
        let minutes = this.state.timeSaved / 1000 / 60;
        // FIXME commented out for testing
        // if (minutes >= 1 && this.blockedRequests) {
        if (this.state.totalBlockedResources) {
          let message = this.newtab_message;
          message = message.replace("${blockedRequests}", this.state.totalBlockedResources);
          message = message.replace("${blockedEntities}", this.state.totalBlockedEntities);
          message = message.replace("${blockedSites}", this.state.totalBlockedSites);
          message = message.replace("${minutes}", minutes.toPrecision(3));

          let logo = doc.createElement("img");
          logo.src = "chrome://browser/skin/controlcenter/tracking-protection.svg#enabled";
          logo.style.height = 48;
          logo.style.width = 48;
          logo.style.float = "left";
          logo.style.padding = "5px";

          let span = doc.createElement("span");
          span.style.fontSize = "24px";
          span.style.fontWeight = "lighter";
          span.style.float = "right";
          span.style.padding = "5px";
          span.innerHTML = message;

          let newContainer = doc.createElement("div");
          newContainer.id = "tracking-protection-message";
          newContainer.style.padding = "24px";
          newContainer.append(logo);
          newContainer.append(span);

          console.log("rhelmer debug", doc);
          let container = doc.getElementById("newtab-margin-top");
          container.append(newContainer);
        }
      }
    }, {once: true});
  },

  onBeforeRequest(details) {
    if (details && details.url && details.browser) {
      let browser = details.browser;
      let currentURI = browser.currentURI;

      if (!currentURI) {
        return;
      }

      if (!details.originUrl) {
        return;
      }

      let currentHost = currentURI.host;
      let host = new URL(details.originUrl).host;

      // Block third-party requests only.
      if (currentHost != host && blocklists.hostInBlocklist(this.state.blocklist, host)) {
        let counter;
        if (this.state.blockedResources.has(details.browser)) {
          counter = this.state.blockedResources.get(details.browser);
          counter++;
        } else {
          counter = 1;
        }

        // TODO enable allowed hosts.
        if (this.state.allowedHosts.has(currentHost)) {
          this.state.totalAllowedResources += 1;
        } else {
          this.state.totalBlockedResources += 1;
        }

        this.state.blockedResources.set(details.browser, counter);

        let enumerator = Services.wm.getEnumerator("navigator:browser");
        while (enumerator.hasMoreElements()) {
          let win = enumerator.getNext();
          if (win === Services.appShell.hiddenDOMWindow) {
            continue;
          }

          if (details.browser == win.gBrowser.selectedBrowser) {
            this.showPageAction(browser.getRootNode());
            this.setPageActionCounter(browser.getRootNode(), counter);
          }
        }
        return {cancel: true};
      }
    }
  },

  /**
   * Shows the page action button for the current window.
   */
  showPageAction(doc) {
    let win = Services.wm.getMostRecentWindow("navigator:browser");
    let currentHost = win.gBrowser.currentURI.host;

    let button = doc.getElementById("tracking-protection-study-button");
    if (button) {
      button.parentElement.removeChild(button);
    }
    doc.getElementById("tracking")
    let urlbar = doc.getElementById("urlbar-icons");

    let panel = doc.createElement("panel");
    panel.setAttribute("id", "tracking-protection-study-panel");
    panel.setAttribute("type", "arrow");
    panel.setAttribute("level", "parent");
    let panelHbox = doc.createElement("hbox");

    let controls = doc.createElement("vbox");

    let group = doc.createElement("radiogroup");
    let enabled = doc.createElement("radio");
    enabled.setAttribute("label", "Enable on this site");
    enabled.addEventListener("click", () => {
      if (this.state.allowedHosts.has(currentHost)) {
        this.state.allowedHosts.delete(currentHost);
      }
      win.gBrowser.reload();
    });
    let disabled = doc.createElement("radio");
    disabled.setAttribute("label", "Disable on this site");
    disabled.addEventListener("click", () => {
      this.state.allowedHosts.add(currentHost);
      win.gBrowser.reload();
    });
    if (this.state.allowedHosts.has(currentHost)) {
      disabled.setAttribute("selected", true);
    } else {
      enabled.setAttribute("selected", true);
    }
    group.append(enabled);
    group.append(disabled);
    controls.append(group);

    let footer = doc.createElement("vbox");
    footer.setAttribute("value", "If the website appears broken, consider disabling" +
                                  "tracking protection and refreshing the page.");

    panelHbox.append(controls);
    panelHbox.append(footer);
    panel.append(panelHbox);

    button = doc.createElement("toolbarbutton");
    if (this.state.allowedHosts.has(currentHost)) {
      button.style.backgroundColor = "yellow";
    } else {
      button.style.backgroundColor = "green";
    }
    button.setAttribute("id", "tracking-protection-study-button");
    button.setAttribute("image", "chrome://browser/skin/controlcenter/tracking-protection.svg#enabled");
    button.append(panel);
    button.addEventListener("command", event => {
      doc.getElementById("panel");
      panel.openPopup(button);
    });

    urlbar.append(button);
  },

  setPageActionCounter(doc, counter) {
    let toolbarButton = doc.getElementById("tracking-protection-study-button");
    if (toolbarButton) {
      toolbarButton.setAttribute("label", counter);
    }
  },

  hidePageAction(doc) {
    let button = doc.getElementById("tracking-protection-study-button");
    if (button) {
      button.parentElement.removeChild(button);
    }
  },

  onTabChange(evt) {
    let win = evt.target.ownerGlobal;
    let currentURI = win.gBrowser.currentURI;
    if (currentURI.scheme != "http" && currentURI.scheme != "https") {
      this.hidePageAction(win.document);
      return;
    }

    let currentWin = Services.wm.getMostRecentWindow("navigator:browser");

    if (win == currentWin) {
      this.hidePageAction(win.document);
      let counter = this.state.blockedResources.get(win.gBrowser.selectedBrowser);

      if (counter) {
        this.showPageAction(win.document);
        this.setPageActionCounter(win.document, counter);
      }
    }
  },

  /**
   * Open URL in new tab on desired chrome window.
   *
   * @param {ChromeWindow} win
   * @param {String} message
   * @param {String} url
   * @param {bool} foreground - true if this tab should open in the foreground.
   */
  openURL(win, message, url, foreground = true) {
    const tab = win.gBrowser.addTab(url);
    if (foreground) {
      win.gBrowser.selectedTab = tab;
    }
  },

  async init() {
    // define treatments as STRING: fn(browserWindow, url)
    this.TREATMENTS = {
      doorhanger: this.openDoorhanger,
      opentab: this.openURL,
    }

    // TODO hardcode for the moment, but get from distribution ID instead
    this.treatment = "opentab";
    this.distribution_id = "test123";
    let newtab_messages = [
      "Firefox blocked ${blockedRequests} trackers today
 from ${blockedEntities} companies that track your browsing",
      "Firefox blocked ${blockedRequests} trackers today
 and saved you ${minutes} minutes",
      "Firefox blocked ${blockedRequests} ads today from
 ${blockedSites} different websites"
    ];
    this.newtab_message = newtab_messages[0];

    // run once now on the most recent window.
    let win = Services.wm.getMostRecentWindow("navigator:browser");

    if (this.treatment === "ALL") {
      Object.keys(this.TREATMENTS).forEach((key, index) => {
        if (Object.prototype.hasOwnProperty.call(this.TREATMENTS, key)) {
          this.TREATMENTS[key](win, this.message, this.url);
        }
      });
    } else if (this.treatment in this.TREATMENTS) {
      this.TREATMENTS[this.treatment](win, this.message, this.url);
    }

    this.state = {
      timeSave: 0,
      blocklist: new Map(),
      allowedHosts: new Set(),
      reportedHosts: {},
      entityList: {},
      blockedResources: new Map(),
      totalBlockedResources: 0,
      totalAllowedResources: 0
    }

    await blocklists.loadLists(this.state);

    let filter = {urls: new MatchPattern("*://*/*")};
    this.onBeforeRequest = this.onBeforeRequest.bind(this);

    WebRequest.onBeforeRequest.addListener(this.onBeforeRequest, filter, ["blocking"]);

    let uri = Services.io.newURI(STYLESHEET_URL);
    styleSheetService.loadAndRegisterSheet(uri, styleSheetService.AGENT_SHEET);

    // Add listeners to all open windows.
    let enumerator = Services.wm.getEnumerator("navigator:browser");
    while (enumerator.hasMoreElements()) {
      let win = enumerator.getNext();
      if (win === Services.appShell.hiddenDOMWindow) {
        continue;
      }

      let gBrowser = win.gBrowser;
      this.addEventListeners(gBrowser);
    }

    // Attach to any new windows.
    Services.wm.addListener(this);
  },

  addEventListeners(gBrowser) {
    this.onTabChange = this.onTabChange.bind(this);

    gBrowser.addTabsProgressListener(this);
    gBrowser.tabContainer.addEventListener("TabSelect", this.onTabChange);
  },

  uninit() {
    // Remove listeners from all open windows.
    let enumerator = Services.wm.getEnumerator("navigator:browser");
    while (enumerator.hasMoreElements()) {
      let win = enumerator.getNext();
      if (win === Services.appShell.hiddenDOMWindow) {
        continue;
      }

      let button = win.document.getElementById("tracking-protection-study")
      if (button) {
        button.parentElement.removeChild(button);
      }

      WebRequest.onBeforeRequest.removeListener(this.onBeforeRequest);
      win.gBrowser.removeTabsProgressListener(this);
      win.gBrowser.tabContainer.removeEventListener("TabSelect", this.onTabChange);

      Services.wm.removeListener(this);
    }

    let uri = Services.io.newURI(STYLESHEET_URL);
    styleSheetService.unregisterSheet(uri, styleSheetService.AGENT_SHEET);
  }
}

this.shutdown = function() {
  TrackingProtectionStudy.uninit();
  Cu.unload("resource://tracking-protection-study/Blocklists.jsm");
};

this.install = function(data, reason) {};

this.startup = async function(data, reason) {
  TrackingProtectionStudy.init();
};

this.shutdown = this.uninstall = function(data, reason) {

  TrackingProtectionStudy.uninit();
}
<|endoftext|>


<commit_before>'use strict';

const log = require("../../net2/logger")('httpd');

const express = require('express');
const https = require('https');
const forge = require('node-forge');
const qs = require('querystring');
const path = require('path');

const port = 8880;
const httpsPort = 8883;
const enableHttps = true;

const Promise = require('bluebird');
const rclient = require('../../util/redis_manager.js').getRedisClient()

const intel = require('./intel.js')(rclient);

const VIEW_PATH = 'firewalla_view';
const STATIC_PATH = 'firewalla_static';

process.title = "FireBlue";

class App {
  constructor() {
    this.app = express();

    this.app.engine('mustache', require('mustache-express')());
    this.app.set('view engine', 'mustache');

    this.app.set('views', path.join(__dirname, VIEW_PATH));
    //this.app.disable('view cache'); //for debug only

    this.routes();
  }

  routes() {
    this.router = express.Router();
    this.router.all('/block', async (req, res) => {
      const hostname = req.hostname;
      const url = qs.unescape(req.query.url);
      const ip = req.ip;
      const method = req.method;
      const count = qs.unescape(req.query.count);

      log.info("Got a request in block views");

      res.render('block', {hostname, url, ip, method, count});
    })

    this.app.use('/' + VIEW_PATH, this.router);
    this.app.use('/' + STATIC_PATH, express.static(path.join(__dirname, STATIC_PATH)));

    this.app.use('*', async (req, res) => {
      log.info("Got a request in *");

      if (!req.originalUrl.includes(VIEW_PATH)) {

        // as a workaround, redirect all traffic as porn
        // TBD implementation a multi-port FireBlue to handle multiple category traffic
        let redirect = await rclient.hgetAsync('redirect','porn')
        redirect = redirect || "google.com"

        if(redirect) {
          res.status(303).location(redirect).send().end()
          return
        }

        let cat = await intel.check(req.hostname);

        log.info(`${req.hostname} 's category is ${cat}`);

        switch(cat) {
          case 'porn':
            await this.isPorn(req, res);
            break;
          case 'ad':
            await this.isAd(req, res);
            break;
          default:
            res.status(200).send().end();
        }
      }
    });
  }

  start() {
    this.app.listen(port, () => log.info(`Httpd listening on port ${port}!`));

    if (enableHttps) {
      const httpsOptions = this.genHttpsOptions();
      https.createServer(httpsOptions, this.app).listen(httpsPort, () => log.info(`Httpd listening on port ${httpsPort}!`));
    }
  }

  async isPorn(req, res) {
    let count = await rclient.hincrbyAsync('block:stats', 'porn', 1);
    let params = qs.stringify({hostname: req.hostname, url: req.originalUrl, count});
    let location = `/${VIEW_PATH}/block`;

    const redirect = await rclient.hgetAsync('redirect','porn')
    if(redirect) {
      res.status(303).location(redirect).send().end()
    } else {
      res.status(303).location(`${location}?${params}`).send().end();
    }

    log.info(`Total porn blocked: ${count}`);
  }

  async isAd(req, res) {
    res.status(200).send().end();
    let count = await rclient.hincrbyAsync('block:stats', 'ad', 1);
    log.info(`Total ad blocked: ${count}`);
  }

  genHttpsOptions() {
    // generate a keypair and create an X.509v3 certificate
    const pki = forge.pki;
    console.log('Generating 1024-bit key-pair...');
    const keys = pki.rsa.generateKeyPair(1024);
    console.log('Key-pair created.');

    console.log('Creating self-signed certificate...');
    const cert = pki.createCertificate();
    cert.publicKey = keys.publicKey;
    cert.serialNumber = '01';
    cert.validity.notBefore = new Date();
    cert.validity.notAfter = new Date();
    cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
    let attrs = [{
      name: 'commonName',
      value: 'blackhole.firewalla.com'
    }, {
      name: 'countryName',
      value: 'US'
    }, {
      shortName: 'ST',
      value: 'New York'
    }, {
      name: 'localityName',
      value: 'Brooklyn'
    }, {
      name: 'organizationName',
      value: 'BLACKHOLE'
    }, {
      shortName: 'OU',
      value: 'BLACKHOLE'
    }];
    cert.setSubject(attrs);
    cert.setIssuer(attrs);
    cert.setExtensions([{
      name: 'basicConstraints',
      cA: true/*,
    pathLenConstraint: 4*/
    }, {
      name: 'keyUsage',
      keyCertSign: true,
      digitalSignature: true,
      nonRepudiation: true,
      keyEncipherment: true,
      dataEncipherment: true
    }, {
      name: 'extKeyUsage',
      serverAuth: true,
      clientAuth: true,
      codeSigning: true,
      emailProtection: true,
      timeStamping: true
    }, {
      name: 'nsCertType',
      client: true,
      server: true,
      email: true,
      objsign: true,
      sslCA: true,
      emailCA: true,
      objCA: true
    }]);
    cert.sign(keys.privateKey);
    log.info('Certificate created.');

    return {
      key: pki.privateKeyToPem(keys.privateKey),
      cert: pki.certificateToPem(cert)
    };
  }
}

new App().start();
<commit_msg>BUG: invalid default redirect location<commit_after>'use strict';

const log = require("../../net2/logger")('httpd');

const express = require('express');
const https = require('https');
const forge = require('node-forge');
const qs = require('querystring');
const path = require('path');

const port = 8880;
const httpsPort = 8883;
const enableHttps = true;

const Promise = require('bluebird');
const rclient = require('../../util/redis_manager.js').getRedisClient()

const intel = require('./intel.js')(rclient);

const VIEW_PATH = 'firewalla_view';
const STATIC_PATH = 'firewalla_static';

process.title = "FireBlue";

class App {
  constructor() {
    this.app = express();

    this.app.engine('mustache', require('mustache-express')());
    this.app.set('view engine', 'mustache');

    this.app.set('views', path.join(__dirname, VIEW_PATH));
    //this.app.disable('view cache'); //for debug only

    this.routes();
  }

  routes() {
    this.router = express.Router();
    this.router.all('/block', async (req, res) => {
      const hostname = req.hostname;
      const url = qs.unescape(req.query.url);
      const ip = req.ip;
      const method = req.method;
      const count = qs.unescape(req.query.count);

      log.info("Got a request in block views");

      res.render('block', {hostname, url, ip, method, count});
    })

    this.app.use('/' + VIEW_PATH, this.router);
    this.app.use('/' + STATIC_PATH, express.static(path.join(__dirname, STATIC_PATH)));

    this.app.use('*', async (req, res) => {
      log.info("Got a request in *");

      if (!req.originalUrl.includes(VIEW_PATH)) {

        // as a workaround, redirect all traffic as porn
        // TBD implementation a multi-port FireBlue to handle multiple category traffic
        let redirect = await rclient.hgetAsync('redirect','porn')
        redirect = redirect || "http://google.com"

        if(redirect) {
          res.status(303).location(redirect).send().end()
          return
        }

        let cat = await intel.check(req.hostname);

        log.info(`${req.hostname} 's category is ${cat}`);

        switch(cat) {
          case 'porn':
            await this.isPorn(req, res);
            break;
          case 'ad':
            await this.isAd(req, res);
            break;
          default:
            res.status(200).send().end();
        }
      }
    });
  }

  start() {
    this.app.listen(port, () => log.info(`Httpd listening on port ${port}!`));

    if (enableHttps) {
      const httpsOptions = this.genHttpsOptions();
      https.createServer(httpsOptions, this.app).listen(httpsPort, () => log.info(`Httpd listening on port ${httpsPort}!`));
    }
  }

  async isPorn(req, res) {
    let count = await rclient.hincrbyAsync('block:stats', 'porn', 1);
    let params = qs.stringify({hostname: req.hostname, url: req.originalUrl, count});
    let location = `/${VIEW_PATH}/block`;

    const redirect = await rclient.hgetAsync('redirect','porn')
    if(redirect) {
      res.status(303).location(redirect).send().end()
    } else {
      res.status(303).location(`${location}?${params}`).send().end();
    }

    log.info(`Total porn blocked: ${count}`);
  }

  async isAd(req, res) {
    res.status(200).send().end();
    let count = await rclient.hincrbyAsync('block:stats', 'ad', 1);
    log.info(`Total ad blocked: ${count}`);
  }

  genHttpsOptions() {
    // generate a keypair and create an X.509v3 certificate
    const pki = forge.pki;
    console.log('Generating 1024-bit key-pair...');
    const keys = pki.rsa.generateKeyPair(1024);
    console.log('Key-pair created.');

    console.log('Creating self-signed certificate...');
    const cert = pki.createCertificate();
    cert.publicKey = keys.publicKey;
    cert.serialNumber = '01';
    cert.validity.notBefore = new Date();
    cert.validity.notAfter = new Date();
    cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
    let attrs = [{
      name: 'commonName',
      value: 'blackhole.firewalla.com'
    }, {
      name: 'countryName',
      value: 'US'
    }, {
      shortName: 'ST',
      value: 'New York'
    }, {
      name: 'localityName',
      value: 'Brooklyn'
    }, {
      name: 'organizationName',
      value: 'BLACKHOLE'
    }, {
      shortName: 'OU',
      value: 'BLACKHOLE'
    }];
    cert.setSubject(attrs);
    cert.setIssuer(attrs);
    cert.setExtensions([{
      name: 'basicConstraints',
      cA: true/*,
    pathLenConstraint: 4*/
    }, {
      name: 'keyUsage',
      keyCertSign: true,
      digitalSignature: true,
      nonRepudiation: true,
      keyEncipherment: true,
      dataEncipherment: true
    }, {
      name: 'extKeyUsage',
      serverAuth: true,
      clientAuth: true,
      codeSigning: true,
      emailProtection: true,
      timeStamping: true
    }, {
      name: 'nsCertType',
      client: true,
      server: true,
      email: true,
      objsign: true,
      sslCA: true,
      emailCA: true,
      objCA: true
    }]);
    cert.sign(keys.privateKey);
    log.info('Certificate created.');

    return {
      key: pki.privateKeyToPem(keys.privateKey),
      cert: pki.certificateToPem(cert)
    };
  }
}

new App().start();
<|endoftext|>


<commit_before>/* Tealium Tools Hello World Example
*
* Github: https://github.com/tygavin/tealium-tools/
* NOTE: This file may run multiple times on the same page.  Preserves current state if Tealium Tool is closed/opened.
*
*/

var helloWorld = helloWorld || {

  // Initialize message object to starting state for Tealium Tool
  message : {
    ui_start     : true,
    data         : {
      web_page_url : document.URL
    }
  },

  // Incoming "data" object from Tealium tools .html (Handlebars) UI
  main : function( data ) {

    var message = helloWorld.message;

    switch ( data.command ){
      case "next":
        console.log( "Hello World: Next" );
        message.ui_finish = true;
        message.ui_start = false;
        message.data.text_area = data.text_area;
        break;
      case "restart":
        console.log( "Hello World: Restart" );
        message.ui_finish = false;
        message.ui_start = true;
        message.data.web_page_url = document.URL;
        break;
      default:
        console.log( "Hello World: Unknown command received from Tealium Tool" );
        break;
    }

    tealiumTools.send( message );

  }
}

window.helloWorld_main = helloWorld.main;

tealiumTools.send( helloWorld.message );

<commit_msg>refactor<commit_after>/* Tealium Tools Hello World Example
*
* Github: https://github.com/tygavin/tealium-tools/
* NOTE: This file may run multiple times on the same page.  Preserves current state if Tealium Tool is closed/opened.
*
*/

var helloWorld = helloWorld || {

  initialized : false,

  // Initialize message object with data object for passing information
  message : { data : {} },

  // Incoming tool object from Tealium tools .html (Handlebars) UI
  main : function( tool ) {

    var message = helloWorld.message;

    switch ( tool.command ){
      case "start":
        console.log( "Hello World: Start" );
        message.ui_finish = false;
        message.ui_start = true;
        message.data.web_page_url = document.URL;
        break;
      case "next":
        console.log( "Hello World: Next" );
        message.ui_finish = true;
        message.ui_start = false;
        message.data.text_area = tool.text_area;
        break;
      default:
        console.log( "Hello World: Unknown command received from Tealium Tool" );
        break;
    }

    tealiumTools.send( message );

  }
}

window.helloWorld_main = helloWorld.main;

if ( !helloWorld.initialized ) {
  helloWorld.initialized = true;
  console.log( "Hello World: Initialized" );
  helloWorld.main( { command : "start" } );
}else{
  console.log( "Hello World: /hello_world.js reloaded" );
}

<|endoftext|>


<commit_before>/* @flow */

import * as React from 'react';
import _ from 'lodash';
import { SchemaModel, Schema } from 'schema-typed';
import classNames from 'classnames';
import { shallowEqual } from 'rsuite-utils/lib/utils';

import { getUnhandledProps, prefix } from './utils';
import { defaultClassPrefix } from './utils/prefix';
import FormContext, { FormValueContext, FormErrorContext } from './FormContext';
import LegacyForm from './_legacy/Form';

type Props = {
  className?: string,
  layout?: 'horizontal' | 'vertical' | 'inline',
  fluid?: boolean,
  formValue?: Object,
  formDefaultValue?: Object,
  formError?: Object,
  checkDelay?: number,
  checkTrigger?: 'change' | 'blur' | 'none',
  onChange?: (formValue: Object, event: SyntheticEvent<*>) => void,
  onError?: (formError: Object) => void,
  onCheck?: (formError: Object) => void,
  model: typeof Schema,
  classPrefix: string,
  errorFromContext?: boolean
};

type State = {
  formError?: Object,
  formValue?: Object
};

function preventDefaultEvent(event) {
  event.preventDefault();
}

class Form extends React.Component<Props, State> {
  formContextValue = null;
  static defaultProps = {
    classPrefix: defaultClassPrefix('form'),
    model: SchemaModel({}),
    layout: 'vertical',
    formDefaultValue: {},
    checkDelay: 500,
    checkTrigger: 'change',
    errorFromContext: true
  };

  constructor(props: Props) {
    super(props);

    const {
      formDefaultValue,
      formError,
      formValue,
      model,
      checkTrigger,
      errorFromContext
    } = this.props;

    this.state = {
      formError: formError || {},
      formValue: formDefaultValue
    };
  }
  getFormValue() {
    const { formValue } = this.props;
    return _.isUndefined(formValue) ? this.state.formValue : formValue;
  }

  getFormError() {
    const { formError } = this.props;
    return _.isUndefined(formError) ? this.state.formError : formError;
  }

  /**
   * public APIs
   */
  check = (callback?: (formError: Object) => void) => {
    const formValue = this.getFormValue() || {};
    const { model, onCheck, onError } = this.props;
    const formError = {};
    let errorCount = 0;

    Object.keys(model.schema).forEach(key => {
      const checkResult = model.checkForField(key, formValue[key], formValue);
      if (checkResult.hasError === true) {
        errorCount += 1;
        formError[key] = checkResult.errorMessage;
      }
    });

    this.setState({ formError });

    onCheck && onCheck(formError);
    callback && callback(formError);

    if (errorCount > 0) {
      onError && onError(formError);
      return false;
    }

    return true;
  };

  /**
   * public APIs
   */
  checkForField = (fieldName: string, callback?: (checkResult: Object) => void) => {
    const formValue = this.getFormValue() || {};
    const { model, onCheck, onError } = this.props;
    const checkResult = model.checkForField(fieldName, formValue[fieldName], formValue);

    const formError = {
      ...this.getFormError(),
      [fieldName]: checkResult.errorMessage
    };

    onCheck && onCheck(formError);
    callback && callback(checkResult);

    if (checkResult.hasError) {
      onError && onError(formError);
      return false;
    }

    return true;
  };

  /**
   * public APIs
   */
  cleanErrors(callback: () => void) {
    this.setState({ formError: {} }, callback);
  }

  /**
   * public APIs
   */
  resetErrors(formError: Object = {}, callback: () => void) {
    this.setState({ formError }, callback);
  }

  handleFieldError = (name: string, errorMessage: string) => {
    const { onError, onCheck } = this.props;
    const formError = {
      ...this.state.formError,
      [name]: errorMessage
    };

    this.setState({ formError }, () => {
      onError && onError(formError);
      onCheck && onCheck(formError);
    });
  };

  handleFieldSuccess = (name: string) => {
    const { onCheck } = this.props;
    const formError = _.omit(this.state.formError, [name]);
    this.setState({ formError }, () => {
      onCheck && onCheck(formError);
    });
  };

  handleFieldChange = (name: string, value: any, event: SyntheticEvent<*>) => {
    const { onChange } = this.props;
    const formValue = this.getFormValue();
    const nextFormValue = {
      ...formValue,
      [name]: value
    };
    this.setState({
      formValue: nextFormValue
    });
    onChange && onChange(nextFormValue, event);
  };

  getFormContextValue() {
    const { formDefaultValue, errorFromContext, model, checkTrigger } = this.props;
    const nextFormContextValue = {
      model,
      checkTrigger,
      formDefaultValue,
      errorFromContext,
      onFieldChange: this.handleFieldChange,
      onFieldError: this.handleFieldError,
      onFieldSuccess: this.handleFieldSuccess
    };

    if (!shallowEqual(nextFormContextValue, this.formContextValue)) {
      this.formContextValue = nextFormContextValue;
    }

    return this.formContextValue;
  }

  checkErrorFromContext(children: React.Node) {
    const { errorFromContext } = this.props;
    if (errorFromContext) {
      return (
        <FormErrorContext.Provider value={this.getFormError()}>
          {children}
        </FormErrorContext.Provider>
      );
    }
    return children;
  }

  render() {
    const { formValue, layout, classPrefix, fluid, className, children, ...props } = this.props;
    const addPrefix = prefix(classPrefix);
    const classes = classNames(
      classPrefix,
      className,
      addPrefix(layout),
      addPrefix(fluid && layout === 'vertical' ? 'fluid' : 'fixed-width')
    );
    const unhandled = getUnhandledProps(Form, props);

    return (
      <form {...unhandled} onSubmit={preventDefaultEvent} className={classes}>
        <FormContext.Provider value={this.getFormContextValue()}>
          <FormValueContext.Provider value={formValue}>
            {this.checkErrorFromContext(children)}
          </FormValueContext.Provider>
        </FormContext.Provider>
      </form>
    );
  }
}

export default (React.createContext ? Form : LegacyForm);
<commit_msg>Fix checkForField api in Form (#291)<commit_after>/* @flow */

import * as React from 'react';
import _ from 'lodash';
import { SchemaModel, Schema } from 'schema-typed';
import classNames from 'classnames';
import { shallowEqual } from 'rsuite-utils/lib/utils';

import { getUnhandledProps, prefix } from './utils';
import { defaultClassPrefix } from './utils/prefix';
import FormContext, { FormValueContext, FormErrorContext } from './FormContext';
import LegacyForm from './_legacy/Form';

type Props = {
  className?: string,
  layout?: 'horizontal' | 'vertical' | 'inline',
  fluid?: boolean,
  formValue?: Object,
  formDefaultValue?: Object,
  formError?: Object,
  checkDelay?: number,
  checkTrigger?: 'change' | 'blur' | 'none',
  onChange?: (formValue: Object, event: SyntheticEvent<*>) => void,
  onError?: (formError: Object) => void,
  onCheck?: (formError: Object) => void,
  model: typeof Schema,
  classPrefix: string,
  errorFromContext?: boolean
};

type State = {
  formError?: Object,
  formValue?: Object
};

function preventDefaultEvent(event) {
  event.preventDefault();
}

class Form extends React.Component<Props, State> {
  formContextValue = null;
  static defaultProps = {
    classPrefix: defaultClassPrefix('form'),
    model: SchemaModel({}),
    layout: 'vertical',
    formDefaultValue: {},
    checkDelay: 500,
    checkTrigger: 'change',
    errorFromContext: true
  };

  constructor(props: Props) {
    super(props);

    const {
      formDefaultValue,
      formError,
      formValue,
      model,
      checkTrigger,
      errorFromContext
    } = this.props;

    this.state = {
      formError: formError || {},
      formValue: formDefaultValue
    };
  }
  getFormValue() {
    const { formValue } = this.props;
    return _.isUndefined(formValue) ? this.state.formValue : formValue;
  }

  getFormError() {
    const { formError } = this.props;
    return _.isUndefined(formError) ? this.state.formError : formError;
  }

  /**
   * public APIs
   */
  check = (callback?: (formError: Object) => void) => {
    const formValue = this.getFormValue() || {};
    const { model, onCheck, onError } = this.props;
    const formError = {};
    let errorCount = 0;

    Object.keys(model.schema).forEach(key => {
      const checkResult = model.checkForField(key, formValue[key], formValue);
      if (checkResult.hasError === true) {
        errorCount += 1;
        formError[key] = checkResult.errorMessage;
      }
    });

    this.setState({ formError });

    onCheck && onCheck(formError);
    callback && callback(formError);

    if (errorCount > 0) {
      onError && onError(formError);
      return false;
    }

    return true;
  };

  /**
   * public APIs
   */
  checkForField = (fieldName: string, callback?: (checkResult: Object) => void) => {
    const formValue = this.getFormValue() || {};
    const { model, onCheck, onError } = this.props;
    const checkResult = model.checkForField(fieldName, formValue[fieldName], formValue);

    const formError = {
      ...this.getFormError(),
      [fieldName]: checkResult.errorMessage
    };

    this.setState({ formError });

    onCheck && onCheck(formError);
    callback && callback(checkResult);

    if (checkResult.hasError) {
      onError && onError(formError);
      return false;
    }

    return true;
  };

  /**
   * public APIs
   */
  cleanErrors(callback: () => void) {
    this.setState({ formError: {} }, callback);
  }

  /**
   * public APIs
   */
  resetErrors(formError: Object = {}, callback: () => void) {
    this.setState({ formError }, callback);
  }

  handleFieldError = (name: string, errorMessage: string) => {
    const { onError, onCheck } = this.props;
    const formError = {
      ...this.state.formError,
      [name]: errorMessage
    };

    this.setState({ formError }, () => {
      onError && onError(formError);
      onCheck && onCheck(formError);
    });
  };

  handleFieldSuccess = (name: string) => {
    const { onCheck } = this.props;
    const formError = _.omit(this.state.formError, [name]);
    this.setState({ formError }, () => {
      onCheck && onCheck(formError);
    });
  };

  handleFieldChange = (name: string, value: any, event: SyntheticEvent<*>) => {
    const { onChange } = this.props;
    const formValue = this.getFormValue();
    const nextFormValue = {
      ...formValue,
      [name]: value
    };
    this.setState({
      formValue: nextFormValue
    });
    onChange && onChange(nextFormValue, event);
  };

  getFormContextValue() {
    const { formDefaultValue, errorFromContext, model, checkTrigger } = this.props;
    const nextFormContextValue = {
      model,
      checkTrigger,
      formDefaultValue,
      errorFromContext,
      onFieldChange: this.handleFieldChange,
      onFieldError: this.handleFieldError,
      onFieldSuccess: this.handleFieldSuccess
    };

    if (!shallowEqual(nextFormContextValue, this.formContextValue)) {
      this.formContextValue = nextFormContextValue;
    }

    return this.formContextValue;
  }

  checkErrorFromContext(children: React.Node) {
    const { errorFromContext } = this.props;
    if (errorFromContext) {
      return (
        <FormErrorContext.Provider value={this.getFormError()}>
          {children}
        </FormErrorContext.Provider>
      );
    }
    return children;
  }

  render() {
    const { formValue, layout, classPrefix, fluid, className, children, ...props } = this.props;
    const addPrefix = prefix(classPrefix);
    const classes = classNames(
      classPrefix,
      className,
      addPrefix(layout),
      addPrefix(fluid && layout === 'vertical' ? 'fluid' : 'fixed-width')
    );
    const unhandled = getUnhandledProps(Form, props);

    return (
      <form {...unhandled} onSubmit={preventDefaultEvent} className={classes}>
        <FormContext.Provider value={this.getFormContextValue()}>
          <FormValueContext.Provider value={formValue}>
            {this.checkErrorFromContext(children)}
          </FormValueContext.Provider>
        </FormContext.Provider>
      </form>
    );
  }
}

export default (React.createContext ? Form : LegacyForm);
<|endoftext|>


<commit_before>/**
 * @fileoverview Defines the chord bank class.
 */
define([
	'lodash',
	'microevent',
	'app/model/chord'
], function(
	_,
	MicroEvent,
	Chord
) {
	"use strict";

	/**
	 * Creates an instance of a chord bank.
	 *
	 * A chord bank is a collection of chords.  The purpose of the chord bank is
	 * to maintain a history of chords that have been played and relay events
	 * that bubble up from the currently active chord. It should know how to
	 * "bank" the current chord and add it to the history and know how to create
	 * a new chord.
	 *
	 * The current chord is the one that is being manipulated by MIDI NOTE
	 * ON/OFF events, while the banked chords are essentially read-only. The
	 * banked chords are primiarily of interest to the notation renderer which
	 * is responsible for rendering all or part of the chord bank and analyzing
	 * its contents.
	 *
	 * Change events from the current chord are relayed to any listeners on the
	 * chord bank itself. When a chord is banked, it also fires a bank event.
	 *
	 * @constructor
	 * @fires change
	 * @fires bank
	 */
	var ChordBank = function() {
		this.init();
	};

	_.extend(ChordBank.prototype, {
		/**
		 * Initializes the object
		 *
		 * @return
		 */
		init: function() {
			var chord = new Chord();

			_.bindAll(this, ['onChangeCurrent']);

			this._addListeners(chord);

			this._limit = 10; // limit number of chords in the bank
			this._items = [chord];
		},
		/**
		 * Banks the current chord and generates a new chord that becomes the
		 * active chord.
		 *
		 * @fires bank
		 * @return
		 */
		bank: function() {
			var chord = new Chord();
			var current = this.current();
			chord.copyTranspose(current);
			chord.copySustain(current);

			// re-wire listeners because we only care about changes to the current chord
			this._removeListeners(current);
			this._addListeners(chord);
			this._add(chord);

			this.trigger('bank');
		},
		/**
		 * Returns a list of chords in the chord bank.
		 *
		 * @param {object} config
		 * @param {number} config.limit Will return items up to the limit.
		 * @param {boolean} config.reverse Will reverse the order of items after
		 * retrieving them up to the limit.
		 * @return
		 */
		items: function(config) {
			config = config || {};
			var items = this._items;
			if(config.limit) {
				items = this._items.slice(0, config.limit);
			}
			if(config.reverse) {
				items.reverse();
			}
			return items;
		},
		/**
		 * Returns the total number of items in the chord bank..
		 *
		 * @return {number}
		 */
		size: function() {
			return this._items.length;
		},
		/**
		 * Clears the chord bank.
		 *
		 * @return
		 */
		clear: function() {
			this._resetItems();
			this.trigger("clear");
		},
		/**
		 * Returns the currently active chord.
		 *
		 * @return {Chord}
		 */
		current: function() {
			return this._items[0];
		},
		/**
		 * Handles change events from the currently active chord
		 * and relays them to the chord bank.
		 *
		 * Listeners who want to be notified when the active chord changes
		 * should listen to the chord bank, *not* the active chord itself.
		 *
		 * @return
		 */
		onChangeCurrent: function() {
			var args = Array.prototype.slice.call(arguments, 0);
			args.unshift("change");
			this.trigger.apply(this, args);
		},

		// returns a flat list of all note numbers in the bank
		getAllNotes: function() {
			var note_map = _.reduce(this._items, function(result, chord) {
				var i, len, note_num, note_nums = chord.getNoteNumbers();
				for(i = 0, len = note_nums.length; i < len; i++) {
					note_num = chord.untranspose(note_nums[i]);
					result[note_num] = true;
				}
				return result;
			}, {});

			var note_nums = _.keys(note_map);

			return note_nums;
		},

		// returns true if any chords are sustained
		anySustained: function() {
			return _.any(this._items, function(chord) {
				return chord.isSustained();
			});
		},

		//--------------------------------------------------

		/**
		 * Adds a chord to the bank.
		 *
		 * @private
		 */
		_add: function(chord) {
			if(this._limit !== null && this._items.length > this._limit) {
				this._items.pop();
			}
			this._items.unshift(chord);
		},
		/**
		 * Adds listeners to the chord.
		 * Used to setup the active chord.
		 *
		 * @private
		 * @param chord
		 * @return
		 */
		_addListeners: function(chord) {
			chord.bind('change', this.onChangeCurrent);
		},
		/**
		 * Removes listeners from a chord.
		 * Used when banking the active chord.
		 *
		 * @private
		 * @param chord
		 * @return
		 */
		_removeListeners: function(chord) {
			chord.unbind('change', this.onChangeCurrent);
		},
		/**
		 * Resets the items.
		 * Used when clearing the bank.
		 *
		 * @private
		 * @return
		 */
		_resetItems: function() {
			var chord = new Chord();
			var current = this.current();
			if(current) {
				this._removeListeners(current);
				chord.copyTranspose(current);
				if(current.isSustained()) {
					chord.sustainNotes();
				}
			}
			this._addListeners(chord);
			this._items = [chord];
		}
	});

	/**
	 * The chord bank is observable.
	 */
	MicroEvent.mixin(ChordBank);

	return ChordBank;
});
<commit_msg>updated jsdoc blocks in chord bank<commit_after>/**
 * @fileoverview Defines the chord bank class.
 */
define([
	'lodash',
	'microevent',
	'app/model/chord'
], function(
	_,
	MicroEvent,
	Chord
) {
	"use strict";

	/**
	 * Creates an instance of a chord bank.
	 *
	 * A chord bank is a collection of chords.  The purpose of the chord bank is
	 * to maintain a history of chords that have been played and relay events
	 * that bubble up from the currently active chord. It should know how to
	 * "bank" the current chord and add it to the history and know how to create
	 * a new chord.
	 *
	 * The current chord is the one that is being manipulated by MIDI NOTE
	 * ON/OFF events, while the banked chords are essentially read-only. The
	 * banked chords are primiarily of interest to the notation renderer which
	 * is responsible for rendering all or part of the chord bank and analyzing
	 * its contents.
	 *
	 * Change events from the current chord are relayed to any listeners on the
	 * chord bank itself. When a chord is banked, it also fires a bank event.
	 *
	 * @constructor
	 * @fires change
	 * @fires bank
	 */
	var ChordBank = function() {
		this.init();
	};

	_.extend(ChordBank.prototype, {
		/**
		 * Initializes the object
		 *
		 * @return
		 */
		init: function() {
			var chord = new Chord();

			_.bindAll(this, ['onChangeCurrent']);

			this._addListeners(chord);

			this._limit = 10; // limit number of chords in the bank
			this._items = [chord];
		},
		/**
		 * Banks the current chord and generates a new chord that becomes the
		 * active chord.
		 *
		 * @fires bank
		 * @return
		 */
		bank: function() {
			var chord = new Chord();
			var current = this.current();
			chord.copyTranspose(current);
			chord.copySustain(current);

			// re-wire listeners because we only care about changes to the current chord
			this._removeListeners(current);
			this._addListeners(chord);
			this._add(chord);

			this.trigger('bank');
		},
		/**
		 * Returns a list of chords in the chord bank.
		 *
		 * @param {object} config
		 * @param {number} config.limit Will return items up to the limit.
		 * @param {boolean} config.reverse Will reverse the order of items after
		 * retrieving them up to the limit.
		 * @return
		 */
		items: function(config) {
			config = config || {};
			var items = this._items;
			if(config.limit) {
				items = this._items.slice(0, config.limit);
			}
			if(config.reverse) {
				items.reverse();
			}
			return items;
		},
		/**
		 * Returns the total number of items in the chord bank..
		 *
		 * @return {number}
		 */
		size: function() {
			return this._items.length;
		},
		/**
		 * Clears the chord bank.
		 *
		 * @return
		 */
		clear: function() {
			this._resetItems();
			this.trigger("clear");
		},
		/**
		 * Returns the currently active chord.
		 *
		 * @return {Chord}
		 */
		current: function() {
			return this._items[0];
		},
		/**
		 * Handles change events from the currently active chord
		 * and relays them to the chord bank.
		 *
		 * Listeners who want to be notified when the active chord changes
		 * should listen to the chord bank, *not* the active chord itself.
		 *
		 * @return
		 */
		onChangeCurrent: function() {
			var args = Array.prototype.slice.call(arguments, 0);
			args.unshift("change");
			this.trigger.apply(this, args);
		},

		/**
		 * Returns *all* distinct notes in the chord bank.
		 *
		 * @return {Array} An array of note numbers.
		 */
		getAllNotes: function() {
			var note_map = _.reduce(this._items, function(result, chord) {
				var i, len, note_num, note_nums = chord.getNoteNumbers();
				for(i = 0, len = note_nums.length; i < len; i++) {
					note_num = chord.untranspose(note_nums[i]);
					result[note_num] = true;
				}
				return result;
			}, {});

			var note_nums = _.keys(note_map);

			return note_nums;
		},

		/**
		 * Returns true if any chords in the bank are sustained.
		 *
		 * @return {boolean} True if any chords are sustained, false otherwise.
		 */
		anySustained: function() {
			return _.any(this._items, function(chord) {
				return chord.isSustained();
			});
		},

		//--------------------------------------------------

		/**
		 * Adds a chord to the bank.
		 *
		 * @private
		 */
		_add: function(chord) {
			if(this._limit !== null && this._items.length > this._limit) {
				this._items.pop();
			}
			this._items.unshift(chord);
		},
		/**
		 * Adds listeners to the chord.
		 * Used to setup the active chord.
		 *
		 * @private
		 * @param chord
		 * @return
		 */
		_addListeners: function(chord) {
			chord.bind('change', this.onChangeCurrent);
		},
		/**
		 * Removes listeners from a chord.
		 * Used when banking the active chord.
		 *
		 * @private
		 * @param chord
		 * @return
		 */
		_removeListeners: function(chord) {
			chord.unbind('change', this.onChangeCurrent);
		},
		/**
		 * Resets the items.
		 * Used when clearing the bank.
		 *
		 * @private
		 * @return
		 */
		_resetItems: function() {
			var chord = new Chord();
			var current = this.current();
			if(current) {
				this._removeListeners(current);
				chord.copyTranspose(current);
				if(current.isSustained()) {
					chord.sustainNotes();
				}
			}
			this._addListeners(chord);
			this._items = [chord];
		}
	});

	/**
	 * The chord bank is observable.
	 */
	MicroEvent.mixin(ChordBank);

	return ChordBank;
});
<|endoftext|>


<commit_before>function makeLineChart(dataset, xName, yObjs, axisLables) {
    var chartObj = {};
    var color = d3.scaleOrdinal(d3.schemeCategory20);
    //var color = d3.scaleOrdinal.range(["#A07A19", "#AC30C0", "#EB9A72", "#BA86F5", "#EA22A8"]);
    chartObj.xAxisLable = axisLables.xAxis;
    chartObj.yAxisLable = axisLables.yAxis;
    /*
     yObjsects format:
     {y1:{column:'',name:'name',color:'color'},y2}
     */

    chartObj.data = dataset;
    chartObj.margin = {top: 15, right: 60, bottom: 30, left: 50};
    chartObj.width = 650 - chartObj.margin.left - chartObj.margin.right;
    chartObj.height = 480 - chartObj.margin.top - chartObj.margin.bottom;

// So we can pass the x and y as strings when creating the function
    chartObj.xFunct = function(d){return d[xName]};

// For each yObjs argument, create a yFunction
    function getYFn(column) {
        return function (d) {
            return d[column];
        };
    }

// Object instead of array
    chartObj.yFuncts = [];
    for (var y  in yObjs) {
        yObjs[y].name = y;
        yObjs[y].yFunct = getYFn(yObjs[y].column); //Need this  list for the ymax function
        chartObj.yFuncts.push(yObjs[y].yFunct);
    }

//Formatter functions for the axes
    chartObj.formatAsNumber = d3.format(".0f");
    chartObj.formatAsDecimal = d3.format(".2f");
    chartObj.formatAsCurrency = d3.format("$.2f");
    chartObj.formatAsYYYYMM = d3.timeFormat("%Y-%m");

    chartObj.formatAsFloat = function (d) {
        if (d % 1 !== 0) {
            return d3.format(".2f")(d);
        } else {
            return d3.format(".0f")(d);
        }

    };

    chartObj.xFormatter = chartObj.formatAsYYYYMM;
    chartObj.yFormatter = chartObj.formatAsFloat;

    chartObj.bisectYear = d3.bisector(chartObj.xFunct).left; //< Can be overridden in definition

//Create scale functions
    chartObj.xScale = d3.scaleLinear().range([0, chartObj.width]).domain(d3.extent(chartObj.data, chartObj.xFunct));
    //chartObj.xScale = d3.scaleTime().range([0, chartObj.width]).domain(d3.extent(chartObj.data, chartObj.xFunct));

// Get the max of every yFunct
    chartObj.max = function (fn) {
        return d3.max(chartObj.data, fn);
    };
    chartObj.yScale = d3.scaleLinear().range([chartObj.height, 0]).domain([0, d3.max(chartObj.yFuncts.map(chartObj.max))]);

    chartObj.formatAsYear = d3.format("");

//Create axis

    chartObj.xAxis  = d3.axisBottom(chartObj.xScale).tickFormat(chartObj.xFormatter);
    chartObj.yAxis = d3.axisLeft(chartObj.yScale).tickFormat(chartObj.yFormatter);

// Build line building functions
    function getYScaleFn(yObj) {
        return function (d) {
            return chartObj.yScale(yObjs[yObj].yFunct(d));
        };
    }

    for (var yObj in yObjs) {
        yObjs[yObj].line = d3.line().x(function (d) {
            return chartObj.xScale(chartObj.xFunct(d));
        }).y(getYScaleFn(yObj));
    }

    chartObj.svg;

// Change chart size according to window size
    chartObj.update_svg_size = function () {
        chartObj.width = parseInt(chartObj.chartDiv.style("width"), 10) - (chartObj.margin.left + chartObj.margin.right);

        chartObj.height = parseInt(chartObj.chartDiv.style("height"), 10) - (chartObj.margin.top + chartObj.margin.bottom);

        /* Update the range of the scale with new width/height */
        chartObj.xScale.range([0, chartObj.width]);
        chartObj.yScale.range([chartObj.height, 0]);

        if (!chartObj.svg) {return false;}

        /* Else Update the axis with the new scale */
        chartObj.svg.select('.x.axis').attr("transform", "translate(0," + chartObj.height + ")").call(chartObj.xAxis);
        chartObj.svg.select('.x.axis .label').attr("x", chartObj.width / 2);

        chartObj.svg.select('.y.axis').call(chartObj.yAxis);
        chartObj.svg.select('.y.axis .label').attr("x", -chartObj.height / 2);

        /* Force D3 to recalculate and update the line */
        for (var y  in yObjs) {
            yObjs[y].path.attr("d", yObjs[y].line);
        }

        d3.selectAll(".focus.line").attr("y2", chartObj.height);

        chartObj.chartDiv.select('svg').attr("width", chartObj.width + (chartObj.margin.left + chartObj.margin.right)).attr("height", chartObj.height + (chartObj.margin.top + chartObj.margin.bottom));

        chartObj.svg.select(".overlay").attr("width", chartObj.width).attr("height", chartObj.height);
        return chartObj;
    };

    chartObj.bind = function (selector) {
        chartObj.mainDiv = d3.select(selector);
        // Add all the divs to make it centered and responsive
        chartObj.mainDiv.append("div").attr("class", "inner-wrapper").append("div").attr("class", "outer-box").append("div").attr("class", "inner-box");
        chartSelector = selector + " .inner-box";
        chartObj.chartDiv = d3.select(chartSelector);
        d3.select(window).on('resize.' + chartSelector, chartObj.update_svg_size);
        chartObj.update_svg_size();
        return chartObj;
    };

// Render the chart
    chartObj.render = function () {
        //Create SVG element
        chartObj.svg = chartObj.chartDiv.append("svg").attr("class", "chart-area").attr("width", chartObj.width + (chartObj.margin.left + chartObj.margin.right)).attr("height", chartObj.height + (chartObj.margin.top + chartObj.margin.bottom)).append("g").attr("transform", "translate(" + chartObj.margin.left + "," + chartObj.margin.top + ")");

        // Draw Lines
        for (var y  in yObjs) {
            yObjs[y].path = chartObj.svg.append("path").datum(chartObj.data).attr("class", "line").attr("d", yObjs[y].line).style("stroke", color(y)).attr("data-series", y).on("mouseover", function () {
                focus.style("display", null);
            }).on("mouseout", function () {
                focus.transition().delay(700).style("display", "none");
            }).on("mousemove", mousemove);
        }

        // .selectAll("text")
        //     .attr("y", 0)
        //     .attr("x", 9)
        //     .attr("dy", ".35em")
        //     .attr("transform", "rotate(90)")
        //     .style("text-anchor", "start")

        // Draw Axis
        chartObj.svg.append("g").attr("class", "x axis").attr("transform", "translate(0," + chartObj.height + ")").call(chartObj.xAxis).append("text").attr("class", "label").attr("x", chartObj.width / 2).attr("y", 30).style("text-anchor", "middle").text(chartObj.xAxisLable);

        chartObj.svg.append("g").attr("class", "y axis").call(chartObj.yAxis).append("text").attr("class", "label").attr("transform", "rotate(-90)").attr("y", -42).attr("x", -chartObj.height / 2).attr("dy", ".71em").style("text-anchor", "middle").text(chartObj.yAxisLable);

        //Draw tooltips
        var focus = chartObj.svg.append("g").attr("class", "focus").style("display", "none");

        for (var y  in yObjs) {
            yObjs[y].tooltip = focus.append("g");
            yObjs[y].tooltip.append("circle").attr("r", 5);
            yObjs[y].tooltip.append("rect").attr("x", 8).attr("y","-5").attr("width",22).attr("height",'0.75em');
            yObjs[y].tooltip.append("text").attr("x", 9).attr("dy", ".35em");
        }

        // Year label
        focus.append("text").attr("class", "focus year").attr("x", 9).attr("y", 7);
        // Focus line
        focus.append("line").attr("class", "focus line").attr("y1", 0).attr("y2", chartObj.height);

        //Draw legend
        var legend = chartObj.mainDiv.append('div').attr("class", "legend");
        for (var y  in yObjs) {
            series = legend.append('div');
            series.append('div').attr("class", "series-marker").style("background-color", color(y));
            series.append('p').text(y);
            yObjs[y].legend = series;
        }

        // Overlay to capture hover
        chartObj.svg.append("rect").attr("class", "overlay").attr("width", chartObj.width).attr("height", chartObj.height).on("mouseover", function () {
            focus.style("display", null);
        }).on("mouseout", function () {
            focus.style("display", "none");
        }).on("mousemove", mousemove);

        return chartObj;
        function mousemove() {

            var x0 = chartObj.xScale.invert(d3.mouse(this)[0]), i = chartObj.bisectYear(dataset, x0, 1), d0 = chartObj.data[i - 1], d1 = chartObj.data[i];

            try {
                var d = x0 - chartObj.xFunct(d0) > chartObj.xFunct(d1) - x0 ? d1 : d0;

            } catch (e) {  console.log(e); return;}
            minY = chartObj.height;
            for (var y  in yObjs) {
                yObjs[y].tooltip.attr("transform", "translate(" + chartObj.xScale(chartObj.xFunct(d)) + "," + chartObj.yScale(yObjs[y].yFunct(d)) + ")");
                yObjs[y].tooltip.select("text").text(chartObj.yFormatter(yObjs[y].yFunct(d)));
                minY = Math.min(minY, chartObj.yScale(yObjs[y].yFunct(d)));
            }

            focus.select(".focus.line").attr("transform", "translate(" + chartObj.xScale(chartObj.xFunct(d)) + ")").attr("y1", minY);
            focus.select(".focus.year").text("Yearmonth: " + chartObj.xFormatter(chartObj.xFunct(d)));
        }

    };
    return chartObj;
}
<commit_msg>d3 color<commit_after>
function makeLineChart(dataset, xName, yObjs, axisLables) {
    var chartObj = {};

    var color = d3.scaleOrdinal().range(["#1F77B4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c"
        ,"#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"
        ,"#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f"
        ,"#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"
        ,"#393b79","#5254a3","#6b6ecf","#9c9ede","#637939"
        ,"#b5cf6b","#cedb9c","#8c6d31","#bd9e39","#e7ba52"
        ,"#ad494a","#d6616b","#7b4173","#a55194","#ce6dbd"]);
    chartObj.xAxisLable = axisLables.xAxis;
    chartObj.yAxisLable = axisLables.yAxis;
    /*
     yObjsects format:
     {y1:{column:'',name:'name',color:'color'},y2}
     */

    chartObj.data = dataset;
    chartObj.margin = {top: 15, right: 60, bottom: 30, left: 50};
    chartObj.width = 650 - chartObj.margin.left - chartObj.margin.right;
    chartObj.height = 480 - chartObj.margin.top - chartObj.margin.bottom;

// So we can pass the x and y as strings when creating the function
    chartObj.xFunct = function(d){return d[xName]};

// For each yObjs argument, create a yFunction
    function getYFn(column) {
        return function (d) {
            return d[column];
        };
    }

// Object instead of array
    chartObj.yFuncts = [];
    for (var y  in yObjs) {
        yObjs[y].name = y;
        yObjs[y].yFunct = getYFn(yObjs[y].column); //Need this  list for the ymax function
        chartObj.yFuncts.push(yObjs[y].yFunct);
    }

//Formatter functions for the axes
    chartObj.formatAsNumber = d3.format(".0f");
    chartObj.formatAsDecimal = d3.format(".2f");
    chartObj.formatAsCurrency = d3.format("$.2f");
    chartObj.formatAsYYYYMM = d3.timeFormat("%Y-%m");

    chartObj.formatAsFloat = function (d) {
        if (d % 1 !== 0) {
            return d3.format(".2f")(d);
        } else {
            return d3.format(".0f")(d);
        }

    };

    chartObj.xFormatter = chartObj.formatAsYYYYMM;
    chartObj.yFormatter = chartObj.formatAsFloat;

    chartObj.bisectYear = d3.bisector(chartObj.xFunct).left; //< Can be overridden in definition

//Create scale functions
    chartObj.xScale = d3.scaleLinear().range([0, chartObj.width]).domain(d3.extent(chartObj.data, chartObj.xFunct));
    //chartObj.xScale = d3.scaleTime().range([0, chartObj.width]).domain(d3.extent(chartObj.data, chartObj.xFunct));

// Get the max of every yFunct
    chartObj.max = function (fn) {
        return d3.max(chartObj.data, fn);
    };
    chartObj.yScale = d3.scaleLinear().range([chartObj.height, 0]).domain([0, d3.max(chartObj.yFuncts.map(chartObj.max))]);

    chartObj.formatAsYear = d3.format("");

//Create axis

    chartObj.xAxis  = d3.axisBottom(chartObj.xScale).tickFormat(chartObj.xFormatter);
    chartObj.yAxis = d3.axisLeft(chartObj.yScale).tickFormat(chartObj.yFormatter);

// Build line building functions
    function getYScaleFn(yObj) {
        return function (d) {
            return chartObj.yScale(yObjs[yObj].yFunct(d));
        };
    }

    for (var yObj in yObjs) {
        yObjs[yObj].line = d3.line().x(function (d) {
            return chartObj.xScale(chartObj.xFunct(d));
        }).y(getYScaleFn(yObj));
    }

    chartObj.svg;

// Change chart size according to window size
    chartObj.update_svg_size = function () {
        chartObj.width = parseInt(chartObj.chartDiv.style("width"), 10) - (chartObj.margin.left + chartObj.margin.right);

        chartObj.height = parseInt(chartObj.chartDiv.style("height"), 10) - (chartObj.margin.top + chartObj.margin.bottom);

        /* Update the range of the scale with new width/height */
        chartObj.xScale.range([0, chartObj.width]);
        chartObj.yScale.range([chartObj.height, 0]);

        if (!chartObj.svg) {return false;}

        /* Else Update the axis with the new scale */
        chartObj.svg.select('.x.axis').attr("transform", "translate(0," + chartObj.height + ")").call(chartObj.xAxis);
        chartObj.svg.select('.x.axis .label').attr("x", chartObj.width / 2);

        chartObj.svg.select('.y.axis').call(chartObj.yAxis);
        chartObj.svg.select('.y.axis .label').attr("x", -chartObj.height / 2);

        /* Force D3 to recalculate and update the line */
        for (var y  in yObjs) {
            yObjs[y].path.attr("d", yObjs[y].line);
        }

        d3.selectAll(".focus.line").attr("y2", chartObj.height);

        chartObj.chartDiv.select('svg').attr("width", chartObj.width + (chartObj.margin.left + chartObj.margin.right)).attr("height", chartObj.height + (chartObj.margin.top + chartObj.margin.bottom));

        chartObj.svg.select(".overlay").attr("width", chartObj.width).attr("height", chartObj.height);
        return chartObj;
    };

    chartObj.bind = function (selector) {
        chartObj.mainDiv = d3.select(selector);
        // Add all the divs to make it centered and responsive
        chartObj.mainDiv.append("div").attr("class", "inner-wrapper").append("div").attr("class", "outer-box").append("div").attr("class", "inner-box");
        chartSelector = selector + " .inner-box";
        chartObj.chartDiv = d3.select(chartSelector);
        d3.select(window).on('resize.' + chartSelector, chartObj.update_svg_size);
        chartObj.update_svg_size();
        return chartObj;
    };

// Render the chart
    chartObj.render = function () {
        //Create SVG element
        chartObj.svg = chartObj.chartDiv.append("svg").attr("class", "chart-area").attr("width", chartObj.width + (chartObj.margin.left + chartObj.margin.right)).attr("height", chartObj.height + (chartObj.margin.top + chartObj.margin.bottom)).append("g").attr("transform", "translate(" + chartObj.margin.left + "," + chartObj.margin.top + ")");

        // Draw Lines
        for (var y  in yObjs) {
            yObjs[y].path = chartObj.svg.append("path").datum(chartObj.data).attr("class", "line").attr("d", yObjs[y].line).style("stroke", color(y)).attr("data-series", y).on("mouseover", function () {
                focus.style("display", null);
            }).on("mouseout", function () {
                focus.transition().delay(700).style("display", "none");
            }).on("mousemove", mousemove);
        }

        // .selectAll("text")
        //     .attr("y", 0)
        //     .attr("x", 9)
        //     .attr("dy", ".35em")
        //     .attr("transform", "rotate(90)")
        //     .style("text-anchor", "start")

        // Draw Axis
        chartObj.svg.append("g").attr("class", "x axis").attr("transform", "translate(0," + chartObj.height + ")").call(chartObj.xAxis).append("text").attr("class", "label").attr("x", chartObj.width / 2).attr("y", 30).style("text-anchor", "middle").text(chartObj.xAxisLable);

        chartObj.svg.append("g").attr("class", "y axis").call(chartObj.yAxis).append("text").attr("class", "label").attr("transform", "rotate(-90)").attr("y", -42).attr("x", -chartObj.height / 2).attr("dy", ".71em").style("text-anchor", "middle").text(chartObj.yAxisLable);

        //Draw tooltips
        var focus = chartObj.svg.append("g").attr("class", "focus").style("display", "none");

        for (var y  in yObjs) {
            yObjs[y].tooltip = focus.append("g");
            yObjs[y].tooltip.append("circle").attr("r", 5);
            yObjs[y].tooltip.append("rect").attr("x", 8).attr("y","-5").attr("width",22).attr("height",'0.75em');
            yObjs[y].tooltip.append("text").attr("x", 9).attr("dy", ".35em");
        }

        // Year label
        focus.append("text").attr("class", "focus year").attr("x", 9).attr("y", 7);
        // Focus line
        focus.append("line").attr("class", "focus line").attr("y1", 0).attr("y2", chartObj.height);

        //Draw legend
        var legend = chartObj.mainDiv.append('div').attr("class", "legend");
        for (var y  in yObjs) {
            series = legend.append('div');
            series.append('div').attr("class", "series-marker").style("background-color", color(y));
            series.append('p').text(y);
            yObjs[y].legend = series;
        }

        // Overlay to capture hover
        chartObj.svg.append("rect").attr("class", "overlay").attr("width", chartObj.width).attr("height", chartObj.height).on("mouseover", function () {
            focus.style("display", null);
        }).on("mouseout", function () {
            focus.style("display", "none");
        }).on("mousemove", mousemove);

        return chartObj;
        function mousemove() {

            var x0 = chartObj.xScale.invert(d3.mouse(this)[0]), i = chartObj.bisectYear(dataset, x0, 1), d0 = chartObj.data[i - 1], d1 = chartObj.data[i];

            try {
                var d = x0 - chartObj.xFunct(d0) > chartObj.xFunct(d1) - x0 ? d1 : d0;

            } catch (e) {  console.log(e); return;}
            minY = chartObj.height;
            for (var y  in yObjs) {
                yObjs[y].tooltip.attr("transform", "translate(" + chartObj.xScale(chartObj.xFunct(d)) + "," + chartObj.yScale(yObjs[y].yFunct(d)) + ")");
                yObjs[y].tooltip.select("text").text(chartObj.yFormatter(yObjs[y].yFunct(d)));
                minY = Math.min(minY, chartObj.yScale(yObjs[y].yFunct(d)));
            }

            focus.select(".focus.line").attr("transform", "translate(" + chartObj.xScale(chartObj.xFunct(d)) + ")").attr("y1", minY);
            focus.select(".focus.year").text("Yearmonth: " + chartObj.xFormatter(chartObj.xFunct(d)));
        }

    };
    return chartObj;
}
<|endoftext|>


<commit_before>'use strict';

angular.module('hyphe.preparecrawlsController', [])

  .controller('PrepareCrawls', ['$scope', 'api', 'store', 'utils', '$location', 'QueriesBatcher', '$modal', 'corpus'
  ,function($scope, api, store, utils, $location, QueriesBatcher, $modal, corpus) {
    $scope.currentPage = 'prepareCrawls'
    $scope.Page.setTitle('Prepare Crawls')
    $scope.corpusName = corpus.getName()
    $scope.corpusId = corpus.getId()

  }])
<commit_msg>prepare crawls wip<commit_after>'use strict';

angular.module('hyphe.preparecrawlsController', [])

  .controller('PrepareCrawls', ['$scope', 'api', 'store', 'utils', '$location', 'QueriesBatcher', '$modal', 'corpus'
  ,function($scope, api, store, utils, $location, QueriesBatcher, $modal, corpus) {
    $scope.currentPage = 'prepareCrawls'
    $scope.Page.setTitle('Prepare Crawls')
    $scope.corpusName = corpus.getName()
    $scope.corpusId = corpus.getId()

    $scope.crawlDepth = 1

    $scope.httpStatusLoading = 0
    $scope.httpStatusWarning = 0
    $scope.httpStatusSuccess = 0

    $scope.paginationPage = 1
    $scope.paginationLength = 50    // How many items per page
    $scope.paginationNumPages = 10   // How many pages to display in the pagination

    $scope.depthRange = [0,1]

    $scope.queriesBatches = []

    $scope.list = []
    var list_byId = {}  // List index

    $scope.getWebentities = getWebentities

    $scope.removeRow = function(objId){
      console.log('remove row',objId)
      var obj = list_byId[objId]

      // Remove old status
      if(obj.startpagesSummary.status == 'warning'){
        $scope.httpStatusWarning--
      } else if(obj.startpagesSummary.status == 'success'){
        $scope.httpStatusSuccess--
      } else {
        $scope.httpStatusLoading--
      }

      $scope.list = $scope.list.filter(function(obj){
        return obj.id != objId
      })

      delete list_byId[objId]

    }

    // Initialization

    getSettingsFromCorpusOptions()
    bootstrapList(store.get('webentities_toCrawl'))
    store.remove('webentities_toCrawl')

    // Functions

    function getSettingsFromCorpusOptions(){
      api.getCorpusOptions({
        id: $scope.corpusId
      }, function(options){
        $scope.depthRange = Array.apply(0, Array(options.max_depth + 1)).map(function(a,i){return i})
      }, function(){
        $scope.status = {message: "Error while getting options", background: 'danger'}
      })
    }

    function bootstrapList(list){
      list = list || []

      // Remove doublons
      list = utils.extractCases(list, function(obj){
        return obj.webentity.id
      })

      // Clean and set exactly what we need
      list = list.map(function(obj, i){
        $scope.httpStatusLoading++
        return {
          id:i
          ,webentity: obj.webentity
          ,status: 'loading'
          ,touched: false
          ,collapsed: true
          ,startpagesSummary: {status: 'loading'}
        }
      })

      // Build index
      list.forEach(function(obj){
        list_byId[obj.id] = obj
      })

      // Redirect if needed
      if(list.length == 0){
        $location.path('/project/'+$scope.corpusId+'/monitorCrawls')
      } else {
        $scope.list = list
        // getWebentities()
      }
    }

    // Get web entities (including start pages)
    function getWebentities(opt){

      // Options
      opt = opt || {}
      // opt.skip_when_start_pages_exist
      // opt.list

      $scope.status = {message:'Loading Web Entities'}

      var queriesBatcher = new QueriesBatcher()
        , listToQuery = opt.list || $scope.list
      $scope.queriesBatches.push(queriesBatcher)

      if(opt.skip_when_start_pages_exist){
        listToQuery = listToQuery.filter(function(obj){
          return obj.webentity.startpages === undefined
        })
      }

      listToQuery.forEach(function(obj){
        // Stack the query
        queriesBatcher.addQuery(
            api.getWebentities                    // Query call
            ,{                                    // Query settings
                id_list:[obj.webentity.id]
              }
            ,function(we_list){                   // Success callback
                if(we_list.length > 0){
                  obj_setStatus(obj, 'loaded')
                  obj.webentity = we_list[0]

                  // TODO : new behavior to implement
                  // updateStartPageLookups(obj)
                } else {
                  obj_setStatus(obj, 'error')
                  console.log('[row '+(obj.id+1)+'] Error while loading web entity ' + obj.webentity.id + '(' + obj.webentity.name + ')', we_list, 'status:', status)
                }
              }
            ,function(data, status, headers){     // Fail callback
                obj_setStatus(obj, 'error')
                console.log('[row '+(obj.id+1)+'] Error while loading web entity ' + obj.webentity.id + '(' + obj.webentity.name + ')', data, 'status:', status)
                if(data && data[0] && data[0].code == 'fail'){
                  obj.infoMessage = data[0].message
                }
              }
            ,{                                    // Options
                label: obj.webentity.id
                ,before: function(){
                    obj_setStatus(obj, 'pending')
                  }
              }
          )
      })

      queriesBatcher.atEachFetch(function(list,pending,success,fail){
        var summary = {
          total: list.length + pending.length + success.length + fail.length
          ,pending: pending.length
          ,loaded: success.length + fail.length
        }
        ,percent = Math.round((summary.loaded / summary.total) * 100)
        ,percent_pending = Math.round((summary.pending / summary.total) * 100)
        ,msg = percent + '% loaded'
        $scope.status = {message: msg, progress:percent, progressPending:percent_pending}
      })

      queriesBatcher.atFinalization(function(list,pending,success,fail){
        // Status message
        $scope.status = {}
      })

      queriesBatcher.run()
    }

    function obj_setStatus(obj, status){
      switch(obj.status){
        case('warning'):
          $scope.httpStatusWarning--
          break
        case('error'):
          $scope.httpStatusWarning--
          break
        case('loaded'):
          switch(obj.startpagesSummary.status){
            case('success'):
              $scope.httpStatusSuccess--
              break
            case('warning'):
              $scope.httpStatusWarning--
              break
            default:
              $scope.httpStatusLoading--
          }
          break
        default:
          $scope.httpStatusLoading--
      }

      obj.status = status

      switch(status){
        case('warning'):
          $scope.httpStatusWarning++
          break
        case('error'):
          $scope.httpStatusWarning++
          break
        case('loaded'):
          switch(obj.startpagesSummary.status){
            case('success'):
              $scope.httpStatusSuccess++
              break
            case('warning'):
              $scope.httpStatusWarning++
              break
            default:
              $scope.httpStatusLoading++
          }
          break
        default:
          $scope.httpStatusLoading++
      }
    }

  }])
<|endoftext|>