<commit_before>const app = require('electron').app;

const CONFIG = require('./config');

let BrowserWindow;

module.exports = function (electron) {
  BrowserWindow = electron.BrowserWindow;

  return {
    openMainWindow,
    openLogInWindow,
    closeLogInWindow,
    activateWithNoOpenWindows,
  };
};

const MAIN_WINDOW = 'main';
const AUTH_WINDOW = 'auth';

const ICON = __dirname + '/../static/icon.png';

const isMac = process.platform === 'darwin';

let windows = {};

function openMainWindow() {
  if (!windows[MAIN_WINDOW])
    windows[MAIN_WINDOW] = createMainWindow();
}

function openLogInWindow(url) {
  windows[AUTH_WINDOW] = createLogInWindow(url);
}

function closeLogInWindow() {
  windows[AUTH_WINDOW].close();
}

function createWindow(windowName, url, width, height, icon) {
  const win = new BrowserWindow({
    title: app.getName(),
    width,
    height,
    icon,
    autoHideMenuBar: true,
    minWidth: 780,
    minHeight: 270,
    frame: isMac,
    'title-bar-style': 'hidden-inset',
  });

  if (isMac)
    app.dock.setIcon(icon);

  if (require('electron-is-dev'))
    win.openDevTools();

  win.loadURL(url);
  win.on('closed', onClosed.bind(null, windowName));

  return win;
}

function createMainWindow() {
  const url = 'file://' + __dirname + '/client/index.html';

  return createWindow(
    MAIN_WINDOW,
    url,
    CONFIG.MAIN_WINDOW.WIDTH,
    CONFIG.MAIN_WINDOW.HEIGHT,
    ICON
  );
}

function createLogInWindow(url) {
  return createWindow(
    AUTH_WINDOW,
    url,
    CONFIG.AUTH_WINDOW.WIDTH,
    CONFIG.AUTH_WINDOW.HEIGHT,
    ICON
  );
}

function activateWithNoOpenWindows() {
  if (!windows[MAIN_WINDOW]) {
    windows[MAIN_WINDOW] = createMainWindow();
  }
}

function onClosed(windowName) {
  // dereference the window
  windows[windowName] = null;
}
<commit_msg>Fix mac titleBarStyle window property<commit_after>const app = require('electron').app;

const CONFIG = require('./config');

let BrowserWindow;

module.exports = function (electron) {
  BrowserWindow = electron.BrowserWindow;

  return {
    openMainWindow,
    openLogInWindow,
    closeLogInWindow,
    activateWithNoOpenWindows,
  };
};

const MAIN_WINDOW = 'main';
const AUTH_WINDOW = 'auth';

const ICON = __dirname + '/../static/icon.png';

const isMac = process.platform === 'darwin';

let windows = {};

function openMainWindow() {
  if (!windows[MAIN_WINDOW])
    windows[MAIN_WINDOW] = createMainWindow();
}

function openLogInWindow(url) {
  windows[AUTH_WINDOW] = createLogInWindow(url);
}

function closeLogInWindow() {
  windows[AUTH_WINDOW].close();
}

function createWindow(windowName, url, width, height, icon) {
  const win = new BrowserWindow({
    title: app.getName(),
    width,
    height,
    icon,
    autoHideMenuBar: true,
    minWidth: 780,
    minHeight: 270,
    frame: isMac,
    titleBarStyle: 'hidden-inset',
  });

  if (isMac)
    app.dock.setIcon(icon);

  if (require('electron-is-dev'))
    win.openDevTools();

  win.loadURL(url);
  win.on('closed', onClosed.bind(null, windowName));

  return win;
}

function createMainWindow() {
  const url = 'file://' + __dirname + '/client/index.html';

  return createWindow(
    MAIN_WINDOW,
    url,
    CONFIG.MAIN_WINDOW.WIDTH,
    CONFIG.MAIN_WINDOW.HEIGHT,
    ICON
  );
}

function createLogInWindow(url) {
  return createWindow(
    AUTH_WINDOW,
    url,
    CONFIG.AUTH_WINDOW.WIDTH,
    CONFIG.AUTH_WINDOW.HEIGHT,
    ICON
  );
}

function activateWithNoOpenWindows() {
  if (!windows[MAIN_WINDOW]) {
    windows[MAIN_WINDOW] = createMainWindow();
  }
}

function onClosed(windowName) {
  // dereference the window
  windows[windowName] = null;
}
<|endoftext|>


<commit_before>'use strict';
const fs = require('fs');
const path = require('path');
module.exports = function(environment) {
  let ENV = {
    modulePrefix: 'consul-ui',
    environment,
    rootURL: '/ui/',
    locationType: 'auto',
    EmberENV: {
      FEATURES: {
        // Here you can enable experimental features on an ember canary build
        // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
      },
      EXTEND_PROTOTYPES: {
        // Prevent Ember Data from overriding Date.parse.
        Date: false,
      },
    },
    APP: {
      // Here you can pass flags/options to your application instance
      // when it is created
    },
    resizeServiceDefaults: {
      injectionFactories: ['view', 'controller', 'component'],
    },
  };
  // TODO: These should probably go onto APP
  ENV = Object.assign({}, ENV, {
    CONSUL_UI_DISABLE_REALTIME: typeof process.env.CONSUL_UI_DISABLE_REALTIME !== 'undefined',
    CONSUL_UI_DISABLE_ANCHOR_SELECTION:
      typeof process.env.CONSUL_UI_DISABLE_ANCHOR_SELECTION !== 'undefined',
    CONSUL_COPYRIGHT_YEAR: (function(val) {
      if (val) {
        return val;
      }
      return require('child_process')
        .execSync('git show -s --format=%ci HEAD')
        .toString()
        .trim()
        .split('-')
        .shift();
    })(process.env.CONSUL_COPYRIGHT_YEAR),
    CONSUL_GIT_SHA: (function(val) {
      if (val) {
        return val;
      }

      return require('child_process')
        .execSync('git rev-parse --short HEAD')
        .toString()
        .trim();
    })(process.env.CONSUL_GIT_SHA),
    CONSUL_VERSION: (function(val) {
      if (val) {
        return val;
      }
      // see /scripts/dist.sh:8
      const version_go = `${path.dirname(path.dirname(__dirname))}/version/version.go`;
      const contents = fs.readFileSync(version_go).toString();
      return contents
        .split('\n')
        .find(function(item, i, arr) {
          return item.indexOf('Version =') !== -1;
        })
        .trim()
        .split('"')[1];
    })(process.env.CONSUL_VERSION),
    CONSUL_BINARY_TYPE: process.env.CONSUL_BINARY_TYPE ? process.env.CONSUL_BINARY_TYPE : 'oss',
    CONSUL_ACLS_ENABLED: false,
    CONSUL_NSPACES_ENABLED: false,

    CONSUL_HOME_URL: 'https://www.consul.io',
    CONSUL_DOCS_URL: 'https://www.consul.io/docs',
    CONSUL_DOCS_LEARN_URL: 'https://learn.hashicorp.com/consul',
    CONSUL_DOCS_API_URL: 'https://www.consul.io/api',
    CONSUL_COPYRIGHT_URL: 'https://www.hashicorp.com',
  });
  const isDevLike = ['development', 'staging', 'test'].indexOf(environment) > -1;
  const isProdLike = ['production', 'staging'].indexOf(environment) > -1;
  switch (true) {
    case environment === 'test':
      ENV = Object.assign({}, ENV, {
        locationType: 'none',
        CONSUL_NSPACES_TEST: true,
        CONSUL_ACLS_ENABLED: true,
        '@hashicorp/ember-cli-api-double': {
          'auto-import': false,
          enabled: true,
          endpoints: ['/node_modules/@hashicorp/consul-api-double/v1'],
        },
        APP: Object.assign({}, ENV.APP, {
          LOG_ACTIVE_GENERATION: false,
          LOG_VIEW_LOOKUPS: false,

          rootElement: '#ember-testing',
          autoboot: false,
        }),
      });
    // passthrough
    case environment === 'staging':
      ENV = Object.assign({}, ENV, {
        '@hashicorp/ember-cli-api-double': {
          enabled: true,
          endpoints: ['/node_modules/@hashicorp/consul-api-double/v1'],
        },
      });
    // passthrough
    case environment === 'production':
      ENV = Object.assign({}, ENV, {
        CONSUL_ACLS_ENABLED: '{{.ACLsEnabled}}',
        CONSUL_NSPACES_ENABLED:
          '{{ if .NamespacesEnabled }}{{.NamespacesEnabled}}{{ else }}false{{ end }}',
      });
    // passthrough
    case isDevLike:
      ENV = Object.assign({}, ENV, {
        CONSUL_NSPACES_ENABLED: true,
        CONSUL_ACLS_ENABLED: true,
        // 'APP': Object.assign({}, ENV.APP, {
        //   'LOG_RESOLVER': true,
        //   'LOG_ACTIVE_GENERATION': true,
        //   'LOG_TRANSITIONS': true,
        //   'LOG_TRANSITIONS_INTERNAL': true,
        //   'LOG_VIEW_LOOKUPS': true,
        // })
      });
      break;
    case isProdLike:
      break;
  }
  return ENV;
};
<commit_msg>ui: Change environment switch statement to correct env vars<commit_after>'use strict';
const fs = require('fs');
const path = require('path');
module.exports = function(environment) {
  let ENV = {
    modulePrefix: 'consul-ui',
    environment,
    rootURL: '/ui/',
    locationType: 'auto',
    EmberENV: {
      FEATURES: {
        // Here you can enable experimental features on an ember canary build
        // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
      },
      EXTEND_PROTOTYPES: {
        // Prevent Ember Data from overriding Date.parse.
        Date: false,
      },
    },
    APP: {
      // Here you can pass flags/options to your application instance
      // when it is created
    },
    resizeServiceDefaults: {
      injectionFactories: ['view', 'controller', 'component'],
    },
  };
  // TODO: These should probably go onto APP
  ENV = Object.assign({}, ENV, {
    CONSUL_UI_DISABLE_REALTIME: typeof process.env.CONSUL_UI_DISABLE_REALTIME !== 'undefined',
    CONSUL_UI_DISABLE_ANCHOR_SELECTION:
      typeof process.env.CONSUL_UI_DISABLE_ANCHOR_SELECTION !== 'undefined',
    CONSUL_COPYRIGHT_YEAR: (function(val) {
      if (val) {
        return val;
      }
      return require('child_process')
        .execSync('git show -s --format=%ci HEAD')
        .toString()
        .trim()
        .split('-')
        .shift();
    })(process.env.CONSUL_COPYRIGHT_YEAR),
    CONSUL_GIT_SHA: (function(val) {
      if (val) {
        return val;
      }

      return require('child_process')
        .execSync('git rev-parse --short HEAD')
        .toString()
        .trim();
    })(process.env.CONSUL_GIT_SHA),
    CONSUL_VERSION: (function(val) {
      if (val) {
        return val;
      }
      // see /scripts/dist.sh:8
      const version_go = `${path.dirname(path.dirname(__dirname))}/version/version.go`;
      const contents = fs.readFileSync(version_go).toString();
      return contents
        .split('\n')
        .find(function(item, i, arr) {
          return item.indexOf('Version =') !== -1;
        })
        .trim()
        .split('"')[1];
    })(process.env.CONSUL_VERSION),
    CONSUL_BINARY_TYPE: process.env.CONSUL_BINARY_TYPE ? process.env.CONSUL_BINARY_TYPE : 'oss',
    CONSUL_ACLS_ENABLED: false,
    CONSUL_NSPACES_ENABLED: false,

    CONSUL_HOME_URL: 'https://www.consul.io',
    CONSUL_DOCS_URL: 'https://www.consul.io/docs',
    CONSUL_DOCS_LEARN_URL: 'https://learn.hashicorp.com/consul',
    CONSUL_DOCS_API_URL: 'https://www.consul.io/api',
    CONSUL_COPYRIGHT_URL: 'https://www.hashicorp.com',
  });
  const isDevLike = ['development', 'staging', 'test'].indexOf(environment) > -1;
  const isProdLike = ['production', 'staging'].indexOf(environment) > -1;
  switch (true) {
    case environment === 'test':
      ENV = Object.assign({}, ENV, {
        locationType: 'none',
        CONSUL_NSPACES_TEST: true,
        CONSUL_ACLS_ENABLED: true,
        '@hashicorp/ember-cli-api-double': {
          'auto-import': false,
          enabled: true,
          endpoints: ['/node_modules/@hashicorp/consul-api-double/v1'],
        },
        APP: Object.assign({}, ENV.APP, {
          LOG_ACTIVE_GENERATION: false,
          LOG_VIEW_LOOKUPS: false,

          rootElement: '#ember-testing',
          autoboot: false,
        }),
      });
      break;
    case environment === 'staging':
      ENV = Object.assign({}, ENV, {
        '@hashicorp/ember-cli-api-double': {
          enabled: true,
          endpoints: ['/node_modules/@hashicorp/consul-api-double/v1'],
        },
      });
      break;
    case environment === 'production':
      ENV = Object.assign({}, ENV, {
        CONSUL_ACLS_ENABLED: '{{.ACLsEnabled}}',
        CONSUL_NSPACES_ENABLED:
          '{{ if .NamespacesEnabled }}{{.NamespacesEnabled}}{{ else }}false{{ end }}',
      });
      break;
  }
  switch (true) {
    case isDevLike:
      ENV = Object.assign({}, ENV, {
        CONSUL_NSPACES_ENABLED: true,
        CONSUL_ACLS_ENABLED: true,
        // 'APP': Object.assign({}, ENV.APP, {
        //   'LOG_RESOLVER': true,
        //   'LOG_ACTIVE_GENERATION': true,
        //   'LOG_TRANSITIONS': true,
        //   'LOG_TRANSITIONS_INTERNAL': true,
        //   'LOG_VIEW_LOOKUPS': true,
        // })
      });
      break;
    case isProdLike:
      break;
  }
  return ENV;
};
<|endoftext|>


