<commit_before>/**
    Framework for building object relational database apps

    Copyright (C) 2015  John Rogelstad
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**/

/*global window, f, m */
(function (f) {
  "use strict";

  var EDIT_MODE = 2,
    LIST_MODE = 1;

  // Calculate scroll bar width
  // http://stackoverflow.com/questions/13382516/getting-scroll-bar-width-using-javascript
  var scrWidth, inner, widthNoScroll, widthWithScroll,
    outer = document.createElement("div");

  outer.style.visibility = "hidden";
  outer.style.width = "100px";
  outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps

  document.body.appendChild(outer);

  widthNoScroll = outer.offsetWidth;
  // force scrollbars
  outer.style.overflow = "scroll";

  // add innerdiv
  inner = document.createElement("div");
  inner.style.width = "100%";
  outer.appendChild(inner);

  widthWithScroll = inner.offsetWidth;

  // remove divs
  outer.parentNode.removeChild(outer);
  scrWidth = widthNoScroll - widthWithScroll;

  // Define workbook view model
  f.viewModels.workbookViewModel = function (options) {
    var selection,
      sheet = options.sheet,
      frmroute = "/" + options.name + "/" + options.config[sheet].form.name,
      name = options.feather.toCamelCase(),
      vm = {};
    frmroute = frmroute.toSpinalCase();

    vm.config = function () {
      return options.config || {};
    };
    vm.models = f.models[name].list();
    vm.attrs = options.config[sheet].list.attrs || ["id"];
    vm.canSave = function (model) {
      var currentState = model.state.current()[0];
      return (currentState === "/Ready/New" ||
        currentState === "/Ready/Fetched/Dirty") &&
        model.isValid();
    };
    vm.goHome = function () {
      m.route("/home");
    };
    vm.goNextRow = function () {
      var list = vm.models(),
        model = vm.selection(),
        idx = list.indexOf(model) + 1;
      if (list.length > idx) {
        vm.select(list[idx]);
      }
    };
    vm.goPrevRow = function () {
      var list = vm.models(),
        model = vm.selection(),
        idx = list.indexOf(model) - 1;
      if (idx >= 0) {
        vm.select(list[idx]);
      }
    };
    vm.focusColumn = m.prop(vm.attrs[0]);
    vm.canSave = function () {
      return vm.models().some(function (model) {
        return model.canSave();
      });
    };
    vm.hasNoSelection = function () {
      return !selection;
    };
    vm.isSelected = function (model) {
      return selection === model;
    };
    vm.modelDelete = function () {
      // Delete now in list mode
      if (vm.mode() === LIST_MODE) {
        selection.delete(true).then(function () {
          vm.models().remove(selection);
        });
        return;
      }

      // Mork for deletion in edit mode
      selection.delete();
    };
    vm.mode = m.prop(LIST_MODE);
    vm.modelNew = function () {
      m.route(frmroute);
    };
    vm.modelOpen = function () {
      m.route(frmroute + "/" + selection.data.id());
    };
    vm.nextFocus = m.prop();
    vm.onkeydown = function (e) {
      var id,
        nav = function (name) {
          id = e.srcElement.id;
          // Navigate in desired direction
          m.startComputation();
          vm[name]();
          m.endComputation();
          // Set focus on the same cell we left
          m.startComputation();
          document.getElementById(id).focus();
          m.endComputation();
        };

      switch (e.key || e.keyIdentifier)
      {
      case "Up":
        nav("goPrevRow");
        break;
      case "Down":
        nav("goNextRow");
        break;
      }
    };
    vm.onscroll = function () {
      var rows = document.getElementById("rows"),
        header = document.getElementById("header");

      // Sync header position with table body position
      header.scrollLeft = rows.scrollLeft;
    };
    vm.scrollbarWidth = function () {
      return scrWidth;
    };
    vm.saveAll = function () {
      vm.models().forEach(function (model) {
        model.save().then(function() {
          if (model.state.current()[0] === "/Deleted") {
            vm.models().remove(model);
          }
        });
      });
    };
    vm.select = function (model) {
      if (selection !== model) {
        selection = model;
      }
      return selection;
    };
    vm.selection = function () {
      return selection;
    };
    vm.selectedTab = m.prop(options.sheet);
    vm.sheets = function () {
      return Object.keys(options.config || {});
    };
    vm.tabClicked = function (sheet) {
      var route = "/" + options.name + "/" + sheet;
      route = route.toSpinalCase();
      m.route(route);
    };
    vm.toggleMode = function () {
      var mode = vm.mode() === LIST_MODE ? EDIT_MODE : LIST_MODE;
      vm.mode(mode);
    };
    vm.toggleOpen = function (model) {
      selection = model;
      vm.modelOpen();
    };
    vm.toggleSelection = function (model, col) {
      var mode = vm.mode(),
        isSelected = selection === model;

      // Toggle row off
      if (isSelected && mode === LIST_MODE) {
        vm.select(undefined);
        return false;
      }

      // Select new row
      vm.select(model);

      // If editing, focus on clicked cell
      if (!isSelected && mode === EDIT_MODE) {
        vm.nextFocus("input" + col.toCamelCase(true));
      }
      return true;
    };
    vm.undo = function () {
      if (selection) { selection.undo(); }
    };

    return vm;
  };

  // Define workbook component
  f.components.workbookDisplay = function (options) {
    var component = {};

    component.controller = function () {
      this.vm = f.viewModels.workbookViewModel(options);
    };

    component.view = function (ctrl) {
      var tbodyConfig, header, rows, tabs, view,
        vm = ctrl.vm;

      // Define scrolling behavior for table body
      tbodyConfig = function (e) {
        var tb = document.getElementById("toolbar"),
          hd = document.getElementById("header"),
          ts = document.getElementById("tabs"),
          mh = window.innerHeight - tb.clientHeight - hd.clientHeight - ts.clientHeight- 12;

        // Set fields table to scroll and toolbar to stay put
        document.documentElement.style.overflow = 'hidden';
        e.style.height = mh + "px";

        // Key down handler for up down movement
        e.addEventListener('keydown', vm.onkeydown);
      };

      // Build header
      header = (function () {
        var ths = vm.attrs.map(function (key) {
            return m("th", {
              style: {
                minWidth: "100px",
                maxWidth: "100px",
                whiteSpace: "nowrap",
                overflow: "hidden",
                textOverflow: "ellipsis"
              }
            },
              key.toProperCase(true));
          });

        // Front cap header navigation
        ths.unshift(m("th", {style: {minWidth: "16px"}}));

        // End cap on header for scrollbar
        ths.push(m("th", {
          style: {
            minWidth: vm.scrollbarWidth() + "px",
            maxWidth: vm.scrollbarWidth() + "px"
          }
        }));
        return m("tr", ths);
      }());

      // Build rows
      rows = vm.models().map(function (model) {
        var tds, row, thContent,
          mode = vm.mode(),
          color = "White",
          isSelected = vm.isSelected(model),
          d = model.data,
          rowOpts = {};

        // Build view row
        if (mode === LIST_MODE || !isSelected) {
          // Build cells
          tds = vm.attrs.map(function (col) {
            var cell,
              value = d[col](),
              hasLocale = value !== null &&
                typeof value.toLocaleString === "function";

            // Build cell
            cell = m("td", {
                onclick: vm.toggleSelection.bind(this, model, col),
                style: {
                  minWidth: "100px",
                  maxWidth: "100px",
                  whiteSpace: "nowrap",
                  overflow: "hidden",
                  textOverflow: "ellipsis"
                }
              },
              hasLocale ? value.toLocaleString() : value);

            return cell;
          });

          // Build row
          if (isSelected) {
            color = "LightBlue";
          }

          rowOpts = {
            ondblclick: vm.toggleOpen.bind(this, model)
          };

        // Build editable row
        } else {
          // Build cells
          tds = vm.attrs.map(function (col) {
            var cell, cellOpts,
              id = "input" + col.toCamelCase(true);

            cellOpts = {
              id: id,
              onclick: vm.toggleSelection.bind(this, model, col),
              onchange: m.withAttr("value", d[col]),
              value: d[col](),
              config: function (e) {
                if (vm.nextFocus() === id) {
                  e.focus();
                  vm.nextFocus(undefined);
                }
              },
              style: {
                minWidth: "100px",
                maxWidth: "100px"
              }
            };

            // Build cell
            cell = m("td", [
              m("input", cellOpts)
            ]);

            return cell;
          });
        }

        // Front cap header navigation
        if (model.state.current()[0] === "/Delete") {
          thContent = [m("i", {class:"fa fa-remove"})];
        } else if (model.canSave()) {
          thContent = [m("i", {class:"fa fa-check"})];
        } else {
          thContent = {
            style: {
              minWidth: "16px"
            }
          };
        }
        tds.unshift(m("th", thContent));

        // Build row
        rowOpts.style = { backgroundColor: color };
        row = m("tr", rowOpts, tds);

        return row;
      });

      // Build tabs
      tabs = vm.sheets().map(function (sheet) {
        var tab,
          sconfig = vm.config()[sheet],
          label = sconfig.feather ?
            sheet : f.catalog.getFeather(sheet).plural;

        // Build tab
        tab = m("button[type=button]", {
          class: vm.selectedTab() === sheet ?
            "pure-button pure-button-active" : "pure-button",
          style: {
            borderTopLeftRadius: "0px",
            borderTopRightRadius: "0px"
          },
          onclick: vm.tabClicked.bind(this, sheet)
        }, label);

        return tab;
      });

      // Finally assemble the whole view
      view = m("form", {
        class: "pure-form"
      }, [
        m("div", {
            id: "toolbar"
          }, [
          m("button", {
            type: "button",
            class: "pure-button",
            style: { margin: "1px" },
            onclick: vm.goHome
          }, [m("i", {class:"fa fa-home"})], " Home"),
          m("button", {
            type: "button",
            class: "pure-button",
            style: {
              margin: "1px",
              display: vm.mode() === EDIT_MODE ? "none" : "inline-block"
            },
            onclick: vm.toggleMode
          }, [m("i", {class:"fa fa-pencil"})], " Edit"),
          m("button", {
            type: "button",
            class: "pure-button",
            style: {
              margin: "1px",
              display: vm.mode() === LIST_MODE ? "none" : "inline-block"
            },
            onclick: vm.toggleMode
          }, [m("i", {class:"fa fa-th-list"})], " List"),
            m("button", {
            type: "button",
            class: "pure-button",
            style: {
              margin: "1px",
              display: vm.mode() === LIST_MODE ? "none" : "inline-block"
            },
            onclick: vm.saveAll,
            disabled: !vm.canSave()
          }, [m("i", {class:"fa fa-save"})], " Save"),
          m("button", {
            type: "button",
            class: "pure-button",
            style: {
              margin: "1px",
              display: vm.mode() === EDIT_MODE ? "none" : "inline-block"
            },
            onclick: vm.modelOpen,
            disabled: vm.hasNoSelection()
          }, [m("i", {class:"fa fa-folder-open"})], " Open"),
          m("button", {
            type: "button",
            class: "pure-button",
            style: { margin: "1px"},
            onclick: vm.modelNew
          }, [m("i", {class:"fa fa-plus-circle"})], " New"),
          m("button", {
            type: "button",
            class: "pure-button",
            style: {
              margin: "1px",
              display: (vm.selection() && vm.selection().canSave()) ?
                "none" : "inline-block"
            },
            onclick: vm.modelDelete,
            disabled: vm.hasNoSelection()
          }, [m("i", {class:"fa fa-remove"})], " Delete"),
          m("button", {
            type: "button",
            class: "pure-button",
            style: {
              margin: "1px",
              display: (vm.selection() && vm.selection().canSave()) ?
                "inline-block" : "none"
            },
            onclick: vm.undo
          }, [m("i", {class:"fa fa-undo"})], " Undo"),
          m("input", {
            type: "search",
            value: "Search",
            id: "search",
            style: {
              color: "LightGrey",
              fontStyle: "italic",
              margin: "2px"
            }
          })
        ]),
        m("table", {
          class: "pure-table",
          style: {
            tableLayout: "fixed",
            width: "100%"
          }
        }, [
            m("thead", {
            id: "header",
            style: {
              display: "inline-block",
              width: "100%",
              overflow: "hidden"
            }
          }, [ header ]),
          m("tbody", {
            id: "rows",
            onscroll: vm.onscroll,
            style: {
              display: "inline-block",
              width: "100%",
              overflow: "auto"
            },
            config: tbodyConfig
          }, [rows])
        ]),
        m("div", {
            id: "tabs"
          }, tabs
        )
      ]);

      return view;
    };

    return component;
  };

}(f));