<commit_before>import React from 'react';
import { connect } from 'react-redux';
import { addLocaleData, IntlProvider } from 'react-intl';
import enLocaleData from 'react-intl/locale-data/en';
import { login } from './auth/authActions';
import { getConfig } from './actions';
import { getStoredBookmarks } from './bookmarks/bookmarksActions';
import { notify } from './app/Notification/notificationActions';
import Notification from './app/Notification/Notification';
import Sidebar from './app/Sidebar';
import * as messages from './translations/i18n';

addLocaleData(enLocaleData);
addLocaleData({
  locale: 'fr',
  parentLocale: 'en',
});

var Wrapper = React.createClass({
  componentWillMount() {
    this.props.login();
    this.props.getConfig();
    this.props.getStoredBookmarks();
  },

  render() {
    console.log(messages);
    const className = (!this.props.app.sidebarIsVisible) ? 'app-wrapper full-width' : 'app-wrapper';
    const { app, auth, notify } = this.props;
    return (
      <IntlProvider locale={app.locale} messages={messages[app.locale]}>

          <Sidebar />
          <Notification />
          {React.cloneElement(
            this.props.children,
            { auth, notify }
          )}

      </IntlProvider>
    );
  }
});

const mapStateToProps = ({ app, auth }) => {
  return {
    app,
    auth
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    login: () => { dispatch(login()); },
    getConfig: () => { dispatch(getConfig()); },
    notify: (text) => { dispatch(notify(text)); },
    getStoredBookmarks: () => { dispatch(getStoredBookmarks()); },
  };
};

module.exports = connect(mapStateToProps, mapDispatchToProps)(Wrapper);
<commit_msg>remove the log<commit_after>import React from 'react';
import { connect } from 'react-redux';
import { addLocaleData, IntlProvider } from 'react-intl';
import enLocaleData from 'react-intl/locale-data/en';
import { login } from './auth/authActions';
import { getConfig } from './actions';
import { getStoredBookmarks } from './bookmarks/bookmarksActions';
import { notify } from './app/Notification/notificationActions';
import Notification from './app/Notification/Notification';
import Sidebar from './app/Sidebar';
import * as messages from './translations/i18n';

addLocaleData(enLocaleData);
addLocaleData({
  locale: 'fr',
  parentLocale: 'en',
});

var Wrapper = React.createClass({
  componentWillMount() {
    this.props.login();
    this.props.getConfig();
    this.props.getStoredBookmarks();
  },

  render() {
    const className = (!this.props.app.sidebarIsVisible) ? 'app-wrapper full-width' : 'app-wrapper';
    const { app, auth, notify } = this.props;
    return (
      <IntlProvider locale={app.locale} messages={messages[app.locale]}>

          <Sidebar />
          <Notification />
          {React.cloneElement(
            this.props.children,
            { auth, notify }
          )}

      </IntlProvider>
    );
  }
});

const mapStateToProps = ({ app, auth }) => {
  return {
    app,
    auth
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    login: () => { dispatch(login()); },
    getConfig: () => { dispatch(getConfig()); },
    notify: (text) => { dispatch(notify(text)); },
    getStoredBookmarks: () => { dispatch(getStoredBookmarks()); },
  };
};

module.exports = connect(mapStateToProps, mapDispatchToProps)(Wrapper);
<|endoftext|>


<commit_before>// ==UserScript==
// @name        Duolingo Course Switcher (New Site)
// @description Simplifies switching between courses that use different interface languages (i.e., base language, the language from which you learn).
// @namespace   https://github.com/zeta12ti/DuolingoCourseSwitcher
// @updateURL   https://github.com/zeta12ti/DuolingoCourseSwitcher/raw/master/DuolingoCourseSwitcher.user.js
// @include     https://*.duolingo.com/*
// @grant       none
// @version     1.1.8
// @author      zeta12ti
// ==/UserScript==

/*
 * NOTE: Annoyingly, duo.l10n.declared may change with time, which shifts the index for each phrase
 * If the header on the course menu is wrong, that's probably what happened. I'll keep track of which duo.versions work here.
*/
// things to check with new versions:
//  duo.l10n.declared[146] is Languages
//  duo.l10n.undeclared[languages[languageCode]] is correct
// console.assert(['0beed7c'].includes(window.duo.version))

// Language names (in english) - for use with duo.l10n.undeclared
var languages = {'ar': 'Arabic', 'bn': 'Bengali', 'ca': 'Catalan', 'cs': 'Czech', 'cy': 'Welsh', 'da': 'Danish', 'de': 'German', 'el': 'Greek', 'en': 'English', 'eo': 'Esperanto', 'es': 'Spanish', 'fr': 'French', 'ga': 'Irish', 'gn': 'Guarani (Jopar\u00e1)', 'he': 'Hebrew', 'hi': 'Hindi', 'hu': 'Hungarian', 'id': 'Indonesian', 'it': 'Italian', 'ja': 'Japanese', 'ko': 'Korean', 'nl-NL': 'Dutch', 'no-BO': 'Norwegian (Bokm\u00e5l)', 'pa': 'Punjabi (Gurmukhi)', 'pl': 'Polish', 'pt': 'Portuguese', 'ro': 'Romanian', 'ru': 'Russian', 'sv': 'Swedish', 'sw': 'Swahili', 'ta': 'Tamil', 'te': 'Telugu', 'th': 'Thai', 'tl': 'Tagalog', 'tlh': 'Klingon', 'tr': 'Turkish', 'uk': 'Ukrainian', 'vi': 'Vietnamese', 'zh-CN': 'Chinese'}

async function getLanguageString (languageCode) {
  return window.duo.l10n.undeclared[languages[languageCode]] || window.duo.l10n.undeclared[languages[languageCode] + '||capitalized'] || languages[languageCode] || 'Unknown'
}

// language flags -
// keys: remove flag- prefix and keep consistency - zs -> zh-CN, kl -> tlh, (zs -> zh-¿?), values: remove _3viv6
var flags = {'ar': '_1ARRD', 'bn': '_2TXAL', 'ca': 'mc4rg', 'cs': '_1uPQW', 'cy': '_1jO8h', 'da': '_1h0xh', 'de': 'oboa9', 'dk': '_3AA1F', 'el': '_2tQo9', 'en': '_2cR-E', 'eo': 'pWj0w', 'es': 'u5W-o', 'fr': '_2KQN3', 'ga': '_1vhNM', 'gn': '_24xu4', 'he': '_PDrK', 'hi': 'OgUIe', 'hu': '_1S3hi', 'id': '_107sn', 'it': '_1PruQ', 'ja': '_2N-Uj', 'ko': '_2lkzc', 'nl-NL': '_1fajz', 'no-BO': '_200jU', 'pl': '_3uusw', 'pt': 'pmGwL', 'ro': '_12U6e', 'ru': '_1eqxJ', 'sn': 'q_PD-', 'sv': '_2DMfV', 'sw': '_3T1km', 'th': '_2oTcA', 'tl': '_1q_MQ', 'tlh': '_6mRM', 'tr': '_1tJJ2', 'uk': '_1zZsN', 'un': 't-XH-', 'vi': '_1KtzC', 'zh': 'xi6jQ', 'zh-CN': '_2gNgd', '_circle-flag': '_2XSZu', '_flag': '_3viv6', 'medium-circle-flag': '_1ct7y _2XSZu', 'micro-circle-flag': '_3i5IF _2XSZu', 'small-circle-flag': '_3PU7E _2XSZu'}
// Use this to identify a row in the original list.
var inverseFlags = {'OgUIe': 'hi', '_107sn': 'id', '_12U6e': 'ro', '_1ARRD': 'ar', '_1KtzC': 'vi', '_1PruQ': 'it', '_1S3hi': 'hu', '_1ct7y _2XSZu': 'medium-circle-flag', '_1eqxJ': 'ru', '_1fajz': 'nl-NL', '_1h0xh': 'da', '_1jO8h': 'cy', '_1q_MQ': 'tl', '_1tJJ2': 'tr', '_1uPQW': 'cs', '_1vhNM': 'ga', '_1zZsN': 'uk', '_200jU': 'no-BO', '_24xu4': 'gn', '_2DMfV': 'sv', '_2KQN3': 'fr', '_2N-Uj': 'ja', '_2TXAL': 'bn', '_2XSZu': '_circle-flag', '_2cR-E': 'en', '_2gNgd': 'zh-CN', '_2lkzc': 'ko', '_2oTcA': 'th', '_2tQo9': 'el', '_3AA1F': 'dk', '_3PU7E _2XSZu': 'small-circle-flag', '_3T1km': 'sw', '_3i5IF _2XSZu': 'micro-circle-flag', '_3uusw': 'pl', '_3viv6': '_flag', '_6mRM': 'tlh', '_PDrK': 'he', 'mc4rg': 'ca', 'oboa9': 'de', 'pWj0w': 'eo', 'pmGwL': 'pt', 'q_PD-': 'sn', 't-XH-': 'un', 'u5W-o': 'es', 'xi6jQ': 'zh'}

async function getLanguageFlag (languageCode) {
    // The 'un' flag is a question mark.
  return flags[languageCode] || flags.un
}

async function getLanguageFromFlag (flagCode) {
  return inverseFlags[flagCode]
}

async function computeMenuOffset (languageSubmenu) {
  var header = languageSubmenu.querySelector('._2PurW')

  var offset = 0
  offset += parseFloat(window.getComputedStyle(languageSubmenu).getPropertyValue('padding-top'))

  offset += parseFloat(window.getComputedStyle(header).getPropertyValue('padding-top'))

  offset += parseFloat(window.getComputedStyle(header).getPropertyValue('padding-bottom'))

  offset += parseFloat(window.getComputedStyle(header).getPropertyValue('line-height'))

  return offset
}

async function setMenuPosition () {
  var verticalOffset = await computeMenuOffset(this)
  var verticalBase = this.parentNode.getBoundingClientRect().top - document.querySelector('._20LC5').getBoundingClientRect().top
  //  var horizontalBase = this.parentNode.getBoundingClientRect().left - document.querySelector('._20LC5').getBoundingClientRect().left
  this.style.top = (verticalBase - verticalOffset) + 'px'
}

async function addRule (stylesheet, rule) {
  stylesheet.insertRule(rule, stylesheet.cssRules.length)
}

async function addFromLanguageItem (fromLanguage, currentCourse) {
  var fromLanguageItem = document.createElement('li')

  var className = '_2kNgI _1qBnH from-course'
  if (currentCourse) {
    className = '_1oVFS ' + className
  }
  fromLanguageItem.className = className

  var container = document.createElement('li')
  container.className = '_2kNgI flag-container'
  fromLanguageItem.appendChild(container)

  var flag = document.createElement('span')
  className = await getLanguageFlag(fromLanguage) + ' _3viv6 _3vx2Z _1ct7y _2XSZu from-flag'
  flag.className = className
  container.appendChild(flag)

  var localizedLanguageName = await getLanguageString(fromLanguage)
  container.appendChild(document.createTextNode(localizedLanguageName))

  document.querySelector('._1XE6M').appendChild(fromLanguageItem)

  return fromLanguageItem
}

async function createInnerMenu (oldHeader) {
  var innerMenu = document.createElement('ul')
  innerMenu.className = 'OSaWc _1ZY-H language-submenu'

  var innerMenuHeader = document.createElement('li')
  innerMenuHeader.className = '_2PurW'
  innerMenuHeader.innerHTML = oldHeader
  innerMenu.appendChild(innerMenuHeader)

  var innerMenuCourseList = document.createElement('ul')
  innerMenuCourseList.className = '_1XE6M'
  innerMenu.appendChild(innerMenuCourseList)

  return innerMenu
}

async function addStyleSheet () {
  var cssStyle = document.createElement('style')
  document.querySelector('html > head').appendChild(cssStyle)
  var stylesheet = cssStyle.sheet

  addRule(stylesheet, 'li._2kNgI._1qBnH {display: none}')
  addRule(stylesheet, 'ul._1XE6M > li.qsrrc {display: none}')
  addRule(stylesheet, '.from-course:hover .language-submenu {display: block}')
  addRule(stylesheet, 'li._2kNgI._1qBnH.from-course {display: block}')
  addRule(stylesheet, 'li._2kNgI._1qBnH.learning-course {display: block}')
  addRule(stylesheet, '.flag-container {display: block}')
  addRule(stylesheet, '.flag-container {width: 100%}')

  addRule(stylesheet, '.language-submenu ._1XE6M .learning-course ._1fA14 {color: #999}')
  addRule(stylesheet, '.language-submenu ._1XE6M .learning-course:hover ._1fA14 {color: #fff}')

  addRule(stylesheet, '.from-course {position: static}')
  addRule(stylesheet, '._1XE6M {position: static}')
  addRule(stylesheet, '.language-submenu {position: absolute}')
  addRule(stylesheet, '.language-submenu {z-index: 10}')
  addRule(stylesheet, '.flag-container {position: relative}')
  addRule(stylesheet, '.from-course {padding: 0px 0px 0px 0px}')
  addRule(stylesheet, '.flag-container {padding: 3px 20px 3px 47px}')
  addRule(stylesheet, 'html[dir="ltr"] .language-submenu {left:90%}')
  addRule(stylesheet, 'html[dir="rtl"] .language-submenu {right:90%}')
}