<commit_msg>Refinements to click focus<commit_after>/**
    Framework for building object relational database apps

    Copyright (C) 2015  John Rogelstad
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**/

/*global window, f, m */
(function (f) {
  "use strict";

  var EDIT_MODE = 2,
    LIST_MODE = 1;

  // Calculate scroll bar width
  // http://stackoverflow.com/questions/13382516/getting-scroll-bar-width-using-javascript
  var scrWidth, inner, widthNoScroll, widthWithScroll,
    outer = document.createElement("div");

  outer.style.visibility = "hidden";
  outer.style.width = "100px";
  outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps

  document.body.appendChild(outer);

  widthNoScroll = outer.offsetWidth;
  // force scrollbars
  outer.style.overflow = "scroll";

  // add innerdiv
  inner = document.createElement("div");
  inner.style.width = "100%";
  outer.appendChild(inner);

  widthWithScroll = inner.offsetWidth;

  // remove divs
  outer.parentNode.removeChild(outer);
  scrWidth = widthNoScroll - widthWithScroll;

  // Define workbook view model
  f.viewModels.workbookViewModel = function (options) {
    var selection,
      sheet = options.sheet,
      frmroute = "/" + options.name + "/" + options.config[sheet].form.name,
      name = options.feather.toCamelCase(),
      vm = {};
    frmroute = frmroute.toSpinalCase();

    vm.config = function () {
      return options.config || {};
    };
    vm.models = f.models[name].list();
    vm.attrs = options.config[sheet].list.attrs || ["id"];
    vm.canSave = function (model) {
      var currentState = model.state.current()[0];
      return (currentState === "/Ready/New" ||
        currentState === "/Ready/Fetched/Dirty") &&
        model.isValid();
    };
    vm.goHome = function () {
      m.route("/home");
    };
    vm.goNextRow = function () {
      var list = vm.models(),
        model = vm.selection(),
        idx = list.indexOf(model) + 1;
      if (list.length > idx) {
        vm.select(list[idx]);
      }
    };
    vm.goPrevRow = function () {
      var list = vm.models(),
        model = vm.selection(),
        idx = list.indexOf(model) - 1;
      if (idx >= 0) {
        vm.select(list[idx]);
      }
    };
    vm.focusColumn = m.prop(vm.attrs[0]);
    vm.canSave = function () {
      return vm.models().some(function (model) {
        return model.canSave();
      });
    };
    vm.hasNoSelection = function () {
      return !selection;
    };
    vm.isSelected = function (model) {
      return selection === model;
    };
    vm.modelDelete = function () {
      // Delete now in list mode
      if (vm.mode() === LIST_MODE) {
        selection.delete(true).then(function () {
          vm.models().remove(selection);
        });
        return;
      }

      // Mork for deletion in edit mode
      selection.delete();
    };
    vm.mode = m.prop(LIST_MODE);
    vm.modelNew = function () {
      m.route(frmroute);
    };
    vm.modelOpen = function () {
      m.route(frmroute + "/" + selection.data.id());
    };
    vm.nextFocus = m.prop();
    vm.onkeydown = function (e) {
      var id,
        nav = function (name) {
          id = e.srcElement.id;
          // Navigate in desired direction
          m.startComputation();
          vm[name]();
          m.endComputation();
          // Set focus on the same cell we left
          m.startComputation();
          document.getElementById(id).focus();
          m.endComputation();
        };

      switch (e.key || e.keyIdentifier)
      {
      case "Up":
        nav("goPrevRow");
        break;
      case "Down":
        nav("goNextRow");
        break;
      }
    };
    vm.onscroll = function () {
      var rows = document.getElementById("rows"),
        header = document.getElementById("header");

      // Sync header position with table body position
      header.scrollLeft = rows.scrollLeft;
    };
    vm.scrollbarWidth = function () {
      return scrWidth;
    };
    vm.saveAll = function () {
      vm.models().forEach(function (model) {
        model.save().then(function() {
          if (model.state.current()[0] === "/Deleted") {
            vm.models().remove(model);
          }
        });
      });
    };
    vm.select = function (model) {
      if (selection !== model) {
        selection = model;
      }
      return selection;
    };
    vm.selection = function () {
      return selection;
    };
    vm.selectedTab = m.prop(options.sheet);
    vm.sheets = function () {
      return Object.keys(options.config || {});
    };
    vm.tabClicked = function (sheet) {
      var route = "/" + options.name + "/" + sheet;
      route = route.toSpinalCase();
      m.route(route);
    };
    vm.toggleMode = function () {
      var mode = vm.mode() === LIST_MODE ? EDIT_MODE : LIST_MODE;
      vm.mode(mode);
    };
    vm.toggleOpen = function (model) {
      selection = model;
      vm.modelOpen();
    };
    vm.toggleSelection = function (model, col) {
      var mode = vm.mode(),
        isSelected = selection === model;

      // Toggle row off
      if (isSelected && mode === LIST_MODE) {
        vm.select(undefined);
        return false;
      }

      // Select new row
      vm.select(model);

      // Set next focus on clicked cell when editing
      vm.nextFocus("input" + col.toCamelCase(true));

      return true;
    };
    vm.undo = function () {
      if (selection) { selection.undo(); }
    };

    return vm;
  };

  // Define workbook component
  f.components.workbookDisplay = function (options) {
    var component = {};

    component.controller = function () {
      this.vm = f.viewModels.workbookViewModel(options);
    };

    component.view = function (ctrl) {
      var tbodyConfig, header, rows, tabs, view,
        vm = ctrl.vm;

      // Define scrolling behavior for table body
      tbodyConfig = function (e) {
        var tb = document.getElementById("toolbar"),
          hd = document.getElementById("header"),
          ts = document.getElementById("tabs"),
          mh = window.innerHeight - tb.clientHeight - hd.clientHeight - ts.clientHeight- 12;

        // Set fields table to scroll and toolbar to stay put
        document.documentElement.style.overflow = 'hidden';
        e.style.height = mh + "px";

        // Key down handler for up down movement
        e.addEventListener('keydown', vm.onkeydown);
      };

      // Build header
      header = (function () {
        var ths = vm.attrs.map(function (key) {
            return m("th", {
              style: {
                minWidth: "100px",
                maxWidth: "100px",
                whiteSpace: "nowrap",
                overflow: "hidden",
                textOverflow: "ellipsis"
              }
            },
              key.toProperCase(true));
          });

        // Front cap header navigation
        ths.unshift(m("th", {style: {minWidth: "16px"}}));

        // End cap on header for scrollbar
        ths.push(m("th", {
          style: {
            minWidth: vm.scrollbarWidth() + "px",
            maxWidth: vm.scrollbarWidth() + "px"
          }
        }));
        return m("tr", ths);
      }());

      // Build rows
      rows = vm.models().map(function (model) {
        var tds, row, thContent, onclick,
          mode = vm.mode(),
          color = "White",
          isSelected = vm.isSelected(model),
          d = model.data,
          rowOpts = {};

        // Build view row
        if (mode === LIST_MODE || !isSelected) {
          // Build cells
          tds = vm.attrs.map(function (col) {
            var cell,
              value = d[col](),
              hasLocale = value !== null &&
                typeof value.toLocaleString === "function";

            // Build cell
            cell = m("td", {
                onclick: vm.toggleSelection.bind(this, model, col),
                style: {
                  minWidth: "100px",
                  maxWidth: "100px",
                  whiteSpace: "nowrap",
                  overflow: "hidden",
                  textOverflow: "ellipsis"
                }
              },
              hasLocale ? value.toLocaleString() : value);

            return cell;
          });

          // Build row
          if (isSelected) {
            color = "LightBlue";
          }

          rowOpts = {
            ondblclick: vm.toggleOpen.bind(this, model)
          };

        // Build editable row
        } else {
          // Build cells
          tds = vm.attrs.map(function (col) {
            var cell, cellOpts,
              id = "input" + col.toCamelCase(true);

            cellOpts = {
              id: id,
              onclick: vm.toggleSelection.bind(this, model, col),
              onchange: m.withAttr("value", d[col]),
              value: d[col](),
              config: function (e) {
                if (vm.nextFocus() === id) {
                  e.focus();
                  vm.nextFocus(undefined);
                }
              },
              style: {
                minWidth: "100px",
                maxWidth: "100px"
              }
            };

            // Build cell
            cell = m("td", [
              m("input", cellOpts)
            ]);

            return cell;
          });
        }

        // Front cap header navigation
        onclick = vm.toggleSelection.bind(this, model, vm.attrs[0]);
        if (model.state.current()[0] === "/Delete") {
          thContent = [m("i", {
            onclick: onclick,
            class:"fa fa-remove"
          })];
        } else if (model.canSave()) {
          thContent = [m("i", {
            onclick: onclick,
            class:"fa fa-check"
          })];
        } else {
          thContent = {
            onclick: onclick,
            style: {
              minWidth: "16px"
            }
          };
        }
        tds.unshift(m("th", thContent));

        // Build row
        rowOpts.style = { backgroundColor: color };
        row = m("tr", rowOpts, tds);

        return row;
      });

      // Build tabs
      tabs = vm.sheets().map(function (sheet) {
        var tab,
          sconfig = vm.config()[sheet],
          label = sconfig.feather ?
            sheet : f.catalog.getFeather(sheet).plural;

        // Build tab
        tab = m("button[type=button]", {
          class: vm.selectedTab() === sheet ?
            "pure-button pure-button-active" : "pure-button",
          style: {
            borderTopLeftRadius: "0px",
            borderTopRightRadius: "0px"
          },
          onclick: vm.tabClicked.bind(this, sheet)
        }, label);

        return tab;
      });

      // Finally assemble the whole view
      view = m("form", {
        class: "pure-form"
      }, [
        m("div", {
            id: "toolbar"
          }, [
          m("button", {
            type: "button",
            class: "pure-button",
            style: { margin: "1px" },
            onclick: vm.goHome
          }, [m("i", {class:"fa fa-home"})], " Home"),
          m("button", {
            type: "button",
            class: "pure-button",
            style: {
              margin: "1px",
              display: vm.mode() === EDIT_MODE ? "none" : "inline-block"
            },
            onclick: vm.toggleMode
          }, [m("i", {class:"fa fa-pencil"})], " Edit"),
          m("button", {
            type: "button",
            class: "pure-button",
            style: {
              margin: "1px",
              display: vm.mode() === LIST_MODE ? "none" : "inline-block"
            },
            onclick: vm.toggleMode
          }, [m("i", {class:"fa fa-th-list"})], " List"),
            m("button", {
            type: "button",
            class: "pure-button",
            style: {
              margin: "1px",
              display: vm.mode() === LIST_MODE ? "none" : "inline-block"
            },
            onclick: vm.saveAll,
            disabled: !vm.canSave()
          }, [m("i", {class:"fa fa-save"})], " Save"),
          m("button", {
            type: "button",
            class: "pure-button",
            style: {
              margin: "1px",
              display: vm.mode() === EDIT_MODE ? "none" : "inline-block"
            },
            onclick: vm.modelOpen,
            disabled: vm.hasNoSelection()
          }, [m("i", {class:"fa fa-folder-open"})], " Open"),
          m("button", {
            type: "button",
            class: "pure-button",
            style: { margin: "1px"},
            onclick: vm.modelNew
          }, [m("i", {class:"fa fa-plus-circle"})], " New"),
          m("button", {
            type: "button",
            class: "pure-button",
            style: {
              margin: "1px",
              display: (vm.selection() && vm.selection().canSave()) ?
                "none" : "inline-block"
            },
            onclick: vm.modelDelete,
            disabled: vm.hasNoSelection()
          }, [m("i", {class:"fa fa-remove"})], " Delete"),
          m("button", {
            type: "button",
            class: "pure-button",
            style: {
              margin: "1px",
              display: (vm.selection() && vm.selection().canSave()) ?
                "inline-block" : "none"
            },
            onclick: vm.undo
          }, [m("i", {class:"fa fa-undo"})], " Undo"),
          m("input", {
            type: "search",
            value: "Search",
            id: "search",
            style: {
              color: "LightGrey",
              fontStyle: "italic",
              margin: "2px"
            }
          })
        ]),
        m("table", {
          class: "pure-table",
          style: {
            tableLayout: "fixed",
            width: "100%"
          }
        }, [
            m("thead", {
            id: "header",
            style: {
              display: "inline-block",
              width: "100%",
              overflow: "hidden"
            }
          }, [ header ]),
          m("tbody", {
            id: "rows",
            onscroll: vm.onscroll,
            style: {
              display: "inline-block",
              width: "100%",
              overflow: "auto"
            },
            config: tbodyConfig
          }, [rows])
        ]),
        m("div", {
            id: "tabs"
          }, tabs
        )
      ]);

      return view;
    };

    return component;
  };

}(f));

<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import {graphql} from 'react-apollo'
import compose from 'util/compose'
import gql from 'graphql-tag'
import BaseLayout from 'client/components/layout/BaseLayout'
import {
    SimpleButton,
    SimpleDialog,
    Typography,
    TextField,
    Divider,
    DeleteIconButton,
    Chip,
    SimpleList,
    Row,
    Col
} from 'ui/admin'
import FileDrop from 'client/components/FileDrop'
import Util from 'client/util'
import config from 'gen/config'

const {BACKUP_URL} = config

class DbDumpContainer extends React.Component {

    constructor(props) {
        super(props)

        this.state = {
            creatingDump: false,
            creatingMediaDump: false,
            importingDbDump: false,
            importingMediaDump: false,
            importMediaDumpDialog: false,
            importDbDumpDialog: false
        }
    }

    createDump() {
        this.setState({creatingDump: true})
        this.props.createDbDump().then(e => {
            console.log(e)
            this.setState({creatingDump: false})
        })

    }

    createMediaDump() {
        this.setState({creatingMediaDump: true})
        this.props.createMediaDump().then(e => {
            console.log(e)
            this.setState({creatingMediaDump: false})
        })

    }

    importMediaDump() {
        this.setState({importMediaDumpDialog: true})
    }

    importDbDump() {
        this.setState({importDbDumpDialog: true})
    }

    handleConfirmMediaDialog() {
        this.setState({importMediaDumpDialog: false})
    }

    handleConfirmDbDialog() {
        this.setState({importDbDumpDialog: false})
    }

    handleMediaDumpUpload(e) {
        console.log(e)
    }

    download(content, filename, contentType)
    {
        if(!contentType) contentType = 'application/octet-stream';
        var a = document.createElement('a');
        var blob = new Blob([content], {'type':contentType});
        a.href = window.URL.createObjectURL(blob);
        a.download = filename;
        a.click();
    }

    authorizedRequest(url, name){
        const xhr = new XMLHttpRequest

        xhr.open("GET",url)
        xhr.responseType = 'blob'

        xhr.addEventListener("load", () => {
            if( xhr.status === 200) {

                this.download(xhr.response, name, 'application/gzip')
            }else{

                alert(`Invalid status ${xhr.status}`)
            }
        }, false)

        xhr.setRequestHeader('Authorization', Util.getAuthToken())
        // xhr.overrideMimeType( "application/octet-stream; charset=x-user-defined;" )
        xhr.send(null)
    }

    render() {
        const {dbDumps, mediaDumps} = this.props
        const {creatingDump, creatingMediaDump, importingMediaDump, importMediaDumpDialog, importDbDumpDialog, importingDbDump} = this.state
        return (
            <BaseLayout>
                <Typography variant="h3" gutterBottom>Backups</Typography>

                <Row spacing={4}>
                    <Col md={6}>

                        <SimpleButton variant="contained" color="primary"
                                      showProgress={creatingDump}
                                      onClick={this.createDump.bind(this)}>{creatingDump ? 'Dump is beeing created' : 'Create new db dump'}</SimpleButton>

                        <SimpleButton color="secondary"
                                      disabled={importDbDumpDialog}
                                      showProgress={importingDbDump}
                                      onClick={this.importDbDump.bind(this)}>{importingDbDump ? 'Dump is beeing restored' : 'Restore db dump'}</SimpleButton>

                        {dbDumps && dbDumps.results && dbDumps.results.length > 0 &&
                        <SimpleList items={dbDumps.results.reduce((a, i) => {
                            a.push({
                                primary: i.name,
                                onClick: () => {

                                    this.authorizedRequest(BACKUP_URL + '/dbdumps/' + i.name, 'db.backup.gz' )

                                },
                                secondary: Util.formattedDatetime(i.createdAt) + ' - ' + i.size
                            })
                            return a
                        }, [])}/>
                        }
                    </Col>

                    <Col md={6}>

                        <SimpleButton variant="contained" color="primary"
                                      disabled={importMediaDumpDialog}
                                      showProgress={creatingMediaDump}
                                      onClick={this.createMediaDump.bind(this)}>{creatingMediaDump ? 'Dump is beeing created' : 'Create new media dump'}</SimpleButton>

                        <SimpleButton color="secondary"
                                      showProgress={importingMediaDump}
                                      onClick={this.importMediaDump.bind(this)}>{importingMediaDump ? 'Dump is beeing imported' : 'Import media dump'}</SimpleButton>

                        {mediaDumps && mediaDumps.results && mediaDumps.results.length > 0 &&
                        <SimpleList items={mediaDumps.results.reduce((a, i) => {
                            a.push({
                                primary: i.name,
                                onClick: () => {
                                    this.authorizedRequest(BACKUP_URL + '/mediadumps/' + i.name, 'medias.backup.gz' )
                                },
                                secondary: Util.formattedDatetime(i.createdAt) + ' - ' + i.size
                            })
                            return a
                        }, [])}/>
                        }
                    </Col>
                </Row>

                <SimpleDialog open={importMediaDumpDialog} onClose={this.handleConfirmMediaDialog.bind(this)}
                              actions={[{key: 'close', label: 'Close'}]}
                              title="Select a media dump file">

                    <FileDrop style={{width: '15rem', height: '10rem'}} accept="application/x-gzip"
                              uploadTo="/graphql/upload/mediadump"
                              label="Drop file here"/>

                </SimpleDialog>

                <SimpleDialog open={importDbDumpDialog} onClose={this.handleConfirmDbDialog.bind(this)}
                              actions={[{key: 'close', label: 'Close'}]}
                              title="Select a db dump file">

                    <FileDrop style={{width: '15rem', height: '10rem'}} accept="application/x-gzip"
                              uploadTo="/graphql/upload/dbdump"
                              label="Drop file here"/>

                </SimpleDialog>

            </BaseLayout>
        )
    }
}

DbDumpContainer.propTypes = {
    /* apollo client props */
    dbDumps: PropTypes.object,
    createDbDump: PropTypes.func.isRequired,
    createMediaDump: PropTypes.func.isRequired,
    loading: PropTypes.bool,
    mediaDumps: PropTypes.object
}

const gqlQuery = gql`query{dbDumps{results{name createdAt size}}}`
const gqlUpdate = gql`mutation createDbDump($type:String){createDbDump(type:$type){name createdAt size}}`
const gqlQueryMedia = gql`query{mediaDumps{results{name createdAt size}}}`
const gqlUpdateMedia = gql`mutation createMediaDump($type:String){createMediaDump(type:$type){name createdAt size}}`

const DbDumpContainerWithGql = compose(
    graphql(gqlQuery, {
        options() {
            return {
                fetchPolicy: 'cache-and-network'
            }
        },
        props: ({data: {dbDumps}}) => ({
            dbDumps
        })
    }),
    graphql(gqlUpdate, {
        props: ({mutate}) => ({
            createDbDump: () => {
                return mutate({
                    variables: {type: 'full'},
                    update: (proxy, {data: {createDbDump}}) => {
                        // Read the data from our cache for this query.
                        const data = proxy.readQuery({query: gqlQuery})
                        // Add our note from the mutation to the end.
                        data.dbDumps.results.unshift(createDbDump)
                        // Write our data back to the cache.
                        proxy.writeQuery({query: gqlQuery, data})
                    }

                })
            }
        })
    }),
    graphql(gqlQueryMedia, {
        options() {
            return {
                fetchPolicy: 'cache-and-network'
            }
        },
        props: ({data: {mediaDumps}}) => ({
            mediaDumps
        })
    }),
    graphql(gqlUpdateMedia, {
        props: ({mutate}) => ({
            createMediaDump: () => {
                return mutate({
                    variables: {type: 'full'},
                    update: (proxy, {data: {createMediaDump}}) => {
                        // Read the data from our cache for this query.
                        const data = proxy.readQuery({query: gqlQueryMedia})
                        // Add our note from the mutation to the end.
                        data.mediaDumps.results.unshift(createMediaDump)
                        // Write our data back to the cache.
                        proxy.writeQuery({query: gqlQueryMedia, data})
                    }

                })
            }
        })
    })
)
(DbDumpContainer)