// TODO: track changes to language name and flag:
// If the order of the xp changes, Duolingo doesn't switch around the nodes,
// it changes them in-place
async function copyChanges (modifications, menuItem) {
  modifications.forEach(mod => {
    if (mod.target.parentNode.classList && mod.target.parentNode.classList.contains('old-level-indicator')) {
      menuItem.querySelector('.level-indicator').innerText = mod.target.parentNode.innerText
    }
    if (mod.target.classList && mod.target.classList.contains('_2kNgI')) {
      menuItem.className = mod.target.className + ' learning-course'
    }
  })
}

async function reorganizeMenu () {
  var uiLanguage = window.duo.uiLanguage
  var baseLanguages = {}
  var courses = document.querySelectorAll('li._2kNgI._1qBnH:not(.from-course)')

  addStyleSheet()

  var oldHeader = document.querySelector('._2PurW').innerHTML
  document.querySelector('._2PurW').innerHTML = '
' + window.duo.l10n.declared[147] + '
' // Languages

  for (var i = 0, len = courses.length; i < len; i++) {
    let menuItem = courses[i].cloneNode(true)
    courses[i].classList.add('old-menu-item')
    courses[i].querySelector('._1fA14').classList.add('old-level-indicator')

    menuItem.addEventListener('click', courses[i].click.bind(courses[i]))
    menuItem.querySelector('._1fA14').classList.add('level-indicator')
    let observer = new window.MutationObserver((mods) => { copyChanges(mods, menuItem) })
    var config = {attributes: true, characterData: true, subtree: true}
    observer.observe(courses[i], config)

    var flags = menuItem.querySelectorAll('._3viv6')
    var learningLanguage, fromLanguage
    if (typeof menuItem.dataset.from === 'undefined' && typeof menuItem.dataset.learning === 'undefined') {
      if (flags.length === 2) {
        learningLanguage = await getLanguageFromFlag(flags[0].classList[0])
        fromLanguage = await getLanguageFromFlag(flags[1].classList[0])
      } else if (flags.length === 1) {
        learningLanguage = await getLanguageFromFlag(flags[0].classList[0])
        fromLanguage = uiLanguage
      } else {
        learningLanguage = 'un'
        fromLanguage = uiLanguage
      }
    } else {
      learningLanguage = menuItem.dataset.learning
      fromLanguage = menuItem.dataset.from
    }

    if (flags.length === 2) {
      flags[1].style.display = 'none'
      flags[0].classList.remove('_2IJLr')
    }
    flags[0].classList.add('learning-flag')

    menuItem.dataset.learning = learningLanguage
    menuItem.dataset.from = fromLanguage
    menuItem.classList.add('learning-course')

    if (fromLanguage in baseLanguages) {
      baseLanguages[fromLanguage].querySelector('._1XE6M').appendChild(menuItem)
    } else {
      var fromLanguageItem = await addFromLanguageItem(fromLanguage, fromLanguage === uiLanguage)
      let innerMenu = await createInnerMenu(oldHeader)
      baseLanguages[fromLanguage] = innerMenu
      baseLanguages[fromLanguage].querySelector('._1XE6M').appendChild(menuItem)

      fromLanguageItem.appendChild(baseLanguages[fromLanguage])
      fromLanguageItem.addEventListener('mouseover', setMenuPosition.bind(innerMenu))
    }
  }
}

async function routine () {
  reorganizeMenu()
}

if (document.readyState === 'complete') { routine() } else {
  window.addEventListener('load', routine)
}
<commit_msg>The version was already at 1.1.8 at master<commit_after>// ==UserScript==
// @name        Duolingo Course Switcher (New Site)
// @description Simplifies switching between courses that use different interface languages (i.e., base language, the language from which you learn).
// @namespace   https://github.com/zeta12ti/DuolingoCourseSwitcher
// @updateURL   https://github.com/zeta12ti/DuolingoCourseSwitcher/raw/master/DuolingoCourseSwitcher.user.js
// @include     https://*.duolingo.com/*
// @grant       none
// @version     1.1.9
// @author      zeta12ti
// ==/UserScript==

/*
 * NOTE: Annoyingly, duo.l10n.declared may change with time, which shifts the index for each phrase
 * If the header on the course menu is wrong, that's probably what happened. I'll keep track of which duo.versions work here.
*/
// things to check with new versions:
//  duo.l10n.declared[146] is Languages
//  duo.l10n.undeclared[languages[languageCode]] is correct
// console.assert(['0beed7c'].includes(window.duo.version))

// Language names (in english) - for use with duo.l10n.undeclared
var languages = {'ar': 'Arabic', 'bn': 'Bengali', 'ca': 'Catalan', 'cs': 'Czech', 'cy': 'Welsh', 'da': 'Danish', 'de': 'German', 'el': 'Greek', 'en': 'English', 'eo': 'Esperanto', 'es': 'Spanish', 'fr': 'French', 'ga': 'Irish', 'gn': 'Guarani (Jopar\u00e1)', 'he': 'Hebrew', 'hi': 'Hindi', 'hu': 'Hungarian', 'id': 'Indonesian', 'it': 'Italian', 'ja': 'Japanese', 'ko': 'Korean', 'nl-NL': 'Dutch', 'no-BO': 'Norwegian (Bokm\u00e5l)', 'pa': 'Punjabi (Gurmukhi)', 'pl': 'Polish', 'pt': 'Portuguese', 'ro': 'Romanian', 'ru': 'Russian', 'sv': 'Swedish', 'sw': 'Swahili', 'ta': 'Tamil', 'te': 'Telugu', 'th': 'Thai', 'tl': 'Tagalog', 'tlh': 'Klingon', 'tr': 'Turkish', 'uk': 'Ukrainian', 'vi': 'Vietnamese', 'zh-CN': 'Chinese'}

async function getLanguageString (languageCode) {
  return window.duo.l10n.undeclared[languages[languageCode]] || window.duo.l10n.undeclared[languages[languageCode] + '||capitalized'] || languages[languageCode] || 'Unknown'
}

// language flags -
// keys: remove flag- prefix and keep consistency - zs -> zh-CN, kl -> tlh, (zs -> zh-¿?), values: remove _3viv6
var flags = {'ar': '_1ARRD', 'bn': '_2TXAL', 'ca': 'mc4rg', 'cs': '_1uPQW', 'cy': '_1jO8h', 'da': '_1h0xh', 'de': 'oboa9', 'dk': '_3AA1F', 'el': '_2tQo9', 'en': '_2cR-E', 'eo': 'pWj0w', 'es': 'u5W-o', 'fr': '_2KQN3', 'ga': '_1vhNM', 'gn': '_24xu4', 'he': '_PDrK', 'hi': 'OgUIe', 'hu': '_1S3hi', 'id': '_107sn', 'it': '_1PruQ', 'ja': '_2N-Uj', 'ko': '_2lkzc', 'nl-NL': '_1fajz', 'no-BO': '_200jU', 'pl': '_3uusw', 'pt': 'pmGwL', 'ro': '_12U6e', 'ru': '_1eqxJ', 'sn': 'q_PD-', 'sv': '_2DMfV', 'sw': '_3T1km', 'th': '_2oTcA', 'tl': '_1q_MQ', 'tlh': '_6mRM', 'tr': '_1tJJ2', 'uk': '_1zZsN', 'un': 't-XH-', 'vi': '_1KtzC', 'zh': 'xi6jQ', 'zh-CN': '_2gNgd', '_circle-flag': '_2XSZu', '_flag': '_3viv6', 'medium-circle-flag': '_1ct7y _2XSZu', 'micro-circle-flag': '_3i5IF _2XSZu', 'small-circle-flag': '_3PU7E _2XSZu'}
// Use this to identify a row in the original list.
var inverseFlags = {'OgUIe': 'hi', '_107sn': 'id', '_12U6e': 'ro', '_1ARRD': 'ar', '_1KtzC': 'vi', '_1PruQ': 'it', '_1S3hi': 'hu', '_1ct7y _2XSZu': 'medium-circle-flag', '_1eqxJ': 'ru', '_1fajz': 'nl-NL', '_1h0xh': 'da', '_1jO8h': 'cy', '_1q_MQ': 'tl', '_1tJJ2': 'tr', '_1uPQW': 'cs', '_1vhNM': 'ga', '_1zZsN': 'uk', '_200jU': 'no-BO', '_24xu4': 'gn', '_2DMfV': 'sv', '_2KQN3': 'fr', '_2N-Uj': 'ja', '_2TXAL': 'bn', '_2XSZu': '_circle-flag', '_2cR-E': 'en', '_2gNgd': 'zh-CN', '_2lkzc': 'ko', '_2oTcA': 'th', '_2tQo9': 'el', '_3AA1F': 'dk', '_3PU7E _2XSZu': 'small-circle-flag', '_3T1km': 'sw', '_3i5IF _2XSZu': 'micro-circle-flag', '_3uusw': 'pl', '_3viv6': '_flag', '_6mRM': 'tlh', '_PDrK': 'he', 'mc4rg': 'ca', 'oboa9': 'de', 'pWj0w': 'eo', 'pmGwL': 'pt', 'q_PD-': 'sn', 't-XH-': 'un', 'u5W-o': 'es', 'xi6jQ': 'zh'}

async function getLanguageFlag (languageCode) {
    // The 'un' flag is a question mark.
  return flags[languageCode] || flags.un
}

async function getLanguageFromFlag (flagCode) {
  return inverseFlags[flagCode]
}

async function computeMenuOffset (languageSubmenu) {
  var header = languageSubmenu.querySelector('._2PurW')

  var offset = 0
  offset += parseFloat(window.getComputedStyle(languageSubmenu).getPropertyValue('padding-top'))

  offset += parseFloat(window.getComputedStyle(header).getPropertyValue('padding-top'))

  offset += parseFloat(window.getComputedStyle(header).getPropertyValue('padding-bottom'))

  offset += parseFloat(window.getComputedStyle(header).getPropertyValue('line-height'))

  return offset
}

async function setMenuPosition () {
  var verticalOffset = await computeMenuOffset(this)
  var verticalBase = this.parentNode.getBoundingClientRect().top - document.querySelector('._20LC5').getBoundingClientRect().top
  //  var horizontalBase = this.parentNode.getBoundingClientRect().left - document.querySelector('._20LC5').getBoundingClientRect().left
  this.style.top = (verticalBase - verticalOffset) + 'px'
}

async function addRule (stylesheet, rule) {
  stylesheet.insertRule(rule, stylesheet.cssRules.length)
}

async function addFromLanguageItem (fromLanguage, currentCourse) {
  var fromLanguageItem = document.createElement('li')

  var className = '_2kNgI _1qBnH from-course'
  if (currentCourse) {
    className = '_1oVFS ' + className
  }
  fromLanguageItem.className = className

  var container = document.createElement('li')
  container.className = '_2kNgI flag-container'
  fromLanguageItem.appendChild(container)

  var flag = document.createElement('span')
  className = await getLanguageFlag(fromLanguage) + ' _3viv6 _3vx2Z _1ct7y _2XSZu from-flag'
  flag.className = className
  container.appendChild(flag)

  var localizedLanguageName = await getLanguageString(fromLanguage)
  container.appendChild(document.createTextNode(localizedLanguageName))

  document.querySelector('._1XE6M').appendChild(fromLanguageItem)

  return fromLanguageItem
}

async function createInnerMenu (oldHeader) {
  var innerMenu = document.createElement('ul')
  innerMenu.className = 'OSaWc _1ZY-H language-submenu'

  var innerMenuHeader = document.createElement('li')
  innerMenuHeader.className = '_2PurW'
  innerMenuHeader.innerHTML = oldHeader
  innerMenu.appendChild(innerMenuHeader)

  var innerMenuCourseList = document.createElement('ul')
  innerMenuCourseList.className = '_1XE6M'
  innerMenu.appendChild(innerMenuCourseList)

  return innerMenu
}

async function addStyleSheet () {
  var cssStyle = document.createElement('style')
  document.querySelector('html > head').appendChild(cssStyle)
  var stylesheet = cssStyle.sheet

  addRule(stylesheet, 'li._2kNgI._1qBnH {display: none}')
  addRule(stylesheet, 'ul._1XE6M > li.qsrrc {display: none}')
  addRule(stylesheet, '.from-course:hover .language-submenu {display: block}')
  addRule(stylesheet, 'li._2kNgI._1qBnH.from-course {display: block}')
  addRule(stylesheet, 'li._2kNgI._1qBnH.learning-course {display: block}')
  addRule(stylesheet, '.flag-container {display: block}')
  addRule(stylesheet, '.flag-container {width: 100%}')

  addRule(stylesheet, '.language-submenu ._1XE6M .learning-course ._1fA14 {color: #999}')
  addRule(stylesheet, '.language-submenu ._1XE6M .learning-course:hover ._1fA14 {color: #fff}')

  addRule(stylesheet, '.from-course {position: static}')
  addRule(stylesheet, '._1XE6M {position: static}')
  addRule(stylesheet, '.language-submenu {position: absolute}')
  addRule(stylesheet, '.language-submenu {z-index: 10}')
  addRule(stylesheet, '.flag-container {position: relative}')
  addRule(stylesheet, '.from-course {padding: 0px 0px 0px 0px}')
  addRule(stylesheet, '.flag-container {padding: 3px 20px 3px 47px}')
  addRule(stylesheet, 'html[dir="ltr"] .language-submenu {left:90%}')
  addRule(stylesheet, 'html[dir="rtl"] .language-submenu {right:90%}')
}