/**
 * Connect the component to
 * the Redux store.
 */
export default DbDumpContainerWithGql
<commit_msg>Small improvements<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import {graphql} from 'react-apollo'
import compose from 'util/compose'
import gql from 'graphql-tag'
import BaseLayout from 'client/components/layout/BaseLayout'
import {
    SimpleButton,
    SimpleDialog,
    Typography,
    TextField,
    Divider,
    DeleteIconButton,
    Chip,
    SimpleList,
    Row,
    Col
} from 'ui/admin'
import FileDrop from 'client/components/FileDrop'
import Util from 'client/util'
import config from 'gen/config'

const {BACKUP_URL} = config

class DbDumpContainer extends React.Component {

    constructor(props) {
        super(props)

        this.state = {
            creatingDump: false,
            creatingMediaDump: false,
            importingDbDump: false,
            importingMediaDump: false,
            importMediaDumpDialog: false,
            importDbDumpDialog: false
        }
    }

    createDump() {
        this.setState({creatingDump: true})
        this.props.createDbDump().then(e => {
            console.log(e)
            this.setState({creatingDump: false})
        })

    }

    createMediaDump() {
        this.setState({creatingMediaDump: true})
        this.props.createMediaDump().then(e => {
            console.log(e)
            this.setState({creatingMediaDump: false})
        })

    }

    importMediaDump() {
        this.setState({importMediaDumpDialog: true})
    }

    importDbDump() {
        this.setState({importDbDumpDialog: true})
    }

    handleConfirmMediaDialog() {
        this.setState({importMediaDumpDialog: false})
    }

    handleConfirmDbDialog() {
        this.setState({importDbDumpDialog: false})
    }

    handleMediaDumpUpload(e) {
        console.log(e)
    }

    download(content, filename, contentType)
    {
        if(!contentType) contentType = 'application/octet-stream';
        var a = document.createElement('a');
        var blob = new Blob([content], {'type':contentType});
        a.href = window.URL.createObjectURL(blob);
        a.download = filename;
        a.click();
    }

    authorizedRequest(url, name){
        const xhr = new XMLHttpRequest

        xhr.open("GET",url)
        xhr.responseType = 'blob'

        xhr.addEventListener("load", () => {
            if( xhr.status === 200) {

                this.download(xhr.response, name, 'application/gzip')
            }else{

                alert(`Invalid status ${xhr.status}`)
            }
        }, false)

        xhr.setRequestHeader('Authorization', Util.getAuthToken())
        // xhr.overrideMimeType( "application/octet-stream; charset=x-user-defined;" )
        xhr.send(null)
    }

    render() {
        const {dbDumps, mediaDumps} = this.props
        const {creatingDump, creatingMediaDump, importingMediaDump, importMediaDumpDialog, importDbDumpDialog, importingDbDump} = this.state
        return (
            <BaseLayout>
                <Typography variant="h3" gutterBottom>Backups</Typography>

                <Row spacing={4}>
                    <Col md={6}>

                        <SimpleButton variant="contained" color="primary"
                                      showProgress={creatingDump}
                                      onClick={this.createDump.bind(this)}>{creatingDump ? 'Dump is beeing created' : 'Create new db dump'}</SimpleButton>

                        <SimpleButton color="secondary"
                                      disabled={importDbDumpDialog}
                                      showProgress={importingDbDump}
                                      onClick={this.importDbDump.bind(this)}>{importingDbDump ? 'Dump is beeing restored' : 'Restore db dump'}</SimpleButton>

                        {dbDumps && dbDumps.results && dbDumps.results.length > 0 &&
                        <SimpleList items={dbDumps.results.reduce((a, i) => {
                            a.push({
                                primary: i.name,
                                onClick: () => {

                                    this.authorizedRequest(BACKUP_URL + '/dbdumps/' + i.name, 'db.backup.gz' )

                                },
                                secondary: Util.formattedDatetime(i.createdAt) + ' - ' + i.size
                            })
                            return a
                        }, [])}/>
                        }
                    </Col>

                    <Col md={6}>

                        <SimpleButton variant="contained" color="primary"
                                      disabled={importMediaDumpDialog}
                                      showProgress={creatingMediaDump}
                                      onClick={this.createMediaDump.bind(this)}>{creatingMediaDump ? 'Dump is beeing created' : 'Create new media dump'}</SimpleButton>

                        <SimpleButton color="secondary"
                                      showProgress={importingMediaDump}
                                      onClick={this.importMediaDump.bind(this)}>{importingMediaDump ? 'Dump is beeing imported' : 'Import media dump'}</SimpleButton>

                        {mediaDumps && mediaDumps.results && mediaDumps.results.length > 0 &&
                        <SimpleList items={mediaDumps.results.reduce((a, i) => {
                            a.push({
                                primary: i.name,
                                onClick: () => {
                                    this.authorizedRequest(BACKUP_URL + '/mediadumps/' + i.name, 'medias.backup.gz' )
                                },
                                secondary: Util.formattedDatetime(i.createdAt) + ' - ' + i.size
                            })
                            return a
                        }, [])}/>
                        }
                    </Col>
                </Row>

                <SimpleDialog open={importMediaDumpDialog} onClose={this.handleConfirmMediaDialog.bind(this)}
                              actions={[{key: 'close', label: 'Close'}]}
                              title="Select a media dump file">

                    <FileDrop maxSize={100000} style={{width: '15rem', height: '10rem'}} accept="application/x-gzip"
                              uploadTo="/graphql/upload/mediadump"
                              label="Drop file here"/>

                </SimpleDialog>

                <SimpleDialog open={importDbDumpDialog} onClose={this.handleConfirmDbDialog.bind(this)}
                              actions={[{key: 'close', label: 'Close'}]}
                              title="Select a db dump file">

                    <FileDrop style={{width: '15rem', height: '10rem'}} accept="application/x-gzip"
                              uploadTo="/graphql/upload/dbdump"
                              label="Drop file here"/>

                </SimpleDialog>

            </BaseLayout>
        )
    }
}

DbDumpContainer.propTypes = {
    /* apollo client props */
    dbDumps: PropTypes.object,
    createDbDump: PropTypes.func.isRequired,
    createMediaDump: PropTypes.func.isRequired,
    loading: PropTypes.bool,
    mediaDumps: PropTypes.object
}

const gqlQuery = gql`query{dbDumps{results{name createdAt size}}}`
const gqlUpdate = gql`mutation createDbDump($type:String){createDbDump(type:$type){name createdAt size}}`
const gqlQueryMedia = gql`query{mediaDumps{results{name createdAt size}}}`
const gqlUpdateMedia = gql`mutation createMediaDump($type:String){createMediaDump(type:$type){name createdAt size}}`

const DbDumpContainerWithGql = compose(
    graphql(gqlQuery, {
        options() {
            return {
                fetchPolicy: 'cache-and-network'
            }
        },
        props: ({data: {dbDumps}}) => ({
            dbDumps
        })
    }),
    graphql(gqlUpdate, {
        props: ({mutate}) => ({
            createDbDump: () => {
                return mutate({
                    variables: {type: 'full'},
                    update: (proxy, {data: {createDbDump}}) => {
                        // Read the data from our cache for this query.
                        const data = proxy.readQuery({query: gqlQuery})
                        // Add our note from the mutation to the end.
                        data.dbDumps.results.unshift(createDbDump)
                        // Write our data back to the cache.
                        proxy.writeQuery({query: gqlQuery, data})
                    }

                })
            }
        })
    }),
    graphql(gqlQueryMedia, {
        options() {
            return {
                fetchPolicy: 'cache-and-network'
            }
        },
        props: ({data: {mediaDumps}}) => ({
            mediaDumps
        })
    }),
    graphql(gqlUpdateMedia, {
        props: ({mutate}) => ({
            createMediaDump: () => {
                return mutate({
                    variables: {type: 'full'},
                    update: (proxy, {data: {createMediaDump}}) => {
                        // Read the data from our cache for this query.
                        const data = proxy.readQuery({query: gqlQueryMedia})
                        // Add our note from the mutation to the end.
                        data.mediaDumps.results.unshift(createMediaDump)
                        // Write our data back to the cache.
                        proxy.writeQuery({query: gqlQueryMedia, data})
                    }

                })
            }
        })
    })
)
(DbDumpContainer)

/**
 * Connect the component to
 * the Redux store.
 */
export default DbDumpContainerWithGql
<|endoftext|>


<commit_before>"use strict";

angular.module('arethusa').controller('MorphImportCtrl', [
  '$scope',
  'plugins',
  'latinAttrs',
  'greekAttrs',
  'configurator',
  'MORPH_IMPORTS',
  '$http',
  function($scope, plugins, latinAttrs, greekAttrs,
           configurator, MORPH_IMPORTS, $http) {
    var morph;
    var attrs = {
      lat: latinAttrs.data,
      grc: greekAttrs.data
    };

    var config = configurator.configuration;

    function useLanguage(attr) {
      config.plugins.morph = attrs[attr];
      $scope.usedLanguage = attr;
    }

    // Use a starting value so the morph service can load properly
    useLanguage('lat');

    var data = "Caesar,Caesar,n-s---mn-,lfdm";

    plugins.start(['morph']).then(function() {
      morph = plugins.get('morph');
      $scope.ready = true;
    });

    $scope.files = MORPH_IMPORTS;

    function loadForms(data, filter) {
      var lines = data.split('\n');
      for (var i=0; i < lines.length; i++) {
        // Fields are organized like
        // Form       - Lemma   - Postag   - User
        // Caesaris   - Caesar  - .......  - ....
        var fields = lines[i].split(',');
        var form = {
          lemma: fields[1],
          postag: fields[2],
          origin: fields[3]
        };

        morph.postagToAttributes(form);
        morph.addToLocalStorage(fields[0], form);
      }
    }

    $scope.loadFile = function(fileObj) {
      $http.get(fileObj.route).then(function(res) {
        useLanguage(fileObj.language);
        loadForms(res.data);
      });

    };
  }
]);
<commit_msg>Postpone user handling<commit_after>"use strict";