// TODO: track changes to language name and flag:
// If the order of the xp changes, Duolingo doesn't switch around the nodes,
// it changes them in-place
async function copyChanges (modifications, menuItem) {
  modifications.forEach(mod => {
    if (mod.target.parentNode.classList && mod.target.parentNode.classList.contains('old-level-indicator')) {
      menuItem.querySelector('.level-indicator').innerText = mod.target.parentNode.innerText
    }
    if (mod.target.classList && mod.target.classList.contains('_2kNgI')) {
      menuItem.className = mod.target.className + ' learning-course'
    }
  })
}

async function reorganizeMenu () {
  var uiLanguage = window.duo.uiLanguage
  var baseLanguages = {}
  var courses = document.querySelectorAll('li._2kNgI._1qBnH:not(.from-course)')

  addStyleSheet()

  var oldHeader = document.querySelector('._2PurW').innerHTML
  document.querySelector('._2PurW').innerHTML = '
' + window.duo.l10n.declared[147] + '
' // Languages

  for (var i = 0, len = courses.length; i < len; i++) {
    let menuItem = courses[i].cloneNode(true)
    courses[i].classList.add('old-menu-item')
    courses[i].querySelector('._1fA14').classList.add('old-level-indicator')

    menuItem.addEventListener('click', courses[i].click.bind(courses[i]))
    menuItem.querySelector('._1fA14').classList.add('level-indicator')
    let observer = new window.MutationObserver((mods) => { copyChanges(mods, menuItem) })
    var config = {attributes: true, characterData: true, subtree: true}
    observer.observe(courses[i], config)

    var flags = menuItem.querySelectorAll('._3viv6')
    var learningLanguage, fromLanguage
    if (typeof menuItem.dataset.from === 'undefined' && typeof menuItem.dataset.learning === 'undefined') {
      if (flags.length === 2) {
        learningLanguage = await getLanguageFromFlag(flags[0].classList[0])
        fromLanguage = await getLanguageFromFlag(flags[1].classList[0])
      } else if (flags.length === 1) {
        learningLanguage = await getLanguageFromFlag(flags[0].classList[0])
        fromLanguage = uiLanguage
      } else {
        learningLanguage = 'un'
        fromLanguage = uiLanguage
      }
    } else {
      learningLanguage = menuItem.dataset.learning
      fromLanguage = menuItem.dataset.from
    }

    if (flags.length === 2) {
      flags[1].style.display = 'none'
      flags[0].classList.remove('_2IJLr')
    }
    flags[0].classList.add('learning-flag')

    menuItem.dataset.learning = learningLanguage
    menuItem.dataset.from = fromLanguage
    menuItem.classList.add('learning-course')

    if (fromLanguage in baseLanguages) {
      baseLanguages[fromLanguage].querySelector('._1XE6M').appendChild(menuItem)
    } else {
      var fromLanguageItem = await addFromLanguageItem(fromLanguage, fromLanguage === uiLanguage)
      let innerMenu = await createInnerMenu(oldHeader)
      baseLanguages[fromLanguage] = innerMenu
      baseLanguages[fromLanguage].querySelector('._1XE6M').appendChild(menuItem)

      fromLanguageItem.appendChild(baseLanguages[fromLanguage])
      fromLanguageItem.addEventListener('mouseover', setMenuPosition.bind(innerMenu))
    }
  }
}

async function routine () {
  reorganizeMenu()
}

if (document.readyState === 'complete') { routine() } else {
  window.addEventListener('load', routine)
}
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015 Alex Grant (@localnerve), LocalNerve LLC
 * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
 */
/* global describe, it, before, beforeEach */
'use strict';

require('node-jsx').install({ extension: '.jsx' });

var expect = require('chai').expect;

var testDom = require('../../utils/testdom');
// This is an isomorphic component, so for testing, prepare to load react with dom here
testDom();

var ApplicationStore = require('../../../stores/ApplicationStore');
var createMockComponentContext = require('fluxible/utils').createMockComponentContext;
var fluxibleApp = require('../../../app');
var React = require('react/addons');

describe('application component', function() {
  var AppFactory, appElement, context, testUtils;

  var homeRoute = {
    params: { key: 'home' },
    config: { page: 'home' }
  };

  function makeHomePath() {
    return '/';
  }

  before(function() {
    AppFactory = fluxibleApp.getComponent();
    testUtils = React.addons.TestUtils;
  });

  beforeEach(function() {
    context = createMockComponentContext({
      stores: [ApplicationStore]
    });
    context.makePath = makeHomePath;
    appElement = AppFactory({
      context: context
    });
    testDom();
  });

  it.skip('should render hello world heading', function() {
    var appStore = context.getStore(ApplicationStore);
    var app = testUtils.renderIntoDocument(appElement);

    // update state
    appStore.handleNavigate(homeRoute);

    var component = testUtils.findRenderedDOMComponentWithTag(app, 'h2');
    expect(component.getDOMNode().textContent).to.match(/hello world/i);
  });

  it.skip('should render navigation', function() {
    var appStore = context.getStore(ApplicationStore);
    var app = testUtils.renderIntoDocument(appElement);

    // update state
    appStore.handleNavigate(homeRoute);

    // throws if not exactly 1
    testUtils.findRenderedDOMComponentWithTag(app, 'ul');
  });
});<commit_msg>update application component test to use dynamic content<commit_after>/**
 * Copyright (c) 2015 Alex Grant (@localnerve), LocalNerve LLC
 * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
 */
/* global describe, it, before, beforeEach */
'use strict';

require('node-jsx').install({ extension: '.jsx' });

var expect = require('chai').expect;

var testDom = require('../../utils/testdom');
// This is an isomorphic component, so for testing, prepare to load react with dom here
testDom();

var createMockComponentContext = require('fluxible/utils').createMockComponentContext;

var ApplicationStore = require('../../../stores/ApplicationStore');
var ContentStore = require('../../../stores/ContentStore');
var serviceData = require('../../fixtures/service-data');
var routesResponse = require('../../fixtures/routes-response');
var fluxibleRoutes = require('../../fixtures/fluxible-routes');
var fluxibleApp = require('../../../app');

var React = require('react');
var ReactAddons = require('react/addons');

describe('application component', function() {
  var appElement, context, testUtils;

  var homeRoute = {
    params: { key: 'home' },
    config: routesResponse.home
  };
  var homePage = {
    resource: routesResponse.home.action.params.resource
  };

  function makeHomePath() {
    return '/';
  }

  before(function() {
    testUtils = ReactAddons.addons.TestUtils;

    serviceData.fetch(homePage, function(err, content) {
      if (err) {
        throw err;
      }
      homePage.content = content;
    });
  });

  beforeEach(function() {
    context = createMockComponentContext({
      stores: [ApplicationStore, ContentStore]
    });
    context.makePath = makeHomePath;

    var appStore = context.getStore(ApplicationStore);
    var contentStore = context.getStore(ContentStore);

    appStore.receiveRoutes(fluxibleRoutes);
    appStore.handleNavigate(homeRoute);
    contentStore.receivePageContent(homePage);

    appElement = React.createElement(fluxibleApp.getComponent(), {
      context: context
    });

    testDom();
  });

  it('should render home content', function() {
    var app = testUtils.renderIntoDocument(appElement);

    var component = testUtils.findRenderedDOMComponentWithClass(app, 'page-content');

    expect(component.getDOMNode().textContent).to.match(/Home/i);
  });

  it('should render navigation', function() {
    var app = testUtils.renderIntoDocument(appElement);

    // throws if not exactly 1
    testUtils.findRenderedDOMComponentWithTag(app, 'ul');
  });
});<|endoftext|>


<commit_before>export default {
  'HOME': 'Home',
  'ADD_ITEM': 'Add item',
  'ADD_CATEGORY': 'Add category',
  'MONTHLY_SALARY': 'Monthly salary',
  'ADD_INCOME': 'Add income',
  'ADD_EXPENSE': 'Add expense',
  'REPORTS': 'Reports',
  'SETTINGS': 'Settings',
  'DETAILED_REPORTS': 'Detailed reports',
  'REPORT_DOWNLOADER': 'Report downloader',
  'EDIT_EXPENSE': 'Edit expense',
  'EDIT_INCOME': 'Edit income',
  'EDIT_ITEM': 'Edit item',
  'EDIT_CATEGORY': 'Edit category',
  'CATEGORIES': 'Categories',
  'ITEMS': 'Items',
  'NOT_FOUND': 'Not found',
  'BUDGET': 'Budget',
  'BUDGET_LEFT': 'Budget left',
  'EXPENSES': 'Expenses',
  'INCOMES': 'Incomes',
  'PIE_CHART_DESCRIPTION': 'Monthly budget for {{month}}',
  'ADD_NEW_EXPENSE': 'Add new expense',
  'ADD_NEW_INCOME': 'Add new income',
  'SEE_CATEGORIES': 'See categories',
  'SEE_ITEMS': 'See items',
  'CHANGE_MONTHLY_SALARY': 'Change montly salary',
  'SEE_REPORTS': 'See reports',
  'DRAWER_EXPENSES_DESCRIPTION': 'Current: {{expense}}{{currency}}',
  'DRAWER_INCOMES_DESCRIPTION': 'Current: {{income}}{{currency}}',
  'DRAWER_CATEGORIES_DESCRIPTION': '{{count}} categories now',
  'DRAWER_ITEMS_DESCRIPTION': '{{count}} items now',
  'DRAWER_SALARY_DESCRIPTION': 'Current {{salary}}{{currency}}',
  'COLOR': 'Color',
  'ICON': 'Icon',
  'TITLE': 'Title',
  'CANCEL': 'Cancel',
  'CONFIRM': 'Confirm',
  'SELECT_DATE': 'select date',
  'CATEGORY': 'Category',
  'ITEM': 'Item',
  'DATE': 'Date',
  'VALUE': 'Value',
  'NAME': 'Name',
  'CHOOSE': 'Choose',
  'NEXT': 'Next',
  'PREV': 'Prev',
  'SAVE': 'Save',
  'EDIT': 'Edit',
  'CATEGORY_CARD_COUNT': '{{count}} items',
  'STACK_BAR_DESCRIPTION': 'Monthly budget',
  'LOGIN_WITH_EMAIL': 'Login with Email',
  'LOGIN_WITH_PHONE': 'Login with Phone',
  'NOT_FOUND_DESCRIPTION': 'The resource could not be found',
  'FILE_SETTINGS': 'File Settings',
  'DECIMAL_SEPARATOR': 'Decimal separator',
  'DELIMITER_CHARACTER': 'Delimiter character',
  'EVENTS': 'Events',
  'REPORT_FOR_TIME_PERIOD': 'Report for time period',
  'FROM_DATE': 'From date',
  'TO_DATE': 'To date',
  'DOWNLOAD_CSV': 'Download CSV',
  'PAST_REPORTS': 'Past reports',
  'NO_PAST_REPORTS': 'No past reports',
  'CHOOSE_LANGUAGE': 'Choose Language',
  'CHOOSE_CURRENCY': 'Choose Currency',
  'AMERICAN_DOLLAR': 'American $',
  'BULGARIAN_LEV': 'Bulgarian lev',
  'APPEARANCE': 'Appearance',
  'LANGUAGE': 'Language',
  'CURRENCY': 'Currency',
  'DEVICE': 'Device',
  'PUSH_NOTIFICATIONS': 'Push Notifications',
  'ACCOUNT': 'Account',
  'LOG_OUT': 'Log Out',
  'CHOOSE_DELIMITER_CHARACTER': 'Choose delimiter character',
  'COMMA': 'Comma ,',
  'SEMICOLON': 'Semicolon ;',
  'OTHER': 'Other',
  'MISC': 'Misc',
  'TAXES': 'Taxes',
  'GROCERIES': 'Groceries',
  'HOUSEHOLD': 'Household',
  'EATING_OUT': 'Eating out',
  'FUN': 'Fun',
  'GIFTS': 'Gifts',
  'CLOTHING': 'Clothing',
  'CAR': 'Car',
  'TRANSPORT': 'Transport',
  'TRAVEL': 'Travel',
  'MEDICAL': 'Medical',
  'EDUCATION': 'Education',
  'BREAD': 'Bread',
  'MILK': 'Milk',
  'MEAT': 'Meat',
  'ELECTRICITY': 'Electricity',
  'WATER': 'Water',
  'RESTARURANT_EAT': 'Restaurant eat',
  'DISCO': 'Disco',
  'FOOTBALL': ' Football',
  'BIRTHDAY': 'Birthday',
  'CHRISTMAS': 'Christmas',
  'JACKET': 'Jacket',
  'T_SHIRT': 'T-Shirt',
  'GAS': 'Gas',
  'REPAIR': 'Repair',
  'MONTHLY_CARD': 'Monthly card',
  'TICKET': 'Ticket',
  'HOTEL_ROOM': 'Hotel room',
  'BAG': 'Bag',
  'MAP': 'Map',
  'MEDICINE': 'Medicine',
  'BOOK': 'Book',
  'COURSE': 'Course',
  'LOGIN_WITHOUT_ACCOUNT': 'Login without account',
  'OR': 'OR',
  'NAME_IS_REQUIRED': 'Name is required',
  'TITLE_IS_REQUIRED': 'Title is required',
  'INVALID_VALUE': 'Invalid value',
  'SALARY': 'Salary',
  'INVALID_SALARY': 'Invalid salary',
  'ADD_THRESHOLD': 'Add threshold',
  'CATEGORIES_THRESHOLD': 'Categories threshold',
  'DELETE': 'Delete',
  'ADD': 'Add',
  'REFRESH': 'Refresh',
  'GLOBAL_THRESHOLD': 'Global threshold',
  'SEE_CALENDAR': 'See calendar',
  'CALENDAR': 'Calendar',
  'THRESHOLD': 'Threshold',
  'NO_EVENTS_FOR_CATEGORY': 'No events for this category',
  'NO_NOTIFICAITONS': 'No notifications currently',
  'NOTIFICATIONS': 'Notifications',
  'DELETE_DIALOG_TITLE': 'Are you sure you want to delete this?',
  'DELETE_DIALOG_CONTENT': 'You cannot restore it if you proceed!',
  'YES': 'Yes',
  'NO': 'No',
  'ABOUT': 'About',
  'ABOUT_EBUDGIE': 'About EBudgie',
  'VERSION': 'Version',
  'WARNING': 'Warning!',
  'REALLY_CLOSE': 'Really close',
  'PASSED_THRESHOLD': 'Your have passed the threshold for {{category}}',
  'CLOSE_THRESHOLD': 'Your are really close to the threshold for {{category}}',
  'PASSED_GLOBAL_THRESHOLD': 'Your have passed the global threshold',
  'CLOSE_GLOBAL_THRESHOLD': 'Your are too close to the global threshold',
  'AT': 'at',
  'TYPE': 'Type',
  'EXPENSE': 'expense',
  'INCOME': 'income',
  'ELEMENT_ADDED': 'Element added',
  'ELEMENT_UPDATED': 'Element updated',
  'ELEMENT_DELETED': 'Element deleted',
  'NEW_ITEM_ADDED': 'New item added',
  'NEW_CATEGORY_ADDED': 'New category added',
  'NEW_INCOME_ADDED': 'New income added',
  'NEW_EXPENSE_ADDED': 'New expense added',
  'NEW_THRESHOLD_ADDED': 'New threshold added',
  'ITEM_UPDATED': 'Item updated',
  'CATEGORY_UPDATED': 'Category updated',
  'INCOME_UPDATED': 'Income updated',
  'EXPENSE_UPDATED': 'Expense updated',
  'SALARY_UPDATED': 'Salary updated',
  'ITEM_DELETED': 'Item deleted',
  'CATEGORY_DELETED': 'Category deleted',
  'INCOME_DELETED': 'Income deleted',
  'EXPENSE_DELETED': 'Expense deleted',
  'STATUS_SLOGAN_EXCELLENT': 'Nice! You are doing excellent {{emoji}} Keep the spirit!',
  'STATUS_SLOGAN_GOOD': 'Your plan is really in place for the month {{emoji}}',
  'STATUS_SLOGAN_NOT_GOOD': 'Be careful until the end of the month {{emoji}}',
  'STATUS_SLOGAN_POOR': 'Uh! You almost reach the limit {{emoji}}',
  'STATUS_SLOGAN_SORRY': 'Really sorry to see this {{emoji}} Plan better next time.',
  'BEGIN': 'Begin',
  'SKIP': 'Skip',
  'SLIDE_1_TITLE': 'Welcome to EBudgie',
  'SLIDE_1_DESCRIPTION': 'Keep track of you monthly budget in one place',
  'SLIDE_2_TITLE': 'Manage your money',
  'SLIDE_2_DESCRIPTION': 'See what is going on with your money at any time',
  'SLIDE_3_TITLE': 'Set limits',
  'SLIDE_3_DESCRIPTION': 'Plan the month and set tresholds for categories and general one',
  'SLIDE_4_TITLE': 'See results',
  'SLIDE_4_DESCRIPTION': 'Find out the results of your planing at the end of the month',
  'SLIDE_5_TITLE': 'Need the data outside of the app?',
  'SLIDE_5_DESCRIPTION': 'No problem. Export it with our intuitive flow',
  'SLIDE_6_TITLE': 'Ready to begin?',
  'SLIDE_6_DESCRIPTION': 'One last thing...for all these, you don\'t need an internet connection :)',
  'SYNCING': 'Syncing...',
  'LINK_CODE': 'Linking code',
};
<commit_msg>Fix typo<commit_after>export default {
  'HOME': 'Home',
  'ADD_ITEM': 'Add item',
  'ADD_CATEGORY': 'Add category',
  'MONTHLY_SALARY': 'Monthly salary',
  'ADD_INCOME': 'Add income',
  'ADD_EXPENSE': 'Add expense',
  'REPORTS': 'Reports',
  'SETTINGS': 'Settings',
  'DETAILED_REPORTS': 'Detailed reports',
  'REPORT_DOWNLOADER': 'Report downloader',
  'EDIT_EXPENSE': 'Edit expense',
  'EDIT_INCOME': 'Edit income',
  'EDIT_ITEM': 'Edit item',
  'EDIT_CATEGORY': 'Edit category',
  'CATEGORIES': 'Categories',
  'ITEMS': 'Items',
  'NOT_FOUND': 'Not found',
  'BUDGET': 'Budget',
  'BUDGET_LEFT': 'Budget left',
  'EXPENSES': 'Expenses',
  'INCOMES': 'Incomes',
  'PIE_CHART_DESCRIPTION': 'Monthly budget for {{month}}',
  'ADD_NEW_EXPENSE': 'Add new expense',
  'ADD_NEW_INCOME': 'Add new income',
  'SEE_CATEGORIES': 'See categories',
  'SEE_ITEMS': 'See items',
  'CHANGE_MONTHLY_SALARY': 'Change monthly salary',
  'SEE_REPORTS': 'See reports',
  'DRAWER_EXPENSES_DESCRIPTION': 'Current: {{expense}}{{currency}}',
  'DRAWER_INCOMES_DESCRIPTION': 'Current: {{income}}{{currency}}',
  'DRAWER_CATEGORIES_DESCRIPTION': '{{count}} categories now',
  'DRAWER_ITEMS_DESCRIPTION': '{{count}} items now',
  'DRAWER_SALARY_DESCRIPTION': 'Current {{salary}}{{currency}}',
  'COLOR': 'Color',
  'ICON': 'Icon',
  'TITLE': 'Title',
  'CANCEL': 'Cancel',
  'CONFIRM': 'Confirm',
  'SELECT_DATE': 'select date',
  'CATEGORY': 'Category',
  'ITEM': 'Item',
  'DATE': 'Date',
  'VALUE': 'Value',
  'NAME': 'Name',
  'CHOOSE': 'Choose',
  'NEXT': 'Next',
  'PREV': 'Prev',
  'SAVE': 'Save',
  'EDIT': 'Edit',
  'CATEGORY_CARD_COUNT': '{{count}} items',
  'STACK_BAR_DESCRIPTION': 'Monthly budget',
  'LOGIN_WITH_EMAIL': 'Login with Email',
  'LOGIN_WITH_PHONE': 'Login with Phone',
  'NOT_FOUND_DESCRIPTION': 'The resource could not be found',
  'FILE_SETTINGS': 'File Settings',
  'DECIMAL_SEPARATOR': 'Decimal separator',
  'DELIMITER_CHARACTER': 'Delimiter character',
  'EVENTS': 'Events',
  'REPORT_FOR_TIME_PERIOD': 'Report for time period',
  'FROM_DATE': 'From date',
  'TO_DATE': 'To date',
  'DOWNLOAD_CSV': 'Download CSV',
  'PAST_REPORTS': 'Past reports',
  'NO_PAST_REPORTS': 'No past reports',
  'CHOOSE_LANGUAGE': 'Choose Language',
  'CHOOSE_CURRENCY': 'Choose Currency',
  'AMERICAN_DOLLAR': 'American $',
  'BULGARIAN_LEV': 'Bulgarian lev',
  'APPEARANCE': 'Appearance',
  'LANGUAGE': 'Language',
  'CURRENCY': 'Currency',
  'DEVICE': 'Device',
  'PUSH_NOTIFICATIONS': 'Push Notifications',
  'ACCOUNT': 'Account',
  'LOG_OUT': 'Log Out',
  'CHOOSE_DELIMITER_CHARACTER': 'Choose delimiter character',
  'COMMA': 'Comma ,',
  'SEMICOLON': 'Semicolon ;',
  'OTHER': 'Other',
  'MISC': 'Misc',
  'TAXES': 'Taxes',
  'GROCERIES': 'Groceries',
  'HOUSEHOLD': 'Household',
  'EATING_OUT': 'Eating out',
  'FUN': 'Fun',
  'GIFTS': 'Gifts',
  'CLOTHING': 'Clothing',
  'CAR': 'Car',
  'TRANSPORT': 'Transport',
  'TRAVEL': 'Travel',
  'MEDICAL': 'Medical',
  'EDUCATION': 'Education',
  'BREAD': 'Bread',
  'MILK': 'Milk',
  'MEAT': 'Meat',
  'ELECTRICITY': 'Electricity',
  'WATER': 'Water',
  'RESTARURANT_EAT': 'Restaurant eat',
  'DISCO': 'Disco',
  'FOOTBALL': ' Football',
  'BIRTHDAY': 'Birthday',
  'CHRISTMAS': 'Christmas',
  'JACKET': 'Jacket',
  'T_SHIRT': 'T-Shirt',
  'GAS': 'Gas',
  'REPAIR': 'Repair',
  'MONTHLY_CARD': 'Monthly card',
  'TICKET': 'Ticket',
  'HOTEL_ROOM': 'Hotel room',
  'BAG': 'Bag',
  'MAP': 'Map',
  'MEDICINE': 'Medicine',
  'BOOK': 'Book',
  'COURSE': 'Course',
  'LOGIN_WITHOUT_ACCOUNT': 'Login without account',
  'OR': 'OR',
  'NAME_IS_REQUIRED': 'Name is required',
  'TITLE_IS_REQUIRED': 'Title is required',
  'INVALID_VALUE': 'Invalid value',
  'SALARY': 'Salary',
  'INVALID_SALARY': 'Invalid salary',
  'ADD_THRESHOLD': 'Add threshold',
  'CATEGORIES_THRESHOLD': 'Categories threshold',
  'DELETE': 'Delete',
  'ADD': 'Add',
  'REFRESH': 'Refresh',
  'GLOBAL_THRESHOLD': 'Global threshold',
  'SEE_CALENDAR': 'See calendar',
  'CALENDAR': 'Calendar',
  'THRESHOLD': 'Threshold',
  'NO_EVENTS_FOR_CATEGORY': 'No events for this category',
  'NO_NOTIFICAITONS': 'No notifications currently',
  'NOTIFICATIONS': 'Notifications',
  'DELETE_DIALOG_TITLE': 'Are you sure you want to delete this?',
  'DELETE_DIALOG_CONTENT': 'You cannot restore it if you proceed!',
  'YES': 'Yes',
  'NO': 'No',
  'ABOUT': 'About',
  'ABOUT_EBUDGIE': 'About EBudgie',
  'VERSION': 'Version',
  'WARNING': 'Warning!',
  'REALLY_CLOSE': 'Really close',
  'PASSED_THRESHOLD': 'Your have passed the threshold for {{category}}',
  'CLOSE_THRESHOLD': 'Your are really close to the threshold for {{category}}',
  'PASSED_GLOBAL_THRESHOLD': 'Your have passed the global threshold',
  'CLOSE_GLOBAL_THRESHOLD': 'Your are too close to the global threshold',
  'AT': 'at',
  'TYPE': 'Type',
  'EXPENSE': 'expense',
  'INCOME': 'income',
  'ELEMENT_ADDED': 'Element added',
  'ELEMENT_UPDATED': 'Element updated',
  'ELEMENT_DELETED': 'Element deleted',
  'NEW_ITEM_ADDED': 'New item added',
  'NEW_CATEGORY_ADDED': 'New category added',
  'NEW_INCOME_ADDED': 'New income added',
  'NEW_EXPENSE_ADDED': 'New expense added',
  'NEW_THRESHOLD_ADDED': 'New threshold added',
  'ITEM_UPDATED': 'Item updated',
  'CATEGORY_UPDATED': 'Category updated',
  'INCOME_UPDATED': 'Income updated',
  'EXPENSE_UPDATED': 'Expense updated',
  'SALARY_UPDATED': 'Salary updated',
  'ITEM_DELETED': 'Item deleted',
  'CATEGORY_DELETED': 'Category deleted',
  'INCOME_DELETED': 'Income deleted',
  'EXPENSE_DELETED': 'Expense deleted',
  'STATUS_SLOGAN_EXCELLENT': 'Nice! You are doing excellent {{emoji}} Keep the spirit!',
  'STATUS_SLOGAN_GOOD': 'Your plan is really in place for the month {{emoji}}',
  'STATUS_SLOGAN_NOT_GOOD': 'Be careful until the end of the month {{emoji}}',
  'STATUS_SLOGAN_POOR': 'Uh! You almost reach the limit {{emoji}}',
  'STATUS_SLOGAN_SORRY': 'Really sorry to see this {{emoji}} Plan better next time.',
  'BEGIN': 'Begin',
  'SKIP': 'Skip',
  'SLIDE_1_TITLE': 'Welcome to EBudgie',
  'SLIDE_1_DESCRIPTION': 'Keep track of you monthly budget in one place',
  'SLIDE_2_TITLE': 'Manage your money',
  'SLIDE_2_DESCRIPTION': 'See what is going on with your money at any time',
  'SLIDE_3_TITLE': 'Set limits',
  'SLIDE_3_DESCRIPTION': 'Plan the month and set tresholds for categories and general one',
  'SLIDE_4_TITLE': 'See results',
  'SLIDE_4_DESCRIPTION': 'Find out the results of your planing at the end of the month',
  'SLIDE_5_TITLE': 'Need the data outside of the app?',
  'SLIDE_5_DESCRIPTION': 'No problem. Export it with our intuitive flow',
  'SLIDE_6_TITLE': 'Ready to begin?',
  'SLIDE_6_DESCRIPTION': 'One last thing...for all these, you don\'t need an internet connection :)',
  'SYNCING': 'Syncing...',
  'LINK_CODE': 'Linking code',
};
<|endoftext|>