angular.module('arethusa').controller('MorphImportCtrl', [
  '$scope',
  'plugins',
  'latinAttrs',
  'greekAttrs',
  'configurator',
  'MORPH_IMPORTS',
  '$http',
  function($scope, plugins, latinAttrs, greekAttrs,
           configurator, MORPH_IMPORTS, $http) {
    var morph;
    var attrs = {
      lat: latinAttrs.data,
      grc: greekAttrs.data
    };

    var config = configurator.configuration;

    function useLanguage(attr) {
      config.plugins.morph = attrs[attr];
      $scope.usedLanguage = attr;
    }

    // Use a starting value so the morph service can load properly
    useLanguage('lat');

    var data = "Caesar,Caesar,n-s---mn-,lfdm";

    plugins.start(['morph']).then(function() {
      morph = plugins.get('morph');
      $scope.ready = true;
    });

    $scope.files = MORPH_IMPORTS;

    var userCache = {
    };

    function loadForms(data, filter) {
      var lines = data.split('\n');
      for (var i=0; i < lines.length; i++) {
        // Fields are organized like
        // Form       - Lemma   - Postag   - User
        // Caesaris   - Caesar  - .......  - ....
        var fields = lines[i].split(',');
        var form = {
          lemma: fields[1],
          postag: fields[2]
          //origin: fields[3]
        };

        morph.postagToAttributes(form);
        morph.addToLocalStorage(fields[0], form);
      }
    }

    $scope.loadFile = function(fileObj) {
      $http.get(fileObj.route).then(function(res) {
        useLanguage(fileObj.language);
        loadForms(res.data);
      });
    };
  }
]);
<|endoftext|>


<commit_before>var KEY_AUTH = 'overlay_networks_group1__auth';

new Vue({
	el: '#app',
	data: {
		isLoading: true,
		auth: {
			authenticated: false,
			username: '',
			contacts: []
		},
		login: {
			username: ''
		},
		contact: {
			contactInput: '',
			contactInputDisabled: false
		},
		chat: {
			messageInput: '',
			messageInputDisabled: false,
			selectedContact: '',
			messages: { }
		},
	},
	computed: {
		currentChatMessages: function() {
			return this.chat.messages[this.chat.selectedContact];
		}
	},
	methods: {
		submitLogin: function() {
			var App = this;

			return App.$http.post('/rest/login', { name: App.login.username }).then(function() {
				App.auth = {
					authenticated: true,
					username: App.login.username,
					contacts: [],
				};
				setAuthToStorage(App.auth);
				App.login.username = '';
			}, function() {
				alert("wrong password");
				App.login.username = '';
			});
		},
		autoLogin: function(storageObject) {
			var App = this;

			return App.$http.post('/rest/login', { name: storageObject.username }).then(function() {
				App.auth = {
					authenticated: true,
					username: storageObject.username,
					contacts: []
				};
				setAuthToStorage(App.auth);

				var contactSet = [];
				for(var i=0; i < storageObject.contacts.length; i++) {
					contactSet.push({
						name: storageObject.contacts[i]
					});
				}

				return App.$http.post('/rest/new-contact-list', contactSet).then(function() {
					App.auth.contacts = storageObject.contacts;
					for(var j=0; j < App.auth.contacts.length; j++) {
						App.chat.messages[App.auth.contacts[j]] = [];
					}

					setAuthToStorage(App.auth);
				}, function() {
					App.auth.contacts = [];
					setAuthToStorage(App.auth);
				});

			}, function() {
				removeAuthFromStorage();
			});
		},
		addContact: function() {
			var App = this;

			// user is already in list
			if(Object.keys(App.chat.messages).indexOf(App.contact.contactInput) !== -1) {
				return;
			}

			App.contact.contactInputDisabled = true;
			App.$http.post('/rest/new-contact', { name: App.contact.contactInput }).then(function() {

				App.auth.contacts.push(App.contact.contactInput);
				setAuthToStorage(App.auth);

				App.chat.messages[App.contact.contactInput] = [];
				App.contact.contactInput = '';
				App.contact.contactInputDisabled = false;

				// set focus back to input field
				window.setTimeout(function() {
					App.$refs.addContactInput.focus();
				});
			}, function() {
				App.contact.contactInputDisabled = false;
			});

		},
		submitMessage: function() {
			var App = this;
			var selectedContact = App.chat.selectedContact;
			var messageInput =  App.chat.messageInput

			App.chat.messageInputDisabled = true;
			App.$http.post('/rest/send-message', { message: messageInput, receiver: { name: selectedContact }}).then(function(response) {
				var messageId = response.body.messageId;

				App.chat.messages[selectedContact].push({
					messageId: messageId,
					content: messageInput,
					isOwnMessage: true,
					approved: false
				});

				App.chat.messageInput = '';
				App.chat.messageInputDisabled = false;

				// set focus back to input field
				window.setTimeout(function() {
					App.$refs.messageInput.focus();
				});

			}, function() {

				App.messageInputDisabled = false;
			});
		},
		logout: function() {
			removeAuthFromStorage();
			this.auth.authenticated = false;
			this.auth.username = '';
			this.auth.contacts = [];
		}
	},
	mounted: function() {
		var App = this;

		var storageObject = getAuthFromStorage();
		if(storageObject && storageObject.authenticated && storageObject.username && storageObject.contacts) {
			App.autoLogin(storageObject).then(function() {
				App.isLoading = false;
			}, function () {
				removeAuthFromStorage();
				App.isLoading = false;
			});
		} else {
			removeAuthFromStorage();
			App.isLoading = false;
		}
	}
});

function getAuthFromStorage() {
	var storageObject = window.localStorage.getItem(KEY_AUTH);
	if (storageObject) {
		var result = null;
		try { result = JSON.parse(storageObject); } catch (error) { result = null; }
		return result;
	} else {
		return null;
	}
}
function setAuthToStorage(auth) {
	window.localStorage.setItem(KEY_AUTH, JSON.stringify(auth));
}
function removeAuthFromStorage() {
	window.localStorage.removeItem(KEY_AUTH);
}

/* Sockets:
 *
 *
var stompClient = null;

function setConnected(connected) {
	$("#connect").prop("disabled", connected);
	$("#disconnect").prop("disabled", !connected);
	if (connected) {
		$("#conversation").show();
	}
	else {
		$("#conversation").hide();
	}
	$("#greetings").html("");
}

function connect() {
	var socket = new SockJS('/websocket-connection');
	stompClient = Stomp.over(socket);
	stompClient.connect({}, function (frame) {
		setConnected(true);
		console.log('Connected: ' + frame);
		stompClient.subscribe('/topic/greetings', function (greeting) {
			showGreeting(JSON.parse(greeting.body).content);
		});
	});
}

function disconnect() {
	if (stompClient != null) {
		stompClient.disconnect();
	}
	setConnected(false);
	console.log("Disconnected");
}

function sendName() {
	stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()}));
}

function showGreeting(message) {
	$("#greetings").append("
" + message + "
");
}

$(function () {
	$("form").on('submit', function (e) {
		e.preventDefault();
	});
	$( "#connect" ).click(function() { connect(); });
	$( "#disconnect" ).click(function() { disconnect(); });
	$( "#send" ).click(function() { sendName(); });
});

*/<commit_msg>improve logout functionality<commit_after>var KEY_AUTH = 'overlay_networks_group1__auth';

new Vue({
	el: '#app',
	data: {
		isLoading: true,
		auth: {
			authenticated: false,
			username: '',
			contacts: []
		},
		login: {
			username: ''
		},
		contact: {
			contactInput: '',
			contactInputDisabled: false
		},
		chat: {
			messageInput: '',
			messageInputDisabled: false,
			selectedContact: '',
			messages: { }
		},
	},
	computed: {
		currentChatMessages: function() {
			return this.chat.messages[this.chat.selectedContact];
		}
	},
	methods: {
		submitLogin: function() {
			var App = this;

			return App.$http.post('/rest/login', { name: App.login.username }).then(function() {
				App.auth = {
					authenticated: true,
					username: App.login.username,
					contacts: [],
				};
				setAuthToStorage(App.auth);
				App.login.username = '';
			}, function() {
				alert("wrong password");
				App.login.username = '';
			});
		},
		autoLogin: function(storageObject) {
			var App = this;

			return App.$http.post('/rest/login', { name: storageObject.username }).then(function() {
				App.auth = {
					authenticated: true,
					username: storageObject.username,
					contacts: []
				};
				setAuthToStorage(App.auth);

				var contactSet = [];
				for(var i=0; i < storageObject.contacts.length; i++) {
					contactSet.push({
						name: storageObject.contacts[i]
					});
				}

				return App.$http.post('/rest/new-contact-list', contactSet).then(function() {
					App.auth.contacts = storageObject.contacts;
					for(var j=0; j < App.auth.contacts.length; j++) {
						App.chat.messages[App.auth.contacts[j]] = [];
					}

					setAuthToStorage(App.auth);
				}, function() {
					App.auth.contacts = [];
					setAuthToStorage(App.auth);
				});

			}, function() {
				removeAuthFromStorage();
			});
		},
		addContact: function() {
			var App = this;

			// user is already in list
			if(Object.keys(App.chat.messages).indexOf(App.contact.contactInput) !== -1) {
				return;
			}

			App.contact.contactInputDisabled = true;
			App.$http.post('/rest/new-contact', { name: App.contact.contactInput }).then(function() {

				App.auth.contacts.push(App.contact.contactInput);
				setAuthToStorage(App.auth);

				App.chat.messages[App.contact.contactInput] = [];
				App.contact.contactInput = '';
				App.contact.contactInputDisabled = false;

				// set focus back to input field
				window.setTimeout(function() {
					App.$refs.addContactInput.focus();
				});
			}, function() {
				App.contact.contactInputDisabled = false;
			});

		},
		submitMessage: function() {
			var App = this;
			var selectedContact = App.chat.selectedContact;
			var messageInput =  App.chat.messageInput

			App.chat.messageInputDisabled = true;
			App.$http.post('/rest/send-message', { message: messageInput, receiver: { name: selectedContact }}).then(function(response) {
				var messageId = response.body.messageId;

				App.chat.messages[selectedContact].push({
					messageId: messageId,
					content: messageInput,
					isOwnMessage: true,
					approved: false
				});

				App.chat.messageInput = '';
				App.chat.messageInputDisabled = false;

				// set focus back to input field
				window.setTimeout(function() {
					App.$refs.messageInput.focus();
				});

			}, function() {

				App.messageInputDisabled = false;
			});
		},
		logout: function() {
			removeAuthFromStorage();
			window.location.reload();
		}
	},
	mounted: function() {
		var App = this;

		var storageObject = getAuthFromStorage();
		if(storageObject && storageObject.authenticated && storageObject.username && storageObject.contacts) {
			App.autoLogin(storageObject).then(function() {
				App.isLoading = false;
			}, function () {
				removeAuthFromStorage();
				App.isLoading = false;
			});
		} else {
			removeAuthFromStorage();
			App.isLoading = false;
		}
	}
});

function getAuthFromStorage() {
	var storageObject = window.localStorage.getItem(KEY_AUTH);
	if (storageObject) {
		var result = null;
		try { result = JSON.parse(storageObject); } catch (error) { result = null; }
		return result;
	} else {
		return null;
	}
}
function setAuthToStorage(auth) {
	window.localStorage.setItem(KEY_AUTH, JSON.stringify(auth));
}
function removeAuthFromStorage() {
	window.localStorage.removeItem(KEY_AUTH);
}

/* Sockets:
 *
 *
var stompClient = null;

function setConnected(connected) {
	$("#connect").prop("disabled", connected);
	$("#disconnect").prop("disabled", !connected);
	if (connected) {
		$("#conversation").show();
	}
	else {
		$("#conversation").hide();
	}
	$("#greetings").html("");
}

function connect() {
	var socket = new SockJS('/websocket-connection');
	stompClient = Stomp.over(socket);
	stompClient.connect({}, function (frame) {
		setConnected(true);
		console.log('Connected: ' + frame);
		stompClient.subscribe('/topic/greetings', function (greeting) {
			showGreeting(JSON.parse(greeting.body).content);
		});
	});
}

function disconnect() {
	if (stompClient != null) {
		stompClient.disconnect();
	}
	setConnected(false);
	console.log("Disconnected");
}

function sendName() {
	stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()}));
}

function showGreeting(message) {
	$("#greetings").append("
" + message + "
");
}

$(function () {
	$("form").on('submit', function (e) {
		e.preventDefault();
	});
	$( "#connect" ).click(function() { connect(); });
	$( "#disconnect" ).click(function() { disconnect(); });
	$( "#send" ).click(function() { sendName(); });
});

*/<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { Row, Col, Button } from 'react-bootstrap';

import UploadFileModal from './UploadFileModal';
import SubscribersTable from '../../components/lists/SubscribersTable';
import ErrorsList from '../../components/lists/ErrorsList';

import previewCSV from '../../utils/subscriberListParsers/parseSubscriberList';

function mapStateToProps(state) {
  // State reducer @ state.createList
  return { isPosting: state.createList.isPosting };
}

@connect(mapStateToProps, null)
export default class ImportCSV extends Component {

  static propTypes = {
    handleCSVSubmit: PropTypes.func.isRequired,
    isPosting: PropTypes.bool.isRequired,
    notification: PropTypes.func.isRequired
  }

  state = {
    file: null,
    fields: null,
    subscribers: null,
    errors: null
  }

  componentWillReceiveProps(props) {
    if (!props.isPosting) {
      this.cancelImport();
    }
  }

  handleErrorWithFile(err) { // Err is a string informing user of an issue with the csv
    this.props.notification({ // Ref https://github.com/pburtchaell/react-notification & https://github.com/pburtchaell/react-notification/blob/master/src/notification.js
      message: err
    });
  }

  handleNewFile(file) {

    const callback = (results) => {
      if (!results.meta.fields.some(field => field.toLowerCase() === 'email')) { // Check if any header field is labeled email
        this.handleErrorWithFile('Please ensure the CSV file contains at least one column field labeled "email" (check the first row)');
      } else {

        const errors = results.errors;
        const subscribers = results.data;
        const fields = results.meta.fields;
        // If errors were encountered don't show the
        // dodgy csv/subscribers list to users
        const errorIsNotSignificant = (errors) => { // Check that thrown errors are actually errors
          if (errors.length) {
            return errors.every(i => { // Put allowed errors below
              return i.code === 'UndetectableDelimiter'; // UndetectableDelimiter can occur when the CSV files has one column, as no delimiter is present
            });
          } else {
            return true;
          }
        };

        if (errorIsNotSignificant(errors)) {
          this.setState({file: file, subscribers: subscribers, fields: fields});
        } else {
          this.setState({errors: errors});
        }

      }
    };

    previewCSV(file, callback);
  }

  handleSubmit(e) {
    e.preventDefault();

    this.props.handleCSVSubmit(this.state.file, this.state.fields);
  }

  deleteSubscriber(subscriberId) {
    // Deletes the subscriber at subscriberId then
    // updates state accordingly
    let newSubscribers = this.state.subscribers;

    newSubscribers.splice(subscriberId, 1);

    this.setState({subscribers: newSubscribers});
  }

  cancelImport() {
    this.setState({subscribers: null, fields: null, errors: null});
  }

  render() {
    return (

        { this.props.isPosting
          ? <Row>
              <Col md={12}>

Your CSV file is uploading ...

Loading...

              </Col>
            </Row>

          : <Row>
            <Col md={12}>

Import CSV

 Please ensure the first row of the CSV file contains column names. This row must contain an email header.

                  {(!this.state.subscribers && !this.state.errors) && <UploadFileModal handleNewFile={this.handleNewFile.bind(this)}/>}

                  <ErrorsList errors={this.state.errors}/>

                  <SubscribersTable fields={this.state.fields} subscribers={this.state.subscribers} deleteSubscriber={this.deleteSubscriber.bind(this)}/>

                  {(this.state.subscribers || this.state.errors) && <Button onClick={this.cancelImport.bind(this)}>Cancel</Button>}

                  {this.state.subscribers && <Button className="pull-right" bsStyle="primary" type="submit" onClick={this.handleSubmit.bind(this)}>Submit</Button>}

            </Col>
          </Row>
        }

    );
  }
}
<commit_msg>Fix: Remove red warning icon from ImportCsv container<commit_after>import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { Row, Col, Button } from 'react-bootstrap';

import UploadFileModal from './UploadFileModal';
import SubscribersTable from '../../components/lists/SubscribersTable';
import ErrorsList from '../../components/lists/ErrorsList';

import previewCSV from '../../utils/subscriberListParsers/parseSubscriberList';

function mapStateToProps(state) {
  // State reducer @ state.createList
  return { isPosting: state.createList.isPosting };
}

@connect(mapStateToProps, null)
export default class ImportCSV extends Component {

  static propTypes = {
    handleCSVSubmit: PropTypes.func.isRequired,
    isPosting: PropTypes.bool.isRequired,
    notification: PropTypes.func.isRequired
  }

  state = {
    file: null,
    fields: null,
    subscribers: null,
    errors: null
  }

  componentWillReceiveProps(props) {
    if (!props.isPosting) {
      this.cancelImport();
    }
  }

  handleErrorWithFile(err) { // Err is a string informing user of an issue with the csv
    this.props.notification({ // Ref https://github.com/pburtchaell/react-notification & https://github.com/pburtchaell/react-notification/blob/master/src/notification.js
      message: err
    });
  }

  handleNewFile(file) {

    const callback = (results) => {
      if (!results.meta.fields.some(field => field.toLowerCase() === 'email')) { // Check if any header field is labeled email
        this.handleErrorWithFile('Please ensure the CSV file contains at least one column field labeled "email" (check the first row)');
      } else {

        const errors = results.errors;
        const subscribers = results.data;
        const fields = results.meta.fields;
        // If errors were encountered don't show the
        // dodgy csv/subscribers list to users
        const errorIsNotSignificant = (errors) => { // Check that thrown errors are actually errors
          if (errors.length) {
            return errors.every(i => { // Put allowed errors below
              return i.code === 'UndetectableDelimiter'; // UndetectableDelimiter can occur when the CSV files has one column, as no delimiter is present
            });
          } else {
            return true;
          }
        };

        if (errorIsNotSignificant(errors)) {
          this.setState({file: file, subscribers: subscribers, fields: fields});
        } else {
          this.setState({errors: errors});
        }

      }
    };

    previewCSV(file, callback);
  }

  handleSubmit(e) {
    e.preventDefault();

    this.props.handleCSVSubmit(this.state.file, this.state.fields);
  }

  deleteSubscriber(subscriberId) {
    // Deletes the subscriber at subscriberId then
    // updates state accordingly
    let newSubscribers = this.state.subscribers;

    newSubscribers.splice(subscriberId, 1);

    this.setState({subscribers: newSubscribers});
  }

  cancelImport() {
    this.setState({subscribers: null, fields: null, errors: null});
  }

  render() {
    return (

        { this.props.isPosting
          ? <Row>
              <Col md={12}>

Your CSV file is uploading ...

Loading...

              </Col>
            </Row>

          : <Row>
            <Col md={12}>

Import CSV

Please ensure the first row of the CSV file contains column names. This row must contain an email header.

                  {(!this.state.subscribers && !this.state.errors) && <UploadFileModal handleNewFile={this.handleNewFile.bind(this)}/>}

                  <ErrorsList errors={this.state.errors}/>

                  <SubscribersTable fields={this.state.fields} subscribers={this.state.subscribers} deleteSubscriber={this.deleteSubscriber.bind(this)}/>

                  {(this.state.subscribers || this.state.errors) && <Button onClick={this.cancelImport.bind(this)}>Cancel</Button>}

                  {this.state.subscribers && <Button className="pull-right" bsStyle="primary" type="submit" onClick={this.handleSubmit.bind(this)}>Submit</Button>}

            </Col>
          </Row>
        }

    );
  }
}
<|endoftext|>