<commit_before>function newOceanStory(story, animate) {
  function newMap (engine, story, layerCatalog) {

    function normalizeSection(data) {
      data.view = {
        center: _.map(data.view.center, parseFloat),
        zoom: parseInt(data.view.zoom),
      };
    }
    story.sections.forEach(normalizeSection);

    var dataLayers = {};
    var visibleDataLayers = [];
    var currentBaseLayer;

    function defaultBaseLayer() {
      // return first base layer
      for (k in engine.baseLayers) {
        return k;
      }
    }

    function setBaseLayer(layer) {
      // return early if layer is unknown
      if (!engine.baseLayers.hasOwnProperty(layer)) {
        console.warn('attempt to set unknown base layer ' + layer);
        layer = currentBaseLayer || defaultBaseLayer();
      }

      if (layer == currentBaseLayer) return;

      console.debug('set base layer ' + layer);
      engine.showLayer(engine.baseLayers[layer], true);

      if (currentBaseLayer) {
        engine.hideLayer(engine.baseLayers[currentBaseLayer]);
      }

      currentBaseLayer = layer;

    }

    function fetchDataLayer(id) {
      if (!dataLayers.hasOwnProperty(id)) {
        if (!layerCatalog.hasOwnProperty(id)) {
          console.warn("Ignoring unknown layer id " + id);
          return false;
        }
        dataLayers[id] = engine.newDataLayer(layerCatalog[id]);
      }
      return dataLayers[id];
    }

    function setDataLayers(layers) {
      var layerKeys = Object.keys(layers)

      // trim unused layers
      _.each(_.difference(visibleDataLayers, layerKeys), function(id) {
        l = fetchDataLayer(id);
        if (l){
          engine.hideLayer(l);
        }
      });

      // add new layers
      _.each(_.difference(layerKeys, visibleDataLayers), function(id) {
        l = fetchDataLayer(id);
        if (l){
          engine.showLayer(l);
        }
      });
      visibleDataLayers = layerKeys;
    }

    return {
      goToSection: function(section) {
        if (section > story.sections.length - 1) {
          console.warn("Requested story section " + (section+1) + ", but only " + story.sections.length + " are present.")
          return;
        }
        var s = story.sections[section];

        engine.setView(s.view.center, s.view.zoom, function(){
          setBaseLayer(s.baseLayer);
          setDataLayers(s.dataLayers);
        });
      },
    };
  }

  var map = $('#map');

  var calcMapHeight = function(){
    var scrollTop = $(this).scrollTop();
    var viewHeight = $( this ).height();
    var startHeight = viewHeight - 60;
    var endHeight = viewHeight/2;
    var proportion = scrollTop/(startHeight - endHeight);
    if (proportion <= 0) {
      return startHeight;
    }
    if (proportion <= 1) {
      return endHeight*proportion + startHeight*(1-proportion);
    }
    return endHeight;
  };

  var setMapHeight = function() {
    var height = calcMapHeight();
    map.height(height);
    mapEngine.updateSize();
  };

  var oceanStoryMap;
  var mapEngine = ol3MapEngine('map', animate);

  $.getJSON("/data_manager/api/layers", function(data) {

    var dataLayers = _.indexBy(data, 'id');
    _.each(dataLayers, function(d) {
      if (d.layer_type == 'ArcRest') {
        hackyMarineCadastreLayerConversion(d);
      }
    })
    oceanStoryMap = newMap(mapEngine, story, dataLayers);

    scrollSpy('.content', 'a.anchor[id^=\'section-\']', function(sectionIndex){
      return oceanStoryMap.goToSection(sectionIndex);
    })

    setMapHeight();
    $(window).scroll(setMapHeight);
    $(window).resize(setMapHeight);
  });

}
<commit_msg>animate in-page scrolling in ocean story<commit_after>function newOceanStory(story, animate) {
  function newMap (engine, story, layerCatalog) {

    function normalizeSection(data) {
      data.view = {
        center: _.map(data.view.center, parseFloat),
        zoom: parseInt(data.view.zoom),
      };
    }
    story.sections.forEach(normalizeSection);

    var dataLayers = {};
    var visibleDataLayers = [];
    var currentBaseLayer;

    function defaultBaseLayer() {
      // return first base layer
      for (k in engine.baseLayers) {
        return k;
      }
    }

    function setBaseLayer(layer) {
      // return early if layer is unknown
      if (!engine.baseLayers.hasOwnProperty(layer)) {
        console.warn('attempt to set unknown base layer ' + layer);
        layer = currentBaseLayer || defaultBaseLayer();
      }

      if (layer == currentBaseLayer) return;

      console.debug('set base layer ' + layer);
      engine.showLayer(engine.baseLayers[layer], true);

      if (currentBaseLayer) {
        engine.hideLayer(engine.baseLayers[currentBaseLayer]);
      }

      currentBaseLayer = layer;

    }

    function fetchDataLayer(id) {
      if (!dataLayers.hasOwnProperty(id)) {
        if (!layerCatalog.hasOwnProperty(id)) {
          console.warn("Ignoring unknown layer id " + id);
          return false;
        }
        dataLayers[id] = engine.newDataLayer(layerCatalog[id]);
      }
      return dataLayers[id];
    }

    function setDataLayers(layers) {
      var layerKeys = Object.keys(layers)

      // trim unused layers
      _.each(_.difference(visibleDataLayers, layerKeys), function(id) {
        l = fetchDataLayer(id);
        if (l){
          engine.hideLayer(l);
        }
      });

      // add new layers
      _.each(_.difference(layerKeys, visibleDataLayers), function(id) {
        l = fetchDataLayer(id);
        if (l){
          engine.showLayer(l);
        }
      });
      visibleDataLayers = layerKeys;
    }

    return {
      goToSection: function(section) {
        if (section > story.sections.length - 1) {
          console.warn("Requested story section " + (section+1) + ", but only " + story.sections.length + " are present.")
          return;
        }
        var s = story.sections[section];

        engine.setView(s.view.center, s.view.zoom, function(){
          setBaseLayer(s.baseLayer);
          setDataLayers(s.dataLayers);
        });
      },
    };
  }

  var map = $('#map');

  var calcMapHeight = function(){
    var scrollTop = $(this).scrollTop();
    var viewHeight = $( this ).height();
    var startHeight = viewHeight - 60;
    var endHeight = viewHeight/2;
    var proportion = scrollTop/(startHeight - endHeight);
    if (proportion <= 0) {
      return startHeight;
    }
    if (proportion <= 1) {
      return endHeight*proportion + startHeight*(1-proportion);
    }
    return endHeight;
  };

  var setMapHeight = function() {
    var height = calcMapHeight();
    map.height(height);
    mapEngine.updateSize();
  };

  var oceanStoryMap;
  var mapEngine = ol3MapEngine('map', animate);

  if (animate) {
    // copied without modification from
    // http://www.paulund.co.uk/smooth-scroll-to-internal-links-with-jquery
    $(document).ready(function(){
      $('a[href^="#"]').on('click',function (e) {
        e.preventDefault();

        var target = this.hash;
        $target = $(target);

        $('html, body').stop().animate({
          'scrollTop': $target.offset().top
        }, 900, 'swing', function () {
          window.location.hash = target;
        });
      });
    });
  }

  $.getJSON("/data_manager/api/layers", function(data) {

    var dataLayers = _.indexBy(data, 'id');
    _.each(dataLayers, function(d) {
      if (d.layer_type == 'ArcRest') {
        hackyMarineCadastreLayerConversion(d);
      }
    })
    oceanStoryMap = newMap(mapEngine, story, dataLayers);

    scrollSpy('.content', 'a.anchor[id^=\'section-\']', function(sectionIndex){
      return oceanStoryMap.goToSection(sectionIndex);
    })

    setMapHeight();
    $(window).scroll(setMapHeight);
    $(window).resize(setMapHeight);
  });

}
<|endoftext|>


<commit_before>'use strict';

const cType = require('content-type');
const P = require('bluebird');
const mwUtil = require('./mwUtil');

// Utility function to split path & version suffix from a profile parameter.
function splitProfile(profile) {
    const match = /^(.*)\/([0-9.]+)$/.exec(profile);
    return {
        path: match[1],
        version: match[2],
    };
}

/**
 * Simple content type enforcement
 *
 * - Assumes that the first `produces` array entry is the latest.
 * - Repeats request with `no-cache` header set if the content-type does not
 *   match the latest version.
 */

function checkContentType(hyper, req, next, expectedContentType, responsePromise) {
    return responsePromise
    .then((res) => {
        // Do not check or re-render if the response was only rendered within
        // the last two minutes.
        if (res.headers && res.headers.etag
            && Date.now() - mwUtil.extractDateFromEtag(res.headers.etag) < 120000) {
            return res;
        }

        if (res.headers && res.headers === undefined) {
            delete res.headers['content-type'];
        }

        if (res.headers
                && res.headers['content-type']
                && res.headers['content-type'] !== expectedContentType) {
            // Parse the expected & response content type, and compare profiles.
            const expectedProfile = cType.parse(expectedContentType).parameters.profile;
            const actualProfile = cType.parse(res.headers['content-type']).parameters.profile;

            if (actualProfile && actualProfile !== expectedProfile) {
                if (!expectedProfile) {
                    return res;
                }
                // Check if actual content type is newer than the spec
                const actualProfileParts = splitProfile(actualProfile);
                const expectedProfileParts = splitProfile(expectedProfile);
                if (actualProfileParts.path === expectedProfileParts.path
                        && actualProfileParts.version > expectedProfileParts.version) {
                    return res;
                }
            }

            // Re-try request with no-cache header
            if (!mwUtil.isNoCacheRequest(req)) {
                req.headers['cache-control'] = 'no-cache';
                return checkContentType(hyper, req, next, expectedContentType, next(hyper, req));
            } else {
                // Log issue
                hyper.logger.log('warn/content-type/upgrade_failed', {
                    msg: 'Could not update the content-type',
                    expected: expectedContentType,
                    actual: res.headers['content-type']
                });
                // Disable response caching, as we aren't setting a vary
                // either.
                res.headers['cache-control'] = 'max-age=0, s-maxage=0';
            }
        }
        // Default: Just return.
        return res;
    });
}

module.exports = (hyper, req, next, options, specInfo) => {
    const produces = specInfo.spec.produces;
    return next(hyper, req)
    .then((res) => {
        // Most likely it's application/json for sections,
        // check if it exactly matches one of the list
        if (Array.isArray(produces)
            && produces.some(expected =>
                res.headers && res.headers['content-type'] === expected)) {
            return res;
        } else {
            const expectedContentType = options.expected
                || (Array.isArray(produces) && produces[0]);
            if (expectedContentType) {
                // Found a content type. Ensure that we return the latest profile.
                return checkContentType(hyper, req, next, expectedContentType, P.resolve(res));
            } else {
                return res;
            }
        }
    });
};
<commit_msg>Fix version issue when actual patch version >= 10<commit_after>'use strict';

const cType = require('content-type');
const P = require('bluebird');
const mwUtil = require('./mwUtil');
const semver = require('semver');

// Utility function to split path & version suffix from a profile parameter.
function splitProfile(profile) {
    const match = /^(.*)\/([0-9.]+)$/.exec(profile);
    return {
        path: match[1],
        version: match[2],
    };
}

/**
 * Simple content type enforcement
 *
 * - Assumes that the first `produces` array entry is the latest.
 * - Repeats request with `no-cache` header set if the content-type does not
 *   match the latest version.
 */

function checkContentType(hyper, req, next, expectedContentType, responsePromise) {
    return responsePromise
    .then((res) => {
        // Do not check or re-render if the response was only rendered within
        // the last two minutes.
        if (res.headers && res.headers.etag
            && Date.now() - mwUtil.extractDateFromEtag(res.headers.etag) < 120000) {
            return res;
        }

        if (res.headers && res.headers === undefined) {
            delete res.headers['content-type'];
        }

        if (res.headers
                && res.headers['content-type']
                && res.headers['content-type'] !== expectedContentType) {
            // Parse the expected & response content type, and compare profiles.
            const expectedProfile = cType.parse(expectedContentType).parameters.profile;
            const actualProfile = cType.parse(res.headers['content-type']).parameters.profile;

            if (actualProfile && actualProfile !== expectedProfile) {
                if (!expectedProfile) {
                    return res;
                }
                // Check if actual content type is newer than the spec
                const actualProfileParts = splitProfile(actualProfile);
                const expectedProfileParts = splitProfile(expectedProfile);
                if (actualProfileParts.path === expectedProfileParts.path
                        && semver.gt(actualProfileParts.version, expectedProfileParts.version)) {
                    return res;
                }
            }

            // Re-try request with no-cache header
            if (!mwUtil.isNoCacheRequest(req)) {
                req.headers['cache-control'] = 'no-cache';
                return checkContentType(hyper, req, next, expectedContentType, next(hyper, req));
            } else {
                // Log issue
                hyper.logger.log('warn/content-type/upgrade_failed', {
                    msg: 'Could not update the content-type',
                    expected: expectedContentType,
                    actual: res.headers['content-type']
                });
                // Disable response caching, as we aren't setting a vary
                // either.
                res.headers['cache-control'] = 'max-age=0, s-maxage=0';
            }
        }
        // Default: Just return.
        return res;
    });
}