<commit_before>/*global gyrocopter */
(function () {
'use strict';

/**
 * The main controller for the app.
 */
gyrocopter.controller('gyrocopterCtrl', function mainCtrl($scope) {

  $scope.platforms = [
  {
    'name': 'W3C',
    'browsers': [
      {
        'name': 'Specification',
        'id': 'w3c-specification',
        'rotation': {
          'alpha': {
            'min': 0,
            'max': 360,
            'zero': 0
          },
          'beta': {
            'min': -180,
            'max': 180,
            'zero': 0
          },
          'gamma': {
            'min': -90,
            'max': 90,
            'zero': 0
          }
        }
      }
    ]
  },
  {
    'name': 'iOS',
    'browsers': [
      {
        'name': 'Safari',
        'id': 'ios-safari',
        'rotation': {
          'alpha': {
            'min': 0,
            'max': 360,
            'zero': 90
          },
          'beta': {
            'min': -90,
            'max': 90,
            'zero': 0
          },
          'gamma': {
            'min': 180,
            'max': -180,
            'zero': 0
          }
        }
      }
    ]
  },
  {
    'name': 'Android',
    'browsers': [
      {
        'name': 'Chrome',
        'id': 'android-chrome',
        'rotation': {
          'alpha': {
            'min': 0,
            'max': 360,
            'zero': 0
          },
          'beta': {
            'min': -90,
            'max': 90,
            'zero': 0
          },
          'gamma': {
            'min': 270,
            'max': -90,
            'zero': 0
          }
        }
      },
      {
        'name': 'Stock Browser',
        'id': 'android-stock',
        'rotation': {
          'alpha': {
            'min': 0,
            'max': 360,
            'zero': 270
          },
          'beta': {
            'min': -90,
            'max': 90,
            'zero': 0
          },
          'gamma': {
            'min': 270,
            'max': -90,
            'zero': 0
          }
        }
      },
      {
        'name': 'Firefox',
        'id': 'android-firefox',
        'rotation': {
          'alpha': {
            'min': 0,
            'max': 360,
            'zero': 0,
            'reverse': true
          },
          'beta': {
            'min': 180,
            'max': -180,
            'zero': 0,
            'reverse': true
          },
          'gamma': {
            'min': -90,
            'max': 90,
            'zero': 0,
            'reverse': true
          }
        }
      }
    ]
  }

  ];

  $scope.selected = $scope.platforms[0].browsers[0];
  $scope.selectBrowser = function(browser){
    $scope.selected = browser;
    $scope.setDefaultRotation();
  };

  $scope.setDefaultRotation = function(){
    $scope.alpha = 0;
    $scope.beta = 90;
    $scope.gamma = 0;

    $scope.rotateDevice();
  }

  /**
   * Compute methods
   */

  $scope.getAlphaRotation = function(){
    var alpha = $scope.alpha;
    return alpha;
  };

  $scope.getBetaRotation = function(){
    var beta = $scope.beta - 180;
    var range = Math.abs($scope.selected.rotation.beta.max - $scope.selected.rotation.beta.min);

    if (range === 180) {
      if (beta > 90) {
        return 90 - beta + 90;
      }
      if (beta < -90) {
        return -1 * (90 + beta + 90);
      }
    }

    return beta;
  };

  $scope.getGammaRotation = function(){
    var gamma = $scope.gamma;
    return gamma;
  };

  /**
   * Rotation methods
   */

  $scope.rotateDevice = function(){
    var alpha = $scope.alpha;
    var beta = $scope.beta;
    var gamma = $scope.gamma;
    var alphaMult = $scope.selected.rotation.alpha.reverse ? -1 : 1;
    var betaMult = $scope.selected.rotation.beta.reverse ? -1 : 1;
    var gammaMult = $scope.selected.rotation.gamma.reverse ? -1 : 1;

    $scope.css = {};

    var a = alpha * alphaMult;
    var b = (- beta + 90 - 180) * betaMult;
    var c = (gamma) * gammaMult;

    $scope.css['transform'] = 'rotateX(' + b + 'deg)' + 'rotateY(' + c + 'deg)' + 'rotateZ(' + a + 'deg)';
    $scope.css['-webkit-transform'] = 'rotateX(' + b + 'deg)' + 'rotateY(' + c + 'deg)' + 'rotateZ(' + a + 'deg)';
  }

  // $scope.rotate = function(alpha, beta, gamma){
  //   $scope.css = {};

  //   var a = 360 - alpha;
  //   var b = - beta + 90;
  //   var c = gamma;

  //   $scope.css['transform'] = 'rotateX(' + b + 'deg)' + 'rotateY(' + c + 'deg)' + 'rotateZ(' + a + 'deg)';
  //   $scope.css['-webkit-transform'] = 'rotateX(' + b + 'deg)' + 'rotateY(' + c + 'deg)' + 'rotateZ(' + a + 'deg)';
  // };

  chrome.storage.local.get(['alpha', 'beta', 'gamma'], function(storage){
    console.log(storage);
    $scope.alpha = Number(storage.alpha) || 0;
    $scope.beta = Number(storage.beta) || 90;
    $scope.gamma = Number(storage.gamma) || 0;

    $scope.rotateDevice();
    $scope.$apply();

    // $scope.orientateDevice($scope.alpha, $scope.beta, $scope.gamma);
  });

  /**
   * Dispatches a DeviceOrientation Event
   * @param  {double?} alpha    Rotate the device frame around its z axis by alpha degrees, with alpha in [0, 360).
   * @param  {double?} beta     Rotate the device frame around its x axis by beta degrees, with beta in [-180, 180).
   * @param  {double?} gamma    Rotate the device frame around its y axis by gamma degrees, with gamma in [-90, 90).
   * @param  {boolean} absolute Indicates absolute values for the three angles or relative to some arbitrary orientation.
   */

  $scope.orientateDevice = function(alpha, beta, gamma, absolute){
    console.log(alpha, beta, gamma);

    var a = alpha;
    var b = beta;
    var c = $scope.computeGamma(gamma);
    absolute = true;
    // var event = document.createEvent("DeviceOrientationEvent");
    // event.initDeviceOrientationEvent("deviceorientation", false, false, alpha, beta, gamma, absolute);
    // window.dispatchEvent(event);

    var js = '';
    js += 'var event = document.createEvent("DeviceOrientationEvent");';
    js += 'event.initDeviceOrientationEvent("deviceorientation", false, false, ' + a + ', ' + b + ', ' + c + ', ' + absolute + ');';
    js += 'window.dispatchEvent(event);';

    chrome.tabs.executeScript({
      code: js
    });

    $scope.rotate($scope.alpha, $scope.beta, $scope.gamma);
  };

  $scope.saveRotation = function(){
    chrome.storage.local.set({'alpha': $scope.alpha, "beta": $scope.beta, "gamma": $scope.gamma}, function() {
      console.log('saved');
    });
  };

  $scope.reset = function(){
    $scope.alpha = 0;
    $scope.beta = 90;
    $scope.gamma = 0;
    $scope.orientateDevice($scope.alpha, $scope.beta, $scope.gamma);
    $scope.saveRotation();
  };

  $scope.stepper = 0.5;

  $scope.compute = function(next, direction, min, max){
    if (direction === 'left') {
      next -= $scope.stepper;
    }
    else {
      next += $scope.stepper;
    }

    if (next < min) {
      next = max - $scope.stepper;
    }
    else if (next >= max) {
      next = min;
    }

    return next;
  };

  $scope.computeGamma = function(gamma){
    if (gamma > 90) {
      return Number(gamma) - 180;
    }
    else if (gamma < -90) {
      return Number(gamma) + 180;
    }
    else {
      return Number(gamma);
    }
  };

  $scope.stepAlpha = function(direction){
    var next =  $scope.compute(Number($scope.alpha), direction, 0, 360);
    $scope.alpha = next;
    $scope.orientateDevice($scope.alpha, $scope.beta, $scope.gamma);
    $scope.saveRotation();
  };

  $scope.stepBeta = function(direction){
    var next =  $scope.compute(Number($scope.beta), direction, -180, 180);
    $scope.beta = next;
    $scope.orientateDevice($scope.alpha, $scope.beta, $scope.gamma);
    $scope.saveRotation();
  };

  $scope.stepGamma = function(direction){
    var next =  $scope.compute(Number($scope.gamma), direction, -180, 180);
    $scope.gamma = next;
    $scope.orientateDevice($scope.alpha, $scope.beta, $scope.gamma);
    $scope.saveRotation();
  };
});

})();
<commit_msg>beta rotation - firefox support<commit_after>/*global gyrocopter */
(function () {
'use strict';

/**
 * The main controller for the app.
 */
gyrocopter.controller('gyrocopterCtrl', function mainCtrl($scope) {

  $scope.platforms = [
  {
    'name': 'W3C',
    'browsers': [
      {
        'name': 'Specification',
        'id': 'w3c-specification',
        'rotation': {
          'alpha': {
            'min': 0,
            'max': 360,
            'zero': 0
          },
          'beta': {
            'min': -180,
            'max': 180,
            'zero': 0
          },
          'gamma': {
            'min': -90,
            'max': 90,
            'zero': 0
          }
        }
      }
    ]
  },
  {
    'name': 'iOS',
    'browsers': [
      {
        'name': 'Safari',
        'id': 'ios-safari',
        'rotation': {
          'alpha': {
            'min': 0,
            'max': 360,
            'zero': 90
          },
          'beta': {
            'min': -90,
            'max': 90,
            'zero': 0
          },
          'gamma': {
            'min': 180,
            'max': -180,
            'zero': 0
          }
        }
      }
    ]
  },
  {
    'name': 'Android',
    'browsers': [
      {
        'name': 'Chrome',
        'id': 'android-chrome',
        'rotation': {
          'alpha': {
            'min': 0,
            'max': 360,
            'zero': 0
          },
          'beta': {
            'min': -90,
            'max': 90,
            'zero': 0
          },
          'gamma': {
            'min': 270,
            'max': -90,
            'zero': 0
          }
        }
      },
      {
        'name': 'Stock Browser',
        'id': 'android-stock',
        'rotation': {
          'alpha': {
            'min': 0,
            'max': 360,
            'zero': 270
          },
          'beta': {
            'min': -90,
            'max': 90,
            'zero': 0
          },
          'gamma': {
            'min': 270,
            'max': -90,
            'zero': 0
          }
        }
      },
      {
        'name': 'Firefox',
        'id': 'android-firefox',
        'rotation': {
          'alpha': {
            'min': 0,
            'max': 360,
            'zero': 0,
            'reverse': true
          },
          'beta': {
            'min': 180,
            'max': -180,
            'zero': 0,
            'reverse': true
          },
          'gamma': {
            'min': -90,
            'max': 90,
            'zero': 0,
            'reverse': true
          }
        }
      }
    ]
  }

  ];

  $scope.selected = $scope.platforms[0].browsers[0];
  $scope.selectBrowser = function(browser){
    $scope.selected = browser;
    $scope.setDefaultRotation();
  };

  $scope.setDefaultRotation = function(){
    $scope.alpha = 0;
    $scope.beta = 90;
    $scope.gamma = 0;

    $scope.rotateDevice();
  }

  /**
   * Compute methods
   */

  $scope.getAlphaRotation = function(){
    var alpha = $scope.alpha;
    return alpha;
  };

  $scope.getBetaRotation = function(){
    var beta = $scope.beta - 180;
    var range = Math.abs($scope.selected.rotation.beta.max - $scope.selected.rotation.beta.min);

    if (range === 180) {
      if (beta > 90) {
        return (90 - beta + 90);
      }
      if (beta < -90) {
        return -1 * (90 + beta + 90);
      }
    }

    return beta;
  };

  $scope.getGammaRotation = function(){
    var gamma = $scope.gamma;
    return gamma;
  };

  /**
   * Rotation methods
   */

  $scope.rotateDevice = function(){
    var alpha = $scope.alpha;
    var beta = $scope.beta;
    var gamma = $scope.gamma;
    var alphaMult = $scope.selected.rotation.alpha.reverse ? -1 : 1;
    var betaMult = $scope.selected.rotation.beta.reverse ? -1 : 1;
    var gammaMult = $scope.selected.rotation.gamma.reverse ? -1 : 1;

    $scope.css = {};

    var a = alpha * alphaMult;
    var b = betaMult > 0 ? (- beta + 90 - 180) * betaMult : (- beta + 90 - 180) * betaMult - 180;
    var c = (gamma) * gammaMult;

    $scope.css['transform'] = 'rotateX(' + b + 'deg)' + 'rotateY(' + c + 'deg)' + 'rotateZ(' + a + 'deg)';
    $scope.css['-webkit-transform'] = 'rotateX(' + b + 'deg)' + 'rotateY(' + c + 'deg)' + 'rotateZ(' + a + 'deg)';
  }

  // $scope.rotate = function(alpha, beta, gamma){
  //   $scope.css = {};

  //   var a = 360 - alpha;
  //   var b = - beta + 90;
  //   var c = gamma;

  //   $scope.css['transform'] = 'rotateX(' + b + 'deg)' + 'rotateY(' + c + 'deg)' + 'rotateZ(' + a + 'deg)';
  //   $scope.css['-webkit-transform'] = 'rotateX(' + b + 'deg)' + 'rotateY(' + c + 'deg)' + 'rotateZ(' + a + 'deg)';
  // };

  chrome.storage.local.get(['alpha', 'beta', 'gamma'], function(storage){
    console.log(storage);
    $scope.alpha = Number(storage.alpha) || 0;
    $scope.beta = Number(storage.beta) || 90;
    $scope.gamma = Number(storage.gamma) || 0;

    $scope.rotateDevice();
    $scope.$apply();

    // $scope.orientateDevice($scope.alpha, $scope.beta, $scope.gamma);
  });

  /**
   * Dispatches a DeviceOrientation Event
   * @param  {double?} alpha    Rotate the device frame around its z axis by alpha degrees, with alpha in [0, 360).
   * @param  {double?} beta     Rotate the device frame around its x axis by beta degrees, with beta in [-180, 180).
   * @param  {double?} gamma    Rotate the device frame around its y axis by gamma degrees, with gamma in [-90, 90).
   * @param  {boolean} absolute Indicates absolute values for the three angles or relative to some arbitrary orientation.
   */

  $scope.orientateDevice = function(alpha, beta, gamma, absolute){
    console.log(alpha, beta, gamma);

    var a = alpha;
    var b = beta;
    var c = $scope.computeGamma(gamma);
    absolute = true;
    // var event = document.createEvent("DeviceOrientationEvent");
    // event.initDeviceOrientationEvent("deviceorientation", false, false, alpha, beta, gamma, absolute);
    // window.dispatchEvent(event);

    var js = '';
    js += 'var event = document.createEvent("DeviceOrientationEvent");';
    js += 'event.initDeviceOrientationEvent("deviceorientation", false, false, ' + a + ', ' + b + ', ' + c + ', ' + absolute + ');';
    js += 'window.dispatchEvent(event);';

    chrome.tabs.executeScript({
      code: js
    });

    $scope.rotate($scope.alpha, $scope.beta, $scope.gamma);
  };

  $scope.saveRotation = function(){
    chrome.storage.local.set({'alpha': $scope.alpha, "beta": $scope.beta, "gamma": $scope.gamma}, function() {
      console.log('saved');
    });
  };

  $scope.reset = function(){
    $scope.alpha = 0;
    $scope.beta = 90;
    $scope.gamma = 0;
    $scope.orientateDevice($scope.alpha, $scope.beta, $scope.gamma);
    $scope.saveRotation();
  };

  $scope.stepper = 0.5;

  $scope.compute = function(next, direction, min, max){
    if (direction === 'left') {
      next -= $scope.stepper;
    }
    else {
      next += $scope.stepper;
    }

    if (next < min) {
      next = max - $scope.stepper;
    }
    else if (next >= max) {
      next = min;
    }

    return next;
  };

  $scope.computeGamma = function(gamma){
    if (gamma > 90) {
      return Number(gamma) - 180;
    }
    else if (gamma < -90) {
      return Number(gamma) + 180;
    }
    else {
      return Number(gamma);
    }
  };

  $scope.stepAlpha = function(direction){
    var next =  $scope.compute(Number($scope.alpha), direction, 0, 360);
    $scope.alpha = next;
    $scope.orientateDevice($scope.alpha, $scope.beta, $scope.gamma);
    $scope.saveRotation();
  };

  $scope.stepBeta = function(direction){
    var next =  $scope.compute(Number($scope.beta), direction, -180, 180);
    $scope.beta = next;
    $scope.orientateDevice($scope.alpha, $scope.beta, $scope.gamma);
    $scope.saveRotation();
  };

  $scope.stepGamma = function(direction){
    var next =  $scope.compute(Number($scope.gamma), direction, -180, 180);
    $scope.gamma = next;
    $scope.orientateDevice($scope.alpha, $scope.beta, $scope.gamma);
    $scope.saveRotation();
  };
});

})();
<|endoftext|>