module.exports = (hyper, req, next, options, specInfo) => {
    const produces = specInfo.spec.produces;
    return next(hyper, req)
    .then((res) => {
        // Most likely it's application/json for sections,
        // check if it exactly matches one of the list
        if (Array.isArray(produces)
            && produces.some(expected =>
                res.headers && res.headers['content-type'] === expected)) {
            return res;
        } else {
            const expectedContentType = options.expected
                || (Array.isArray(produces) && produces[0]);
            if (expectedContentType) {
                // Found a content type. Ensure that we return the latest profile.
                return checkContentType(hyper, req, next, expectedContentType, P.resolve(res));
            } else {
                return res;
            }
        }
    });
};
<|endoftext|>


<commit_before>/* @flow */
import { flatMap } from 'lodash-es'
import { assign } from 'lodash-es'
import { isEqual } from 'lodash-es'
import { pick } from 'lodash-es'
import { findIndex } from 'lodash-es'

import Grande from './presets/Grande'
import Juggler from './presets/Juggler'
import Sampler from './presets/Sampler'
import Short from './presets/Short'
import Tall from './presets/Tall'

import Component from '../Component'
import type { MidiMessage } from '../Launchpad'
import { Buttons, Colors } from '../Launchpad'

import { modes, retainAttackMode } from './ModifierSidebar'
import type { Preset } from './Preset'
import type { Modifier } from './ModifierSidebar'
import type { ControlComponentBuilder } from '../Controls/ControlComponent'
import type MidiComponent, { MidiComponentBuilder } from '../Controls/MidiComponent'

type Size = 'short' | 'tall' | 'grande'
type Block = {|
  offset: [number, number],
  size: Size,
  channel: number,
  index: number
|}

type Diff = [Block[], Block[]]

const initialChannels = [0, 1]

const onMidi = (selectorBar, channel, modifier: Modifier) => retainAttackMode(modifier, (mode, { value }: MidiMessage) => {
  const selected = selectorBar.getChord()
  modes(mode,
    () => {
      if (!value && selected.length) {
        const diff = reorganize(selectorBar.getLayout(), selected)
        selectorBar.updateLayout(diff)
        selectorBar.removeChord()
      } else if (value) {
        selectorBar.addToChord(channel)
      }
    },
    () => {
      if (value) {
        if (selected.length) selectorBar.removeChord()
        const diff = cycle(channel, selectorBar.getLayout(), 1)
        selectorBar.updateLayout(diff)
      }
    },
    () => {
      if (value) {
        if (selected.length) selectorBar.removeChord()
        const diff = cycle(channel, selectorBar.getLayout(), -1)
        selectorBar.updateLayout(diff)
      }
    }
  )
})

class SelectorBar extends Component {
  id: string
  bindings: [MidiComponent, Function][]
  controlComponentBuilder: ControlComponentBuilder
  midiComponentBuilder: MidiComponentBuilder
  modifier: Modifier
  chord: number[]
  layout: { [key: string]: Block }
  mountedPresets: { [key: number]: Preset }

  static buttons = [ 'up', 'down', 'left', 'right', 'session', 'user1', 'user2', 'mixer' ]

  static channels = [0, 1, 2, 3, 4, 5, 6, 7]

  constructor (controlComponentBuilder: ControlComponentBuilder, midiComponentBuilder: MidiComponentBuilder, modifier: Modifier, id: string) {
    super()
    this.id = id
    this.bindings = SelectorBar.buttons
      .map((v, i) => {
        const binding = midiComponentBuilder(Buttons[v])
        return [binding, onMidi(this, i, modifier)]
      })
    this.controlComponentBuilder = controlComponentBuilder
    this.midiComponentBuilder = midiComponentBuilder
    this.modifier = modifier
    this.chord = []
    this.layout = { }
    this.mountedPresets = { }
  }

  getLayout () {
    const res = []
    for (const k in this.layout) {
      res.push(this.layout[k])
    }
    return res
  }

  updateLayout (diff: Diff) {
    const removedChannels = diff[0].map((block) => block.channel)
    removedChannels.forEach((ch) => {
      delete this.layout[String(ch)]
      this.bindings[ch][0].button.sendColor(Colors.black)
      this.mountedPresets[ch].unmount()
    })
    const addedBlocks = diff[1]
    addedBlocks.forEach((block) => {
      this.layout[String(block.channel)] = block
      if (block.index) {
        this.bindings[block.channel][0].button.sendColor(Colors.hi_orange)
      } else {
        this.bindings[block.channel][0].button.sendColor(Colors.hi_green)
      }
      this.mountedPresets[block.channel] = cycled[block.size][block.index](this.controlComponentBuilder)(this.midiComponentBuilder)(this.modifier)(`${this.id}.deck.${block.channel}`)(block.channel)(block.offset)
      this.mountedPresets[block.channel].mount()
    })
  }

  removeChord () {
    const layout = this.getLayout()
    this.chord.forEach((ch) => {
      const found = findIndex(layout, (b) => b.channel === ch)
      if (found === -1) {
        this.bindings[ch][0].button.sendColor(Colors.black)
      } else {
        const block = layout[found]
        if (block.index) {
          this.bindings[ch][0].button.sendColor(Colors.hi_orange)
        } else {
          this.bindings[ch][0].button.sendColor(Colors.hi_green)
        }
      }
      this.chord = []
    })
  }

  addToChord (channel: number) {
    if (this.chord.length === 4) {
      const rem = this.chord.shift()
      const found = findIndex(this.layout, (b) => b.channel === rem)
      if (found === -1) {
        this.bindings[rem][0].button.sendColor(Colors.black)
      } else {
        const layout = this.layout[found]
        if (layout.index) {
          this.bindings[rem][0].button.sendColor(Colors.hi_orange)
        } else {
          this.bindings[rem][0].button.sendColor(Colors.hi_green)
        }
      }
    }
    this.chord.push(channel)
    this.bindings[channel][0].button.sendColor(Colors.hi_red)
  }

  getChord () {
    return this.chord
  }

  onMount () {
    this.bindings.forEach(([binding, midi]) => {
      binding.mount()
      binding.on('midi', midi)
    })
  }

  onUnmount () {
    this.bindings.forEach(([binding, midi]) => {
      binding.removeListener('midi', midi)
      binding.unmount()
    })
  }
}

export default class Layout extends Component {
  selectorBar: SelectorBar

  constructor (controlComponentBuilder: ControlComponentBuilder, midiComponentBuilder: MidiComponentBuilder, modifier: Modifier, id: string) {
    super()
    this.selectorBar = new SelectorBar(controlComponentBuilder, midiComponentBuilder, modifier, `${id}.selectorBar`)
  }
  onMount () {
    this.selectorBar.mount()
    const diff = reorganize([], initialChannels)
    this.selectorBar.updateLayout(diff)
  }
  onUnmount () {
    const diff = reorganize(this.selectorBar.getLayout(), [])
    this.selectorBar.updateLayout(diff)
    this.selectorBar.unmount()
  }
}

export const makeLayout =
  (controlComponentBuilder: ControlComponentBuilder) =>
    (midiComponentBuilder: MidiComponentBuilder) =>
      (modifier: Modifier) =>
        (id: string) => {
          return new Layout(controlComponentBuilder, midiComponentBuilder, modifier, `${id}.selectorBar`)
        }

const offsets = [
  [0, 0],
  [4, 0],
  [0, 4],
  [4, 4]
]

const presets = {
  grande: [ Grande ],
  tall: [ Tall, Juggler ],
  short: [ Short, Sampler ]
}

const cycled = {
  'grande': flatMap(pick(presets, ['grande', 'tall', 'short']), (x) => x),
  'tall': flatMap(pick(presets, ['tall', 'short']), (x) => x),
  'short': flatMap(pick(presets, ['short']), (x) => x)
}

const reorganize = (current: Block[], selectedChannels: number[]): Diff => {
  const next = selectedChannels.length <= 1
    ? [{
      offset: offsets[0],
      size: 'grande',
      channel: selectedChannels[0],
      index: 0
    }]
    : selectedChannels.length <= 2
      ? [{
        offset: offsets[0],
        size: 'tall',
        channel: selectedChannels[0],
        index: 0
      }, {
        offset: offsets[1],
        size: 'tall',
        channel: selectedChannels[1],
        index: 0
      }]
      : selectedChannels.length <= 3
        ? [{
          offset: offsets[0],
          size: 'tall',
          channel: selectedChannels[0],
          index: 0
        }, {
          offset: offsets[1],
          size: 'short',
          channel: selectedChannels[1],
          index: 0
        }, {
          offset: offsets[3],
          size: 'short',
          channel: selectedChannels[2],
          index: 0
        }]
        : [{
          offset: offsets[0],
          size: 'short',
          channel: selectedChannels[0],
          index: 0
        }, {
          offset: offsets[1],
          size: 'short',
          channel: selectedChannels[1],
          index: 0
        }, {
          offset: offsets[2],
          size: 'short',
          channel: selectedChannels[2],
          index: 0
        }, {
          offset: offsets[3],
          size: 'short',
          channel: selectedChannels[3],
          index: 0
        }]
  return current.reduce((diff, block) => {
    const [neg, pos] = diff
    const matched = findIndex(pos, (b) => isEqual(block, b))
    return matched === -1
    ? [neg.concat([block]), pos]
    : [neg, pos.slice(0, matched).concat(pos.slice(matched + 1, pos.length))]
  }, [[], next])
}

const cycle = (channel: number, current: Block[], dir: 1 | -1): Diff => {
  const matched = findIndex(current, (block) => block.channel === channel)
  if (matched === -1) {
    return [[], []]
  }
  const nextIndex = (current[matched].index + dir) % cycled[current[matched].size].length
  if (nextIndex === current[matched].index) {
    return [[], []]
  }
  return [[current[matched]], [assign({}, current[matched], { index: nextIndex })]]
}
<commit_msg>fix linter errors<commit_after>/* @flow */
import { assign, isEqual, findIndex, flatMap, pick } from 'lodash-es'

import Grande from './presets/Grande'
import Juggler from './presets/Juggler'
import Sampler from './presets/Sampler'
import Short from './presets/Short'
import Tall from './presets/Tall'

import Component from '../Component'
import type { MidiMessage } from '../Launchpad'
import { Buttons, Colors } from '../Launchpad'

import { modes, retainAttackMode } from './ModifierSidebar'
import type { Preset } from './Preset'
import type { Modifier } from './ModifierSidebar'
import type { ControlComponentBuilder } from '../Controls/ControlComponent'
import type MidiComponent, { MidiComponentBuilder } from '../Controls/MidiComponent'

type Size = 'short' | 'tall' | 'grande'
type Block = {|
  offset: [number, number],
  size: Size,
  channel: number,
  index: number
|}

type Diff = [Block[], Block[]]

const initialChannels = [0, 1]

const onMidi = (selectorBar, channel, modifier: Modifier) => retainAttackMode(modifier, (mode, { value }: MidiMessage) => {
  const selected = selectorBar.getChord()
  modes(mode,
    () => {
      if (!value && selected.length) {
        const diff = reorganize(selectorBar.getLayout(), selected)
        selectorBar.updateLayout(diff)
        selectorBar.removeChord()
      } else if (value) {
        selectorBar.addToChord(channel)
      }
    },
    () => {
      if (value) {
        if (selected.length) selectorBar.removeChord()
        const diff = cycle(channel, selectorBar.getLayout(), 1)
        selectorBar.updateLayout(diff)
      }
    },
    () => {
      if (value) {
        if (selected.length) selectorBar.removeChord()
        const diff = cycle(channel, selectorBar.getLayout(), -1)
        selectorBar.updateLayout(diff)
      }
    }
  )
})

class SelectorBar extends Component {
  id: string
  bindings: [MidiComponent, Function][]
  controlComponentBuilder: ControlComponentBuilder
  midiComponentBuilder: MidiComponentBuilder
  modifier: Modifier
  chord: number[]
  layout: { [key: string]: Block }
  mountedPresets: { [key: number]: Preset }

  static buttons = [ 'up', 'down', 'left', 'right', 'session', 'user1', 'user2', 'mixer' ]

  static channels = [0, 1, 2, 3, 4, 5, 6, 7]

  constructor (controlComponentBuilder: ControlComponentBuilder, midiComponentBuilder: MidiComponentBuilder, modifier: Modifier, id: string) {
    super()
    this.id = id
    this.bindings = SelectorBar.buttons
      .map((v, i) => {
        const binding = midiComponentBuilder(Buttons[v])
        return [binding, onMidi(this, i, modifier)]
      })
    this.controlComponentBuilder = controlComponentBuilder
    this.midiComponentBuilder = midiComponentBuilder
    this.modifier = modifier
    this.chord = []
    this.layout = { }
    this.mountedPresets = { }
  }

  getLayout () {
    const res = []
    for (const k in this.layout) {
      res.push(this.layout[k])
    }
    return res
  }