<commit_before>/**
 * Created by rodriguesc on 03/03/2017.
 */

import url from 'url';

import config from './config';

//mangas from http://mangafox.me/manga
const mangas = doc =>{
  const xpath = '//div[@class=\'manga_list\']/ul/li/a';
  return doc.find(xpath).map(x=>{
    return {
      name: x.text(),
      src:x.attr('href').value()};
  });
};

//const latest = osm => resolveArray(resolveLatest(osm));
//latest from http://mangafox.me/releases
const latest = doc => {
  const xpath = '//dt/span/a[@class=\'chapter\']';
  return doc.find(xpath).map(x=>{
    return {
      name: x.text(),
      src:x.attr('href').value(),
      volume: x.get('following-sibling::text()')
    };
  });
};

//const mangaInfo = osm => resolveObject(resolveMangaInfo(osm));
//info from http://mangafox.me/manga/**
const mangaInfo = doc=>{
  let image = doc.get('//div[@class=\'cover\']/img').attr('src').value();
  let title = doc.get('//div[@class=\'cover\']/img').attr('alt').value();
  let synonyms = doc.get('//div[@id=\'title\']/h3').text().split('; ');
  let released = doc.get('//div[@id=\'title\']/table/tr[2]/td[1]/a').text();
  let authors = [doc.get('//div[@id=\'title\']/table/tr[2]/td[2]/a').text()];
  let artists = [doc.get('//div[@id=\'title\']/table/tr[2]/td[3]/a').text()];
  let genres = doc.find('//div[@id=\'title\']/table/tr[2]/td[4]/a').map(x=>x.text());
  let synopsis = doc.get('//div[@id=\'title\']/p').text();
  let status = doc.get('//div[@id=\'series_info\']/div[@class=\'data\'][1]/span/text()[1]').text().trim().slice(0,-1);
  let ranked = doc.get('//div[@id=\'series_info\']/div[@class=\'data\'][2]/span').text();
  let rating = doc.get('//div[@id=\'series_info\']/div[@class=\'data\'][3]/span').text();
  let scanlators = doc.find('//div[@id=\'series_info\']/div[@class=\'data\'][4]/span/a').map(x=>x.text());

  return {
    image,
    title,
    synonyms,
    released,
    authors,
    artists,
    genres,
    synopsis,
    status,
    ranked,
    rating,
    scanlators
  }
};

//const image = osm => resolveObject(resolveImage(osm));
//images from chapter
const image = html =>{
  const __imgID__ = /src=".*\?token[^"]*".*id=/gmi;
  const __img__ = /src=".*\?token[^"]*/gmi;

  return html.match(__imgID__)[0].match(__img__)[0].slice(5);
};

//const imagesPaths = osm => resolveArray(resolveImagesPaths(osm));
const imagesPaths = doc =>{
  const xpath = '//form[@id=\'top_bar\']/div/div[@class=\'l\']/select/option[position()< last()]';
  return doc.find(xpath)
    .map(x=>url.resolve(config.site,x + '.html'));
}

//const chapters = osm => resolveArray(resolveChapters(osm));
const chapters = doc=>{
  const xpath = '//div[@id=\'chapters\']/ul/li/div//a[@class=\'tips\']';

  return doc.find(xpath)
    .map(x=>{
      return {
        number : x.text(),
        src :x.attr('href').value(),
        volume: x.get('following-sibling::text()').text()
      };
    });
};

const exp = {
  config,

  image,
  imagesPaths,
  latest,
  mangas,
  chapters,
  mangaInfo,
};

export default exp;
<commit_msg>adding error handling to mangafox<commit_after>/**
 * Created by rodriguesc on 03/03/2017.
 */

import url from 'url';

import config from './config';

//mangas from http://mangafox.me/manga
const mangas = doc =>{
  const xpath = '//div[@class=\'manga_list\']/ul/li/a';
  return doc.find(xpath).map(x=>{
    return {
      name: x.text(),
      src:x.attr('href').value()};
  });
};

//const latest = osm => resolveArray(resolveLatest(osm));
//latest from http://mangafox.me/releases
const latest = doc => {
  const xpath = '//dt/span/a[@class=\'chapter\']';
  return doc.find(xpath).map(x=>{
    return {
      name: x.text(),
      src:x.attr('href').value(),
      volume: x.get('following-sibling::text()')
    };
  });
};

//const mangaInfo = osm => resolveObject(resolveMangaInfo(osm));
//info from http://mangafox.me/manga/**
const mangaInfo = doc=>{
  let image = doc.get('//div[@class=\'cover\']/img').attr('src').value();
  let title = doc.get('//div[@class=\'cover\']/img').attr('alt').value();
  let synonyms = doc.get('//div[@id=\'title\']/h3').text().split('; ');
  let released = doc.get('//div[@id=\'title\']/table/tr[2]/td[1]/a').text();
  let authors = [doc.get('//div[@id=\'title\']/table/tr[2]/td[2]/a').text()];
  let artists = [doc.get('//div[@id=\'title\']/table/tr[2]/td[3]/a').text()];
  let genres = doc.find('//div[@id=\'title\']/table/tr[2]/td[4]/a').map(x=>x.text());
  let synopsis = doc.get('//div[@id=\'title\']/p').text();
  let status = doc.get('//div[@id=\'series_info\']/div[@class=\'data\'][1]/span/text()[1]').text().trim().slice(0,-1);
  let ranked = doc.get('//div[@id=\'series_info\']/div[@class=\'data\'][2]/span').text();
  let rating = doc.get('//div[@id=\'series_info\']/div[@class=\'data\'][3]/span').text();
  let scanlators = doc.find('//div[@id=\'series_info\']/div[@class=\'data\'][4]/span/a').map(x=>x.text());

  return {
    image,
    title,
    synonyms,
    released,
    authors,
    artists,
    genres,
    synopsis,
    status,
    ranked,
    rating,
    scanlators
  }
};

//const image = osm => resolveObject(resolveImage(osm));
//images from chapter
const image = html =>{
  const __imgID__ = /src=".*\?token[^"]*".*id=/gmi;
  const __img__ = /src=".*\?token[^"]*/gmi;

  let m = html.match(__imgID__)[0];
  if(!m)
    throw new Error('Image not found');
  m = m.match(__img__)[0]
  if(!m)
    throw new Error('Image not found');

  return m.slice(5);
};

//const imagesPaths = osm => resolveArray(resolveImagesPaths(osm));
const imagesPaths = doc =>{
  const xpath = '//form[@id=\'top_bar\']/div/div[@class=\'l\']/select/option[position()< last()]';
  return doc.find(xpath)
    .map(x=>url.resolve(config.site,x + '.html'));
}

//const chapters = osm => resolveArray(resolveChapters(osm));
const chapters = doc=>{
  const xpath = '//div[@id=\'chapters\']/ul/li/div//a[@class=\'tips\']';

  return doc.find(xpath)
    .map(x=>{
      return {
        number : x.text(),
        src :x.attr('href').value(),
        volume: x.get('following-sibling::text()').text()
      };
    });
};

const exp = {
  config,

  image,
  imagesPaths,
  latest,
  mangas,
  chapters,
  mangaInfo,
};

export default exp;
<|endoftext|>