  updateLayout (diff: Diff) {
    const removedChannels = diff[0].map((block) => block.channel)
    removedChannels.forEach((ch) => {
      delete this.layout[String(ch)]
      this.bindings[ch][0].button.sendColor(Colors.black)
      this.mountedPresets[ch].unmount()
    })
    const addedBlocks = diff[1]
    addedBlocks.forEach((block) => {
      this.layout[String(block.channel)] = block
      if (block.index) {
        this.bindings[block.channel][0].button.sendColor(Colors.hi_orange)
      } else {
        this.bindings[block.channel][0].button.sendColor(Colors.hi_green)
      }
      this.mountedPresets[block.channel] = cycled[block.size][block.index](this.controlComponentBuilder)(this.midiComponentBuilder)(this.modifier)(`${this.id}.deck.${block.channel}`)(block.channel)(block.offset)
      this.mountedPresets[block.channel].mount()
    })
  }

  removeChord () {
    const layout = this.getLayout()
    this.chord.forEach((ch) => {
      const found = findIndex(layout, (b) => b.channel === ch)
      if (found === -1) {
        this.bindings[ch][0].button.sendColor(Colors.black)
      } else {
        const block = layout[found]
        if (block.index) {
          this.bindings[ch][0].button.sendColor(Colors.hi_orange)
        } else {
          this.bindings[ch][0].button.sendColor(Colors.hi_green)
        }
      }
      this.chord = []
    })
  }

  addToChord (channel: number) {
    if (this.chord.length === 4) {
      const rem = this.chord.shift()
      const found = findIndex(this.layout, (b) => b.channel === rem)
      if (found === -1) {
        this.bindings[rem][0].button.sendColor(Colors.black)
      } else {
        const layout = this.layout[found]
        if (layout.index) {
          this.bindings[rem][0].button.sendColor(Colors.hi_orange)
        } else {
          this.bindings[rem][0].button.sendColor(Colors.hi_green)
        }
      }
    }
    this.chord.push(channel)
    this.bindings[channel][0].button.sendColor(Colors.hi_red)
  }

  getChord () {
    return this.chord
  }

  onMount () {
    this.bindings.forEach(([binding, midi]) => {
      binding.mount()
      binding.on('midi', midi)
    })
  }

  onUnmount () {
    this.bindings.forEach(([binding, midi]) => {
      binding.removeListener('midi', midi)
      binding.unmount()
    })
  }
}

export default class Layout extends Component {
  selectorBar: SelectorBar

  constructor (controlComponentBuilder: ControlComponentBuilder, midiComponentBuilder: MidiComponentBuilder, modifier: Modifier, id: string) {
    super()
    this.selectorBar = new SelectorBar(controlComponentBuilder, midiComponentBuilder, modifier, `${id}.selectorBar`)
  }
  onMount () {
    this.selectorBar.mount()
    const diff = reorganize([], initialChannels)
    this.selectorBar.updateLayout(diff)
  }
  onUnmount () {
    const diff = reorganize(this.selectorBar.getLayout(), [])
    this.selectorBar.updateLayout(diff)
    this.selectorBar.unmount()
  }
}

export const makeLayout =
  (controlComponentBuilder: ControlComponentBuilder) =>
    (midiComponentBuilder: MidiComponentBuilder) =>
      (modifier: Modifier) =>
        (id: string) => {
          return new Layout(controlComponentBuilder, midiComponentBuilder, modifier, `${id}.selectorBar`)
        }

const offsets = [
  [0, 0],
  [4, 0],
  [0, 4],
  [4, 4]
]

const presets = {
  grande: [ Grande ],
  tall: [ Tall, Juggler ],
  short: [ Short, Sampler ]
}

const cycled = {
  'grande': flatMap(pick(presets, ['grande', 'tall', 'short']), (x) => x),
  'tall': flatMap(pick(presets, ['tall', 'short']), (x) => x),
  'short': flatMap(pick(presets, ['short']), (x) => x)
}

const reorganize = (current: Block[], selectedChannels: number[]): Diff => {
  const next = selectedChannels.length <= 1
    ? [{
      offset: offsets[0],
      size: 'grande',
      channel: selectedChannels[0],
      index: 0
    }]
    : selectedChannels.length <= 2
      ? [{
        offset: offsets[0],
        size: 'tall',
        channel: selectedChannels[0],
        index: 0
      }, {
        offset: offsets[1],
        size: 'tall',
        channel: selectedChannels[1],
        index: 0
      }]
      : selectedChannels.length <= 3
        ? [{
          offset: offsets[0],
          size: 'tall',
          channel: selectedChannels[0],
          index: 0
        }, {
          offset: offsets[1],
          size: 'short',
          channel: selectedChannels[1],
          index: 0
        }, {
          offset: offsets[3],
          size: 'short',
          channel: selectedChannels[2],
          index: 0
        }]
        : [{
          offset: offsets[0],
          size: 'short',
          channel: selectedChannels[0],
          index: 0
        }, {
          offset: offsets[1],
          size: 'short',
          channel: selectedChannels[1],
          index: 0
        }, {
          offset: offsets[2],
          size: 'short',
          channel: selectedChannels[2],
          index: 0
        }, {
          offset: offsets[3],
          size: 'short',
          channel: selectedChannels[3],
          index: 0
        }]
  return current.reduce((diff, block) => {
    const [neg, pos] = diff
    const matched = findIndex(pos, (b) => isEqual(block, b))
    return matched === -1
    ? [neg.concat([block]), pos]
    : [neg, pos.slice(0, matched).concat(pos.slice(matched + 1, pos.length))]
  }, [[], next])
}

const cycle = (channel: number, current: Block[], dir: 1 | -1): Diff => {
  const matched = findIndex(current, (block) => block.channel === channel)
  if (matched === -1) {
    return [[], []]
  }
  const nextIndex = (current[matched].index + dir) % cycled[current[matched].size].length
  if (nextIndex === current[matched].index) {
    return [[], []]
  }
  return [[current[matched]], [assign({}, current[matched], { index: nextIndex })]]
}
<|endoftext|>


<commit_before>import React, {useContext} from 'react';
import {StyleSheet, SafeAreaView, Text, Image, FlatList, View, TouchableHighlight} from 'react-native';
import {NavigationContext} from 'navigation-react';
import {CoordinatorLayout, NavigationBar, TabBar, TabBarItem} from 'navigation-react-native';

const Follow = ({account: {id, name, logo}}) => {
  const {stateNavigator} = useContext(NavigationContext);
  return (
    <TouchableHighlight
      underlayColor="white"
      onPress={() => {
        stateNavigator.navigate('timeline', {id});
      }}>
      <View style={styles.follow}>
        <View>
          <Image style={styles.logo} source={logo} />
          <View style={styles.details}>
          <Text style={styles.name}>{name}</Text>
          <Text>followed you.</Text>
          </View>
        </View>
      </View>
    </TouchableHighlight>
  );
}

const Tweet = ({id, account: {id: accountId, name, logo}, text}) => {
  const {stateNavigator} = useContext(NavigationContext);
  return (
    <TouchableHighlight
      underlayColor="white"
      onPress={() => {
        stateNavigator.navigate('tweet', {id});
      }}>
      <View style={styles.tweet}>
        <TouchableHighlight
          underlayColor="white"
          onPress={() => {
            stateNavigator.navigate('timeline', {id: accountId});
        }}>
          <Image style={styles.tweetLogo} source={logo} />
        </TouchableHighlight>
        <View style={styles.tweetDetails}>
          <Text style={styles.tweetName}>{name}</Text>
          <Text>{text}</Text>
        </View>
      </View>
    </TouchableHighlight>
  );
}

export default ({notifications}) => {
  return (
    <SafeAreaView style={{flex: 1}}>
      <CoordinatorLayout>
        <NavigationBar
          title="Notifications"
          barTintColor={Platform.OS === 'android' ? 'rgba(255,255,255, 0)' : null}>
          <TabBar selectedTintColor="#1da1f2" />
        </NavigationBar>
        <TabBar
          segmented={true}
          barTintColor={Platform.OS === 'ios' ? '#1da1f2' : null}
          unselectedTintColor="#fff"
          selectedTintColor="#1da1f2">
          <TabBarItem title="All">
            <FlatList
              data={notifications}
              keyExtractor={(_item, index) => '' + index}
              nestedScrollEnabled={true}
              style={styles.view}
              renderItem={({item}) => (
                item.follow ? <Follow {...item} /> : <Tweet {...item} />
              )} />
          </TabBarItem>
          <TabBarItem title="Mentions">
            <FlatList
              data={notifications.filter(({mention}) => mention)}
              keyExtractor={(_item, index) => '' + index}
              nestedScrollEnabled={true}
              style={styles.view}
              renderItem={({item}) => <Tweet {...item} />} />
          </TabBarItem>
        </TabBar>
      </CoordinatorLayout>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  view: {
    paddingLeft: 20,
    paddingRight: 20,
    backgroundColor: '#fff',
  },
  follow: {
    flexDirection: 'row',
    paddingTop: 10,
    paddingBottom: 15,
    borderBottomColor: '#ccd6dd',
    borderBottomWidth: 1,
  },
  icon: {
    marginRight: 10,
  },
  details: {
    flexDirection: 'row',
  },
  name: {
    fontWeight: 'bold',
    paddingRight: 4,
  },
  logo: {
    width: 35,
    height: 35,
    borderRadius: 17.5,
    marginRight: 5,
    marginBottom: 10,
  },
  tweet: {
    flexDirection: 'row',
    paddingTop: 10,
    paddingBottom: 10,
    borderBottomColor: '#ccd6dd',
    borderBottomWidth: 1,
  },
  tweetDetails: {
    flex: 1,
  },
  tweetName: {
    fontWeight: 'bold',
  },
  tweetLogo: {
    width: 50,
    height: 50,
    borderRadius: 25,
    marginRight: 8,
  },
});
<commit_msg>Used default segmented control colours on ios<commit_after>import React, {useContext} from 'react';
import {StyleSheet, SafeAreaView, Text, Image, FlatList, View, TouchableHighlight} from 'react-native';
import {NavigationContext} from 'navigation-react';
import {CoordinatorLayout, NavigationBar, TabBar, TabBarItem} from 'navigation-react-native';

const Follow = ({account: {id, name, logo}}) => {
  const {stateNavigator} = useContext(NavigationContext);
  return (
    <TouchableHighlight
      underlayColor="white"
      onPress={() => {
        stateNavigator.navigate('timeline', {id});
      }}>
      <View style={styles.follow}>
        <View>
          <Image style={styles.logo} source={logo} />
          <View style={styles.details}>
          <Text style={styles.name}>{name}</Text>
          <Text>followed you.</Text>
          </View>
        </View>
      </View>
    </TouchableHighlight>
  );
}

const Tweet = ({id, account: {id: accountId, name, logo}, text}) => {
  const {stateNavigator} = useContext(NavigationContext);
  return (
    <TouchableHighlight
      underlayColor="white"
      onPress={() => {
        stateNavigator.navigate('tweet', {id});
      }}>
      <View style={styles.tweet}>
        <TouchableHighlight
          underlayColor="white"
          onPress={() => {
            stateNavigator.navigate('timeline', {id: accountId});
        }}>
          <Image style={styles.tweetLogo} source={logo} />
        </TouchableHighlight>
        <View style={styles.tweetDetails}>
          <Text style={styles.tweetName}>{name}</Text>
          <Text>{text}</Text>
        </View>
      </View>
    </TouchableHighlight>
  );
}

export default ({notifications}) => {
  return (
    <SafeAreaView style={{flex: 1}}>
      <CoordinatorLayout>
        <NavigationBar
          title="Notifications"
          barTintColor={Platform.OS === 'android' ? 'rgba(255,255,255, 0)' : null}>
          <TabBar selectedTintColor="#1da1f2" />
        </NavigationBar>
        <TabBar segmented={true}>
          <TabBarItem title="All">
            <FlatList
              data={notifications}
              keyExtractor={(_item, index) => '' + index}
              nestedScrollEnabled={true}
              style={styles.view}
              renderItem={({item}) => (
                item.follow ? <Follow {...item} /> : <Tweet {...item} />
              )} />
          </TabBarItem>
          <TabBarItem title="Mentions">
            <FlatList
              data={notifications.filter(({mention}) => mention)}
              keyExtractor={(_item, index) => '' + index}
              nestedScrollEnabled={true}
              style={styles.view}
              renderItem={({item}) => <Tweet {...item} />} />
          </TabBarItem>
        </TabBar>
      </CoordinatorLayout>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  view: {
    paddingLeft: 20,
    paddingRight: 20,
    backgroundColor: '#fff',
  },
  follow: {
    flexDirection: 'row',
    paddingTop: 10,
    paddingBottom: 15,
    borderBottomColor: '#ccd6dd',
    borderBottomWidth: 1,
  },
  icon: {
    marginRight: 10,
  },
  details: {
    flexDirection: 'row',
  },
  name: {
    fontWeight: 'bold',
    paddingRight: 4,
  },
  logo: {
    width: 35,
    height: 35,
    borderRadius: 17.5,
    marginRight: 5,
    marginBottom: 10,
  },
  tweet: {
    flexDirection: 'row',
    paddingTop: 10,
    paddingBottom: 10,
    borderBottomColor: '#ccd6dd',
    borderBottomWidth: 1,
  },
  tweetDetails: {
    flex: 1,
  },
  tweetName: {
    fontWeight: 'bold',
  },
  tweetLogo: {
    width: 50,
    height: 50,
    borderRadius: 25,
    marginRight: 8,
  },
});
<|endoftext|>