(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
  'use strict';

  const i18n = require('../../../libs/spotify-i18n')({
    'cs': require('./json/cs.json'), // 2 Czech
    'de': require('./json/de.json'), // 3 German
    'el': require('./json/el.json'), // 4 Greek
    'en': require('./json/en.json'), // 5 English DONE!
    'es': require('./json/es.json'), // 6 Spanish
    'es-419': require('./json/es-419.json'), // 7 Latin American Spanish
    'fi': require('./json/fi.json'),  // 8 Finnish
    'fr': require('./json/fr.json'), // 9 French
    'fr-CA': require('./json/fr-CA.json'), // 10 Canadian French
    'hu': require('./json/hu.json'), // 11 Hungarian
    'id': require('./json/id.json'), // 12 Indonesian
    'it': require('./json/it.json'), // 13 Italian
    'ja': require('./json/ja.json'), // 14 Japanese
    'nl': require('./json/nl.json'), // 15 Dutch
    'pl': require('./json/pl.json'), // 16 Polish
    'pt-BR': require('./json/pt-BR.json'), // 17 Brazilian Portuguese
    'sv': require('./json/sv.json'), // 18 Swedish
    'th': require('./json/th.json'), // 19 Thai
    'tr': require('./json/tr.json'), // 20 Turkish
    'vi': require('./json/vi.json'), // 21 Vietnamese DONE!
    'zh-Hant': require('./json/zh-Hant.json'), // 22 Traditional Chinese
    'zsm': require('./json/zsm.json') // 23 Malaysian
  });

  module.exports = i18n;
  
  },{"../../../libs/spotify-i18n":312,"./json/cs.json":2,"./json/de.json":3,"./json/el.json":4,"./json/en.json":5,"./json/es-419.json":6,"./json/es.json":7,"./json/fi.json":8,"./json/fr-CA.json":9,"./json/fr.json":10,"./json/hu.json":11,"./json/id.json":12,"./json/it.json":13,"./json/ja.json":14,"./json/nl.json":15,"./json/pl.json":16,"./json/pt-BR.json":17,"./json/sv.json":18,"./json/th.json":19,"./json/tr.json":20,"./json/vi.json":21,"./json/zh-Hant.json":22,"./json/zsm.json":23}],2:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} sledující",
    "info.followers_count_plural": "{0} sledujících",
    "info.empty_subreddit":"Zatím žádné skladby",
    "error.unknown_error_first": "Došlo k neznámé chybě.",
    "error.unknown_error_second": "Zkus to prosím později.",
    "settings": "Nastavení",
    "cancle":"Zrušit",
    "save":"Uložit",
    "setting.options.title": "Možnosti",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",  
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],3:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} Follower",
    "info.followers_count_plural": "{0} Follower",
    "info.empty_subreddit":"Noch nichts da",
    "error.unknown_error_first": "Ein unbekannter Fehler ist aufgetreten.",
    "error.unknown_error_second": "Bitte versuch es später noch mal.",
    "settings": "Einstellungen",
    "cancle":"Abbrechen",
    "save":"Speichern",
    "setting.options.title": "Optionen",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],4:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} οπαδός",
    "info.followers_count_plural": "{0} οπαδοί",
    "info.empty_subreddit":"Ακόμα δεν υπάρχει τίποτα εδώ",
    "error.unknown_error_first": "Προέκυψε άγνωστο σφάλμα.",
    "error.unknown_error_second": "Δοκίμασε ξανά αργότερα.",
    "settings": "Ρυθμίσεις",
    "cancle":"Ακύρωση",
    "save":"Αποθήκευση",
    "setting.options.title": "Επιλογές",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],5:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} follower",
    "info.followers_count_plural": "{0} followers",
    "info.empty_subreddit":"Nothing here yet",
    "error.unknown_error_first": "There was an unknown error.",
    "error.unknown_error_second": "Please try again later.",
    "settings": "Settings",
    "cancle":"Cancel",
    "save":"Save", 
    "setting.options.title": "Options",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],6:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} seguidor",
    "info.followers_count_plural": "{0} seguidores",
    "info.empty_subreddit":"Aquí todavía no hay nada",
    "error.unknown_error_first": "Hubo un error desconocido.",
    "error.unknown_error_second": "Inténtalo de nuevo más adelante.",
    "settings": "Preferencias",
    "cancle":"Cancelar",
    "save":"Guardar",
    "setting.options.title": "Opciones",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],7:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} seguidor",
    "info.followers_count_plural": "{0} seguidores",
    "info.empty_subreddit":"Todavía no hay nada para mostrar.",
    "error.unknown_error_first": "Hubo un error desconocido.",
    "error.unknown_error_second": "Prueba de nuevo más tarde.",
    "settings": "Configuración",
    "cancle":"Cancelar",
    "save":"Guardar",
    "setting.options.title": "Opciones",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],8:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} seuraaja",
    "info.followers_count_plural": "{0} seuraajaa",
    "info.empty_subreddit":"Täällä ei vielä ole mitään",
    "error.unknown_error_first": "Ilmeni tuntematon virhe.",
    "error.unknown_error_second": "Yritä myöhemmin uudelleen.",
    "settings": "Asetukset",
    "cancle":"Peruuta",
    "save":"Tallenna",
    "setting.options.title": "Vaihtoehdot",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],9:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} abonné",
    "info.followers_count_plural": "{0} abonnés",
    "info.empty_subreddit":"Aucun contenu affiché pour l'instant.",
    "error.unknown_error_first": "Une erreur inconnue s'est produite.",
    "error.unknown_error_second": "Réessayez plus tard.",
    "settings": "Préférences",
    "cancle":"Annuler",
    "save":"Sauvegarder",
    "setting.options.title": "Options",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],10:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} abonné",
    "info.followers_count_plural": "{0} abonnés",
    "info.empty_subreddit":"Rien de nouveau ici pour l'instant",
    "error.unknown_error_first": "Une erreur inconnue s'est produite.",
    "error.unknown_error_second": "Merci de réessayer plus tard.",
    "settings": "Préférences",
    "cancle":"Annuler",
    "save":"Sauvegarder",
    "setting.options.title": "Options",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],11:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} követő",
    "info.followers_count_plural": "{0} követő",
    "info.empty_subreddit":"Még nincs itt semmi",
    "error.unknown_error_first": "Ismeretlen hiba történt.",
    "error.unknown_error_second": "Próbálkozz újra később.",
    "settings": "Beállítások",
    "cancle":"Mégse",
    "save":"Mentés",
    "setting.options.title": "Opciók",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],12:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} pengikut",
    "info.followers_count_plural": "{0} pengikut",
    "info.empty_subreddit":"Belum ada apa pun di sini",
    "error.unknown_error_first": "Terjadi kesalahan yang tidak diketahui.",
    "error.unknown_error_second": "Coba lagi nanti.",
    "settings": "Pengaturan",
    "cancle":"Batalkan",
    "save":"Simpan",
    "setting.options.title": "Opsi ",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],13:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} follower",
    "info.followers_count_plural": "{0} follower",
    "info.empty_subreddit":"Ancora nulla qui",
    "error.unknown_error_first": "Errore sconosciuto.",
    "error.unknown_error_second": "Riprova in un altro momento.",
    "settings": "Impostazioni",
    "cancle":"Annulla",
    "save":"Salva",
    "setting.options.title": "Opzioni",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],14:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0}人のフォロワー",
    "info.followers_count_plural": "{0}人のフォロワー",
    "info.empty_subreddit":"こちらにはまだ何も入っていません",
    "error.unknown_error_first": "不明なエラーが発生しました。",
    "error.unknown_error_second": "しばらくしてからまた試してください",
    "settings": "設定",
    "cancle":"キャンセル",
    "save":"保存",
    "setting.options.title": "オプション",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],15:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} volger",
    "info.followers_count_plural": "{0} volgers",
    "info.empty_subreddit":"Hier is nog niets",
    "error.unknown_error_first": "Er is een onbekende fout opgetreden.",
    "error.unknown_error_second": "Probeer het later nog eens.",
    "settings": "Instellingen",
    "cancle":"Annuleren",
    "save":"Opslaan",
    "setting.options.title": "Opties",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],16:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} obserwujący",
    "info.followers_count_plural": "{0} obserwujących",
    "info.empty_subreddit":"Nic tu jeszcze nie ma",
    "error.unknown_error_first": "Wystąpił nieznany błąd.",
    "error.unknown_error_second": "Spróbuj ponownie później.",
    "settings": "Ustawienia",
    "cancle":"Anuluj",
    "save":"Zapisz",
    "setting.options.title": "Opcje",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],17:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} seguidor",
    "info.followers_count_plural": "{0} seguidores",
    "info.empty_subreddit":"Nada por aqui ainda",
    "error.unknown_error_first": "Ocorreu um erro desconhecido.",
    "error.unknown_error_second": "Tente de novo mais tarde.",
    "settings": "Preferências",
    "cancle":"Cancelar",
    "save":"Salvar",
    "setting.options.title": "Opções",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],18:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} följare",
    "info.followers_count_plural": "{0} följare",
    "info.empty_subreddit":"Ingenting här än",
    "error.unknown_error_first": "Det uppstod ett okänt fel.",
    "error.unknown_error_second": "Försök igen senare.",
    "settings": "Inställningar",
    "cancle":"Avbryt",
    "save":"Spara",
    "setting.options.title": "Alternativ",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],19:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "ผู้ติดตาม {0} คน",
    "info.followers_count_plural": "ผู้ติดตาม {0} คน",
    "info.empty_subreddit":"ที่นี่ยังไม่มีเนื้อหาอะไรเลย",
    "error.unknown_error_first": "เกิดข้อผิดพลาดที่ไม่รู้จัก",
    "error.unknown_error_second": "โปรดลองอีกครั้งในภายหลัง",
    "settings": "การตั้งค่า",
    "cancle":"ยกเลิก",
    "save":"บันทึก",
    "setting.options.title": "ตัวเลือก",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],20:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} takipçi",
    "info.followers_count_plural": "{0} takipçi",
    "info.empty_subreddit":"Burada henüz bir şey yok",
    "error.unknown_error_first": "Bilinmeyen bir hata oluştu.",
    "error.unknown_error_second": "Lütfen daha sonra yeniden dene.",
    "settings": "Ayarlar",
    "cancle":"İptal",
    "save":"Kaydet",
    "setting.options.title": "Seçenekler",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],21:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} người theo dõi",
    "info.followers_count_plural": "{0} người theo dõi",
    "info.empty_subreddit":"Chưa có gì ở đây",
    "error.unknown_error_first": "Đã xuất hiện một lỗi không xác định.",
    "error.unknown_error_second": "Vui lòng thử lại sau.",
    "settings": "Cài đặt",
    "cancle":"Hủy",
    "save":"Lưu",
    "setting.options.title": "Tùy chọn",
    "setting.options.show_type_name": "Hiển thị loại bài gửi (PLAYLIST, BÀI HÁT,...)",
    "setting.options.show_upvote": "Hiển thị điểm upvote",
    "setting.options.show_follower_count": "Hiển thị số người theo dõi",
    "setting.options.fetch_youtube": "Nhận bài gửi Youtube",
    "setting.form.add_subreddit": "Thêm subreddit",
    "setting.form.subreddit_name": "Tên subreddit",
    "sorts": "Sắp xếp theo {0}",
    "sort.hot": "Nóng",
    "sort.new": "Mới",
    "sort.top": "Top",
    "sort.rising": "Đang nổi",
    "sort.controversial": "Gây tranh cãi",
    "sort.hour": "Trong 1 giờ",
    "sort.day": "Trong ngày",
    "sort.week": "Trong tuần",
    "sort.month": "Trong tháng",
    "sort.year": "Trong năm",
    "sort.all": "Của tất cả",
  }
  },{}],22:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} 粉絲",
    "info.followers_count_plural": "{0} 位粉絲",
    "info.empty_subreddit":"這裡什麼都沒有",
    "error.unknown_error_first": "發生未知錯誤。",
    "error.unknown_error_second": "請稍後再試一次。",
    "settings": "設定",
    "cancle":"取消",
    "save":"儲存",
    "setting.options.title": "選項",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],23:[function(require,module,exports){
  module.exports={
    "app.name": "Reddit",
    "app.description": "Reddit",
    "info.followers_count_single": "{0} pengikut",
    "info.followers_count_plural": "{0} pengikut",
    "info.empty_subreddit":"Tiada apa-apa di sini buat masa ini",
    "error.unknown_error_first": "Terdapat ralat tidak diketahui.",
    "error.unknown_error_second": "Sila cuba lagi kemudian.",
    "settings": "Tetapan",
    "cancle":"Batal",
    "save":"Simpan",
    "setting.options.title": "Pilihan",
    "setting.options.show_type_name": "Show post type (PLAYLIST, TRACK,...)",
    "setting.options.show_upvote": "Show upvotes count",
    "setting.options.show_follower_count": "Show followers count",
    "setting.options.fetch_youtube": "Fetch Youtube posts",
    "setting.form.add_subreddit": "Add subreddit",
    "setting.form.subreddit_name": "Subreddit name",
    "sorts": "Sort by {0}",
    "sort.hot": "Hot",
    "sort.new": "New",
    "sort.top": "Top",
    "sort.rising": "Rising",
    "sort.controversial": "Controversial",
    "sort.hour": "Past Hour",
    "sort.day": "Past Day",
    "sort.week": "Past Week",
    "sort.month": "Past Month",
    "sort.year": "Past Year",
    "sort.all": "Of All time",
  }
  },{}],24:[function(require,module,exports){
  'use strict';
  
  var _setup = require('./src/spotlet-environment/setup');
  
  var _setup2 = _interopRequireDefault(_setup);
  
  var _setup3 = require('./src/store/setup');
  
  var _setup4 = _interopRequireDefault(_setup3);
  
  var _setup5 = require('./src/view/setup');
  
  var _setup6 = _interopRequireDefault(_setup5);
  
  var _setup7 = require('./src/actions/setup');
  
  var _setup8 = _interopRequireDefault(_setup7);
  
  var redditSetting = require('reddit-setting');

  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
  
  (0, _setup2.default)();
  
  const store = (0, _setup4.default)();
  (0, _setup8.default)(store);
  (0, _setup6.default)(store);
  (0, redditSetting.passStore)(store);
  
  },{"./src/actions/setup":26,"./src/spotlet-environment/setup":30,"./src/store/setup":32,"./src/view/setup":50,"reddit-setting":595}],25:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.loadDataBasedOnApplicationArguments = loadDataBasedOnApplicationArguments;
  exports.subscribeToOfflineState = subscribeToOfflineState;
  exports.updateProductState = updateProductState;
  exports.updateI18n = updateI18n;
  exports.updateLoggedInUser = updateLoggedInUser;
  
  var _promisifyNative = require('promisify-native');
  
  var _promisifyNative2 = _interopRequireDefault(_promisifyNative);
  
  var _get = require('lodash/get');
  
  var _get2 = _interopRequireDefault(_get);
  
  var liburi = require('spotify-liburi');

  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  const unEscape = [
    ['&amp;', '&'],
    ['&lt;', '<'],
    ['&gt;', '>'],
    ['&quot;', '"'],
    ['&#x27;', "'"],
    ['&#x60;', '`'],
    ['&#x3D;', '=']
  ];
  var initTime = true;
  function unEscapeHTML(string) {
    unEscape.forEach(sym => {
      var regExp = new RegExp(sym[0], 'g')
      string = string.replace(regExp, sym[1])
    });
    return string
  }

  function loadDataBasedOnApplicationArguments({ bridge }) {
    return function (dispatch, getState) {
      const state = getState();
      if (!state.application.isOnline) return;

      const fetchYoutube = state.application.options.youtube;
      const { appName, section, sort, time } = state.application.args

      dispatch({
        type: 'fetch-data-result',
        appName,
        path: '/volatile',
      });

      (section && sort) && getSubreddit();
//asdasd
      function getSubreddit(playlists = [], after = '') {
        // www is needed or it will block with "cross-origin" error.
        var url = `https://www.reddit.com/r/${section}/${sort}.json?limit=100`
        if (after) {
          url += `&after=${after}`
        }
        if (sort.match(/top|constroversial/) && time) {
          url += `&t=${time}`
        }

        fetch(url)
        .then(response => response.json())
        .then(data => getSubredditMetadata(
            playlists, 
            postMapper(data.data.children), 
            data.data.after
          )
        );
      }

      function postMapper(posts) {
        var mappedPosts = [];
        posts.forEach(post => {
          var uri = liburi.from(post.data.url);
          if (uri && (uri.type == "playlist" || uri.type == "track" || uri.type == "album")) {
            mappedPosts.push({
              uri: uri.toURI(),
              type: uri.type,
              title: post.data.title,
              upvotes: post.data.ups
            })
          } else if (fetchYoutube && post.data.media && post.data.media.type == "youtube.com") {
            mappedPosts.push({
              type: "youtube",
              title: unEscapeHTML(post.data.media.oembed.title),
              upvotes: post.data.ups,
              html: unEscapeHTML(post.data.media.oembed.html),
              description: unEscapeHTML(post.data.title),
              image: post.data.media.oembed.thumbnail_url
            })
          }
        });
        return mappedPosts;
      }

      function getSubredditMetadata(playlists, posts, after) {
        var fetchingCount = 0;
        var fetchingContent = [];
        var promises = [];
        for (const post of posts) {
          if (post.type == "playlist") { //Start categorize post type
            promises.push(new Promise((resolve, reject) => {
              bridge.cosmosJSON({
                method: 'GET',
                uri: `sp://core-playlist/v1/playlist/${post.uri}/metadata`,
                body: {
                  policy: {
                    name: true,
                    picture: true,
                    followed: true,
                    followers: true,
                    owner: {
                      name: true
                    }
                  }
                }
              }, (error, { error: responseError = null, metadata } = {}) => {
                  if (!error && !responseError && metadata) {
                    resolve({
                      type: "playlist",
                      uri: post.uri,
                      name: metadata.name,
                      description: unEscapeHTML(post.title),
                      upvotes: post.upvotes,
                      followers_count: metadata.followers,
                      is_following: metadata.followed,
                      image_url: metadata.picture,
                    });
                  } else {
                    reject(error || responseError)
                  }
              });
            }))
          } else if (post.type == "track" || post.type == "album") {
            promises.push(new Promise((resolve, reject) => {
              bridge.request(`${post.type}_metadata`, [post.uri], (error, metadata)  => {
                if (!error) {
                  resolve({
                    type: post.type,
                    uri: post.uri,
                    name: metadata.name,
                    artists: metadata.artists,
                    upvotes: post.upvotes,
                    image_url: metadata.image
                  });
                } else {
                  reject(error);
                }
              });
            }));
          } else if (post.type == "youtube") {
            promises.push(new Promise((resolve, reject) => {
              resolve({
                type: post.type,
                uri: 'youtube',
                name: post.title,
                description: post.description,
                upvotes: post.upvotes,
                image_url: post.image,
                html: escape(post.html)
              });
            }))
          } //End categorize post type
        }

        Promise.all(promises)
          .then(data => {
            playlists.push(...data);

            dispatch({
              type: 'fetch-data-result',
              appName,
              path: 'update-content' ,
              result: {
                section,
                sort,
                time,
                content: playlists
              }
            });

            if (after && playlists.length < 100) {
              getSubreddit(playlists, after);
            } else {
              promises = [];
              playlists = [];
              posts = [];
            }
        }).catch(e => console.log(e))
      }
    };
  }
  
  function subscribeToOfflineState({ bridge, debounce }) {
    return function (dispatch) {
      bridge.cosmosJSON({
        method: 'SUB',
        uri: 'sp://offline/v1/connection'
      }, debounce(function (error, data) {
        dispatch({
          type: 'connection-status-updated',
          status: data.connection.status
        });
        dispatch(loadDataBasedOnApplicationArguments({ bridge }));
      }, 100));
    };
  }
  
  function updateProductState({ window }) {
    return {
      type: 'product-state-update',
      productState: window.__spotify.product_state
    };
  }
  
  function updateI18n({ window, i18n }) {
    return {
      type: 'i18n-update',
      locale: window.__spotify.locale,
      strings: i18n.appStrings(),
      glueStrings: i18n.glueStrings()
    };
  }
  
  function updateLoggedInUser({ window }) {
    return {
      type: 'logged-in-user-update',
      username: window.__spotify.username
    };
  }
  
  function cosmosGET(bridge, uri) {
    return (0, _promisifyNative2.default)(bridge.cosmosJSON)({
      method: 'GET',
      uri: uri
    });
  }
  
  },{"lodash/get":422,"promisify-native":482,"spotify-liburi":553,"./util/bridge":165}],26:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.default = setupActions;
  
  var _i18n = require('../../i18n');
  
  var _i18n2 = _interopRequireDefault(_i18n);
  
  var _contextmenu = require('../../../../libs/contextmenu');
  
  var _debounce = require('lodash/debounce');
  
  var _debounce2 = _interopRequireDefault(_debounce);
  
  var _spotifyCosmosApi = require('spotify-cosmos-api');
  
  var _spotifyCosmosApi2 = _interopRequireDefault(_spotifyCosmosApi);
  
  var _creators = require('./creators');
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function setupActions(reduxStore) {
    const bridge = require('../../../../libs/bridge');

    reduxStore.dispatch((0, _creators.updateI18n)({ window, i18n: _i18n2.default }));
    (function subscribeToApplicationArguments() {
      const live = require('../../../../libs/live');
  
      (function listen() {
        live('spotify:application').on('update', function (changes) {
          if ('arguments' in changes) {
            next(changes.arguments.split(':'));
          }
        }).query('arguments', function (err, data) {
          next(data.arguments.split(':'));
        });
      })();
  
      function next(args) {
        reduxStore.dispatch({
          type: 'application-arguments-updated',
          description: args,
          args: args
        });
        reduxStore.dispatch((0, _creators.loadDataBasedOnApplicationArguments)({ bridge }));
      }
    })();
  
    reduxStore.dispatch((0, _creators.subscribeToOfflineState)({ bridge, debounce: _debounce2.default }));
  
    reduxStore.dispatch((0, _creators.updateProductState)({ window }));
    reduxStore.dispatch((0, _creators.updateLoggedInUser)({ window }));
  }
  
  },{"../../../../libs/bridge":53,"../../../../libs/contextmenu":56,"../../../../libs/live":172,"../../i18n":1,"./creators":25,"lodash/debounce":420,"spotify-cosmos-api":543}],27:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.default = setupSpotifyEvents;
  
  var _spotifyEvents = require('../../../../libs/spotify-events');
  
  var _spotifyEvents2 = _interopRequireDefault(_spotifyEvents);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function setupSpotifyEvents() {
    _spotifyEvents2.default.attach();
  }
  
  },{"../../../../libs/spotify-events":260}],28:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.default = setupHandlebars;
  
  var _spotifyHandlebars = require('../../../../libs/spotify-handlebars');
  
  var _spotifyHandlebars2 = _interopRequireDefault(_spotifyHandlebars);
  
  var _i18n = require('../../i18n');
  
  var _i18n2 = _interopRequireDefault(_i18n);
  
  var _helpers = require('../../../../libs/spotify-handlebars/helpers');
  
  var _helpers2 = _interopRequireDefault(_helpers);
  
  var _numeral = require('../../../../libs/spotify-handlebars/helpers/numeral');
  
  var _numeral2 = _interopRequireDefault(_numeral);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function setupHandlebars() {
    _numeral2.default.setLocale(window.__spotify.locale);
    _helpers2.default.loc.register(_i18n2.default.glueStrings());
    _helpers2.default.loc.register(_i18n2.default.appStrings());
    _spotifyHandlebars2.default.register(_helpers2.default);
  }
  
  },{"../../../../libs/spotify-handlebars":309,"../../../../libs/spotify-handlebars/helpers":299,"../../../../libs/spotify-handlebars/helpers/numeral":304,"../../i18n":1}],29:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.default = setupLiveModels;
  
  var _live = require('../../../../libs/live');
  
  var _live2 = _interopRequireDefault(_live);
  
  var _liveModels = require('../../../../libs/live-models');
  
  var _liveModels2 = _interopRequireDefault(_liveModels);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function setupLiveModels() {
    _liveModels2.default.register();
  }
  
  },{"../../../../libs/live":172,"../../../../libs/live-models":135}],30:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.default = setupSpotletEnvironment;
  
  var _events = require('./events');
  
  var _events2 = _interopRequireDefault(_events);
  
  var _handlebars = require('./handlebars');
  
  var _handlebars2 = _interopRequireDefault(_handlebars);
  
  var _liveModels = require('./live-models');
  
  var _liveModels2 = _interopRequireDefault(_liveModels);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function setupSpotletEnvironment() {
    (0, _events2.default)();
    (0, _handlebars2.default)();
    (0, _liveModels2.default)();
  }
  
  },{"./events":27,"./handlebars":28,"./live-models":29}],31:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  
  var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
  
  var _redux = require('redux');

  function args(state = {}, { type, args }) {
    let appName = __spotify.app_uri.split(':')[2]
    const subs = subreddits();
    let section = subs[0];
    let sort = options().sortBy;
    let time = options().sortTime;
    if (args) {
      if (args.length > 0) {
        if (subs.indexOf(args[0]) !== -1) {
          section = args[0];
        }

        if (args[1]) {
          sort = args[1];
        }

        if (args[2]) {
          time = args[2];
        }
      }
    }
    return type === 'application-arguments-updated' ? { appName, section, sort, time } : state;
  }
  
  function subreddits() {
    let subreddits = JSON.parse(localStorage.getItem("SubredditList"));
    if (!subreddits || !Array.isArray(subreddits) || subreddits.length === 0) {
      subreddits = ["spotify", "monstercat", "EDM", "popheads"];
      localStorage.setItem("SubredditList", '["spotify", "monstercat", "EDM", "popheads"]');
    }
    return subreddits
  }

  function options() {
    let options = JSON.parse(localStorage.getItem("RedditOptions"));
    if (!options) {
      options = {
        "typename": true,
        "upvote": true,
        "follower": true,
        "youtube": false,
        "sortBy": "hot",
        "sortTime": "day"
      };
      localStorage.setItem("RedditOptions", JSON.stringify(options));
    }
    const sortByRE = new RegExp("hot|new|top|rising|controversial", "i")
    if (!options.sortBy || !sortByRE.test(options.sortBy)) {
      options.sortBy = "hot";
      localStorage.setItem("RedditOptions", JSON.stringify(options));
    }
    const sortTimeRE = new RegExp("hour|day|week|month|year|all")
    if (!options.sortTime || !sortTimeRE.test(options.sortTime)) {
      options.sortTime = "day";
      localStorage.setItem("RedditOptions", JSON.stringify(options));
    }
    return options;
  }

  function i18n(state = {}, { type, strings, locale, glueStrings}) {
    if (type !== 'i18n-update') return state;
    let { Album, Playlist, Track } = glueStrings;
    strings = _extends({}, strings, {
      album: Album,
      playlist: Playlist,
      track: Track,
      youtube: "youtube"
    });
    return _extends({}, state, {
      strings,
      locale
    });
  }
  
  function isOnline(state = null, { type, status }) {
    return type === 'connection-status-updated' ? status !== 'offline' && status !== 'forced_offline' : state;
  }
  
  function loggedInUsername(state = null, { type, username }) {
    return type === 'logged-in-user-update' ? username : state;
  }
  
  function profiles(state = {}, action) {
    if (action.type === 'fetch-data-result') {
      const { path, appName, result } = action;
      if (path === '/volatile') {
        return _extends({}, state, {
          [appName]: _extends({}, state[appName], {
            hasError: false,
            fullName: "Reddit",
            options: options()
          })
        });
      } else if (path === 'update-content') {
        const currOptions = options()
        if (currOptions.sortBy === result.sort && currOptions.sortTime === result.time) {
          return _extends({}, state, {
            [appName]: _extends({}, state[appName], {
              section: result.section,
              sort: result.sort,
              time: result.time,
              contents: _extends({}, state[appName].contents, {
                [result.section]: result.content
              })
            })
          });
        }
        
      } else if (path === 'reload-data') {
        return _extends({}, state, {
          [appName]: _extends({}, state[appName], {
            options: result.options
          })
        });
      }

    }
    return state;
    
  } 
  const application = (0, _redux.combineReducers)({
    i18n,
    isOnline,
    loggedInUsername,
    args,
    subreddits,
    options
  });
  
  exports.default = (0, _redux.combineReducers)({
    profiles,
    application
  });
  
  },{"redux":531}],32:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.default = setupReduxStore;
  
  var _redux = require('redux');
  
  var _reduxThunk = require('redux-thunk');
  
  var _reduxThunk2 = _interopRequireDefault(_reduxThunk);
  
  var _reducer = require('./reducer');
  
  var _reducer2 = _interopRequireDefault(_reducer);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function setupReduxStore() {
    const initialState = {};
    return (0, _redux.createStore)(_reducer2.default, initialState, (0, _redux.applyMiddleware)(_reduxThunk2.default));
  }
  
  },{"./reducer":31,"redux":531,"redux-thunk":525}],33:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.isOnline = exports.locale = exports.strings = exports.loggedInUsername = exports.section = exports.appName = exports.applicationArguments = undefined;
  
  var _reselect = require('reselect');
  
  const application = state => state.application;
  const i18n = (0, _reselect.createSelector)(application, x => x.i18n);
  
  const namedApplicationArguments  = exports.applicationArguments = (0, _reselect.createSelector)(application, x => x.args);
  
  const appName = exports.appName = (0, _reselect.createSelector)(namedApplicationArguments, x => x && x.appName);
  
  const section = exports.section = (0, _reselect.createSelector)(namedApplicationArguments, x => x && x.section);

  const subreddits = exports.subreddits = (0, _reselect.createSelector)(application, x => x && x.subreddits);
  
  const options = exports.options = (0, _reselect.createSelector)(application, x => x && x.options);
  
  const loggedInUsername = exports.loggedInUsername = (0, _reselect.createSelector)(application, x => x && x.loggedInUsername);
  
  const strings = exports.strings = (0, _reselect.createSelector)(i18n, x => x && x.strings);

  const locale = exports.locale = (0, _reselect.createSelector)(i18n, x => x && x.locale);
  
  const isOnline = exports.isOnline = (0, _reselect.createSelector)(application, x => x && x.isOnline);
  
  },{"reselect":533}],34:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.ContentComponent = undefined;
  
  var _react = require('react');
  
  var _react2 = _interopRequireDefault(_react);
  
  var _handlebars = require('./handlebars.component');
  
  var _handlebars2 = _interopRequireDefault(_handlebars);
  
  var _contentTemplate = require('./content.template.hbs');
  
  var _contentTemplate2 = _interopRequireDefault(_contentTemplate);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  const ContentComponent = exports.ContentComponent = props => _react2.default.createElement(_handlebars2.default, {
    data: props.templateData,
    templateFunction: _contentTemplate2.default
  });
  
  exports.default = ContentComponent;
  
  },{"./content.template.hbs":37,"./handlebars.component":39,"react":522}],35:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.mapStateToProps = undefined;
  
  var _reactRedux = require('react-redux');
  
  var _content = require('./content.component');
  
  var _content2 = _interopRequireDefault(_content);
  
  var _content3 = require('./content.selectors');
  
  var contentSelectors = _interopRequireWildcard(_content3);
  
  function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  const mapStateToProps = exports.mapStateToProps = state => {
    return ({
      templateData: {
        contents: contentSelectors.contents(state),
        section: contentSelectors.section(state),
        subreddits: contentSelectors.subreddits(state),
        options: contentSelectors.options(state)
      }
  })};
  
  const ContentConnected = (0, _reactRedux.connect)(mapStateToProps)(_content2.default);
  
  exports.default = ContentConnected;
  
  },{"./content.component":34,"./content.selectors":36,"react-redux":509}],36:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.playlistTemplateData = playlistTemplateData;
  
  var _reselect = require('reselect');
  
  var _spotifyLiburi = require('spotify-liburi');
  
  var _spotifyLiburi2 = _interopRequireDefault(_spotifyLiburi);
  
  var _profile = require('./profile.selectors');
  
  var profileSelectors = _interopRequireWildcard(_profile);
  
  var _app = _interopRequireWildcard(require('./application.selectors'));
  
  
  function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  const createListSelector = (mapper, itemSelector) => (0, _reselect.createSelector)(
    itemSelector, _app.strings, _app.locale, _app.loggedInUsername, 
    _app.section, _app.subreddits, _app.options,
    (items, strings, locale, loggedInUsername, section, subreddits, options) => {
      if (!strings || !locale || !loggedInUsername || !items) {
        return {[section]: { isVisible: true, isEmpty: true}}
      };

      var contents = {}
      subreddits.forEach(sub => {
        var isVisible = sub === section;
        if (items[sub]) {
          items[sub] = items[sub].filter(item => !!item);
          contents[sub] = {
            isVisible,
            isEmpty: items[sub].length === 0,
            items: items[sub].map(item => mapper(strings, locale, loggedInUsername, options, item))
          };
        } else {
          contents[sub] = {
            isVisible,
            isEmpty: true
          }
        }
      });
      return contents;
  });
  
  const subreddits = exports.subreddits = (0, _reselect.createSelector)(_app.subreddits, (subreddits) => {
    return subreddits;
  });

  const options = exports.options = (0, _reselect.createSelector)(_app.options, (options) => {
    return options;
  });

  const contents = exports.contents = createListSelector(playlistTemplateData, profileSelectors.contents);
  
  const section = exports.section = (0, _reselect.createSelector)(_app.section, (section) => {
    return section;
  });

  function followersCountString(strings, locale, count) {
    const numberFormat = new Intl.NumberFormat(locale);
    return count === 1 ? strings['info.followers_count_single'].replace('{0}', 1) : strings['info.followers_count_plural'].replace('{0}', numberFormat.format(count));
  }
  
  function upvoteCountString(strings, locale, count) {
    const numberFormat = new Intl.NumberFormat(locale);
    return `${numberFormat.format(count)} 🔺`;
  }

  function typeString(strings, type) {
    return strings[type];
  }

  function playlistTemplateData(strings, locale, username, options, data) {
    if (!data) return;
    if (!options.youtube && data.type == "youtube") {
      return;
    }

    const uri = _spotifyLiburi2.default.from(data.uri);
    var upvote;
    var follower;
    var metadata;
    if (options.upvote) {
      upvote = upvoteCountString(strings, locale, data.upvotes);
    }

    if (options.follower && data.type == "playlist") {
      follower = followersCountString(strings, locale, data.followers_count);
    }

    if (upvote && follower) {
      metadata = `${upvote} ${follower}`;
    } else {
      metadata = upvote || follower;
    }
    // zxczxc
    return {
      uri: data.uri,
      imageUrl: data.image_url,
      name: data.name,
      title: data.name,
      currentUserPlaylist: uri && (uri.username === username),
      dragText: data.uri,
      metadata: metadata,
      showMeta: !!metadata,
      type: typeString(strings, data.type),
      showType: options.typename,
      description: data.description,
      subtitleLinks: data.artists,
      html: data.html,
    };
  }
  
  },{"./application.selectors":33,"./profile.selectors":49,"reselect":533,"spotify-liburi":553}],37:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('../../../../libs/glue/templates/cards/card.hbs');
  HandlebarsCompiler.registerPartial('../../../../libs/glue/templates/cards/card.hbs', partial$0);
  var partial$1 = require('../../../../libs/glue/templates/cards/card.hbs');
  HandlebarsCompiler.registerPartial('../../../../libs/glue/templates/cards/card.hbs', partial$1);
  var partial$2 = require('../../../../libs/glue/templates/cards/card.hbs');
  HandlebarsCompiler.registerPartial('../../../../libs/glue/templates/cards/card.hbs', partial$2);
  var partial$3 = require('../../../../libs/glue/templates/cards/card.hbs');
  HandlebarsCompiler.registerPartial('../../../../libs/glue/templates/cards/card.hbs', partial$3);
  var partial$4 = require('../../../../libs/glue/templates/list-group-item-social.hbs');
  HandlebarsCompiler.registerPartial('../../../../libs/glue/templates/list-group-item-social.hbs', partial$4);
  var partial$5 = require('../../../../libs/glue/templates/list-group-item-social.hbs');
  HandlebarsCompiler.registerPartial('../../../../libs/glue/templates/list-group-item-social.hbs', partial$5);
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.isVisible : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"2":function(container,depth0,helpers,partials,data) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
      return ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isEmpty : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n    <section>\n"
      + ((stack1 = helpers["with"].call(alias1,(depth0 != null ? depth0 : depth0),{"name":"with","hash":{},"fn":container.program(14, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"3":function(container,depth0,helpers,partials,data) {
      return "      <div class=\"empty-state\">\n        <div class=\"message\">\n          <i></i>\n          <h4>"
      + container.escapeExpression((helpers.loc || (depth0 && depth0.loc) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"info.no_activity",{"name":"loc","hash":{},"data":data}))
      + "</h4>\n        </div>\n      </div>\n";
  },"5":function(container,depth0,helpers,partials,data) {
      var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3=container.escapeExpression, alias4="function", buffer = 
    "      <section data-log-context=\"recently-played-artists\">\n        <div class=\"section-divider\">\n          <div>"
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"section.artists",{"name":"loc","hash":{},"data":data}))
      + "</div>\n          <div class=\"section-auxiliary\">\n            <a href=\"spotify:app:profile:"
      + alias3(((helper = (helper = helpers.encodedUsername || (depth0 != null ? depth0.encodedUsername : depth0)) != null ? helper : alias2),(typeof helper === alias4 ? helper.call(alias1,{"name":"encodedUsername","hash":{},"data":data}) : helper)))
      + ":"
      + alias3(((helper = (helper = helpers.seeAllId || (depth0 != null ? depth0.seeAllId : depth0)) != null ? helper : alias2),(typeof helper === alias4 ? helper.call(alias1,{"name":"seeAllId","hash":{},"data":data}) : helper)))
      + "\" data-navbar-id=\"navbar\" data-navbar-item-id=\"recently-played-artists\">\n              "
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"section.see_all",{"name":"loc","hash":{},"data":data}))
      + "\n            </a>\n          </div>\n        </div>\n        <div class=\"row standard-grid clamp-lg-6 clamp-md-4 clamp-sm-3\">\n";
    stack1 = ((helper = (helper = helpers.items || (depth0 != null ? depth0.items : depth0)) != null ? helper : alias2),(options={"name":"items","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data}),(typeof helper === alias4 ? helper.call(alias1,options) : helper));
    if (!helpers.items) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
    if (stack1 != null) { buffer += stack1; }
    return buffer + "        </div>\n      </section>\n";
  },"6":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "            <div class=\"col-lg-2 col-md-3 col-sm-4 col-xs-6\">\n"
      + ((stack1 = container.invokePartial(partials["../../../../libs/glue/templates/cards/card.hbs"],depth0,{"name":"../../../../libs/glue/templates/cards/card.hbs","data":data,"indent":"              ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "            </div>\n";
  },"8":function(container,depth0,helpers,partials,data) {
      var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3=container.escapeExpression, alias4="function", buffer = 
    "        <div class=\"section-divider\">\n          <div>"
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"section.playlists",{"name":"loc","hash":{},"data":data}))
      + "</div>\n          <div class=\"section-auxiliary\">\n            <a href=\"spotify:app:profile:"
      + alias3(((helper = (helper = helpers.encodedUsername || (depth0 != null ? depth0.encodedUsername : depth0)) != null ? helper : alias2),(typeof helper === alias4 ? helper.call(alias1,{"name":"encodedUsername","hash":{},"data":data}) : helper)))
      + ":"
      + alias3(((helper = (helper = helpers.seeAllId || (depth0 != null ? depth0.seeAllId : depth0)) != null ? helper : alias2),(typeof helper === alias4 ? helper.call(alias1,{"name":"seeAllId","hash":{},"data":data}) : helper)))
      + "\" data-navbar-id=\"navbar\" data-navbar-item-id=\"public-playlists\">\n              "
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"section.see_all",{"name":"loc","hash":{},"data":data}))
      + "\n            </a>\n          </div>\n        </div>\n        <div class=\"row standard-grid clamp-lg-6 clamp-md-4 clamp-sm-3\">\n";
    stack1 = ((helper = (helper = helpers.items || (depth0 != null ? depth0.items : depth0)) != null ? helper : alias2),(options={"name":"items","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data}),(typeof helper === alias4 ? helper.call(alias1,options) : helper));
    if (!helpers.items) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
    if (stack1 != null) { buffer += stack1; }
    return buffer + "        </div>\n      </section>\n";
  },"10":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.isVisible : depth0),{"name":"if","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"11":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.isEmpty : depth0),{"name":"if","hash":{},"fn":container.program(12, data, 0),"inverse":container.program(14, data, 0),"data":data})) != null ? stack1 : "");
  },"12":function(container,depth0,helpers,partials,data) {
      return "        <div class=\"empty-state\">\n          <div class=\"message\">\n            <i></i>\n            <h4>"
      + container.escapeExpression((helpers.loc || (depth0 && depth0.loc) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"info.no_activity",{"name":"loc","hash":{},"data":data}))
      + "</h4>\n          </div>\n        </div>\n";
  },"14":function(container,depth0,helpers,partials,data) {
      var stack1, helper, options, buffer = 
    "        <div class=\"row standard-grid no-section-divider\">\n";
    stack1 = ((helper = (helper = helpers.items || (depth0 != null ? depth0.items : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"items","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
    if (!helpers.items) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
    if (stack1 != null) { buffer += stack1; }
    return buffer + "        </div>\n";
  },"16":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.isVisible : depth0),{"name":"if","hash":{},"fn":container.program(17, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"17":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.isEmpty : depth0),{"name":"if","hash":{},"fn":container.program(18, data, 0),"inverse":container.program(14, data, 0),"data":data})) != null ? stack1 : "");
  },"18":function(container,depth0,helpers,partials,data) {
    return "        <div class=\"empty-state\">\n          <div class=\"message\">\n            <i></i>\n            <h4>"
      + container.escapeExpression((helpers.loc || (depth0 && depth0.loc) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"info.empty_subreddit",{"name":"loc","hash":{},"data":data}))
      + "</h4>\n          </div>\n        </div>\n";
  },"22":function(container,depth0,helpers,partials,data) {
    return "        <div class=\"empty-state\">\n          <div class=\"message\">\n            <i></i>\n            <h4>"
      + container.escapeExpression((helpers.loc || (depth0 && depth0.loc) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"info.empty_following",{"name":"loc","hash":{},"data":data}))
      + "</h4>\n          </div>\n        </div>\n";
  },"24":function(container,depth0,helpers,partials,data) {
      var stack1, helper, options, buffer = 
    "        <div class=\"list-group\">\n";
    stack1 = ((helper = (helper = helpers.items || (depth0 != null ? depth0.items : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"items","hash":{},"fn":container.program(25, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
    if (!helpers.items) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
    if (stack1 != null) { buffer += stack1; }
    return buffer + "        </div>\n";
  },"25":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = container.invokePartial(partials["../../../../libs/glue/templates/list-group-item-social.hbs"],depth0,{"name":"../../../../libs/glue/templates/list-group-item-social.hbs","data":data,"indent":"            ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
  },"27":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.isVisible : depth0),{"name":"if","hash":{},"fn":container.program(28, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"28":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.isEmpty : depth0),{"name":"if","hash":{},"fn":container.program(22, data, 0),"inverse":container.program(29, data, 0),"data":data})) != null ? stack1 : "");
  },"29":function(container,depth0,helpers,partials,data) {
      var stack1, helper, options, buffer = 
    "      <div class=\"list-group\">\n";
    stack1 = ((helper = (helper = helpers.items || (depth0 != null ? depth0.items : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"items","hash":{},"fn":container.program(30, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
    if (!helpers.items) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
    if (stack1 != null) { buffer += stack1; }
    return buffer + "      </div>\n";
  },"30":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = container.invokePartial(partials["../../../../libs/glue/templates/list-group-item-social.hbs"],depth0,{"name":"../../../../libs/glue/templates/list-group-item-social.hbs","data":data,"indent":"          ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
    var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
    var result = '';
    //qweqwe
    var subreddits = depth0.subreddits;
    subreddits.forEach(sub => {
      result += "<section\n  class=\"profile-section container\"\n  data-navbar-view-id=\"" + sub + "\"\n>\n"
      + ((stack1 = helpers["with"].call(alias1,(depth0 != null ? depth0.contents[sub] : depth0),{"name":"with","hash":{},"fn":container.program(16, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "</section>\n"
    })
    return result;
  },"usePartial":true,"useData":true});
  
  },{"../../../../libs/glue/templates/cards/card.hbs":90,"../../../../libs/glue/templates/list-group-item-social.hbs":113,"hbsfy/runtime":368}],38:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  
  var _react = require('react');
  
  var _react2 = _interopRequireDefault(_react);
  
  var _handlebars = require('./handlebars.component');
  
  var _handlebars2 = _interopRequireDefault(_handlebars);
  
  var _errorAndOffline = require('../../../../libs/glue/templates/error-and-offline.hbs');
  
  var _errorAndOffline2 = _interopRequireDefault(_errorAndOffline);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  class Error extends _react2.default.PureComponent {
    render() {
      return _react2.default.createElement(_handlebars2.default, {
        data: { useDefaultError: true },
        templateFunction: _errorAndOffline2.default
      });
    }
  }
  exports.default = Error;
  
  },{"../../../../libs/glue/templates/error-and-offline.hbs":97,"./handlebars.component":39,"react":522}],39:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  
  var _propTypes = require('prop-types');
  
  var _propTypes2 = _interopRequireDefault(_propTypes);
  
  var _react = require('react');
  
  var _react2 = _interopRequireDefault(_react);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  class Handlebars extends _react2.default.Component {
    shouldComponentUpdate(nextProps) {
      const deepEquals = require('mout/lang/deepEquals');
      return !deepEquals(this.props.data, nextProps.data);
    }
  
    render() {
      return _react2.default.createElement('div', {
        dangerouslySetInnerHTML: {
          __html: this.props.data && this.props.templateFunction(this.props.data)
        }
      });
    }
  }
  
  exports.default = Handlebars;
  Handlebars.propTypes = {
    data: _propTypes2.default.object,
    templateFunction: _propTypes2.default.func
  };
  
  },{"mout/lang/deepEquals":453,"prop-types":486,"react":522}],40:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  
  var _react = require('react');
  
  var _react2 = _interopRequireDefault(_react);
  
  var _handlebars = require('./handlebars.component');
  
  var _handlebars2 = _interopRequireDefault(_handlebars);
  
  var _headerTemplate = require('./header.template.hbs');
  
  var _headerTemplate2 = _interopRequireDefault(_headerTemplate);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  const Header = ({ templateData }) => {
    return _react2.default.createElement(_handlebars2.default, {
      key: 'header',
      data: templateData,
      templateFunction: _headerTemplate2.default
    });
  };
  
  exports.default = Header;
  
  },{"./handlebars.component":39,"./header.template.hbs":43,"react":522}],41:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  
  var _reactRedux = require('react-redux');
  
  var _header = require('./header.component');
  
  var _header2 = _interopRequireDefault(_header);
  
  var _header3 = require('./header.selectors');
  
  var headerSelectors = _interopRequireWildcard(_header3);
  
  function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function mapStateToProps(state) {
    return {
      templateData: headerSelectors.template(state)
    };
  }
  
  const HeaderConnected = (0, _reactRedux.connect)(mapStateToProps)(_header2.default);
  
  exports.default = HeaderConnected;
  
  },{"./header.component":40,"./header.selectors":42,"react-redux":509}],42:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.template = exports.navbar = exports.imageUpload = exports.displayLegalWarning = undefined;
  
  var _reselect = require('reselect');
  
  var _spotifyLiburi = require('spotify-liburi');
  
  var _spotifyLiburi2 = _interopRequireDefault(_spotifyLiburi);
  
  var _profile = require('./profile.selectors');
  
  var profileSelectors = _interopRequireWildcard(_profile);
  
  var _application = require('./application.selectors');
  
  var applicationSelectors = _interopRequireWildcard(_application);
  
  function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

  const navbar = exports.navbar = (0, _reselect.createSelector)(applicationSelectors.appName, applicationSelectors.subreddits, (appName, subreddits) => {
    const glueNavItems = [];
    const baseURI = `spotify:app:${appName}`;

    subreddits.forEach(sub => {
      glueNavItems.push({
        id: sub,
        url: `${baseURI}:${sub}`,
        name: sub
      });
    })
  
    return {
      'history': true,
      //Position of argument
      'historyPosition': 0,
      'nav-items': glueNavItems
    };
  });

  const template = exports.template = (0, _reselect.createSelector)(navbar, applicationSelectors.strings, applicationSelectors.options, (innerNavbar, strings, options) => {
    return {
      isVisible: true,
      pageHeader: {
        buttons: {
          customButtons: [
`<button 
  class="button button-green"
  id="reddit-sortby"
  data-interaction-intent="reddit-open-sortby-contextmenu">
  ${strings.sorts.replace("{0}", strings[`sort.${options.sortBy}`])}
</button>`,
`<button 
  class="button button-green ${options.sortBy.match(/top|controversial/i) ? "" : "hidden"}"
  id="reddit-sorttime"
  data-interaction-intent="reddit-open-sorttime-contextmenu">
  ${strings[`sort.${options.sortTime}`]}
</button>`,
`<button 
  class="button button-with-stroke"
  id="reddit-setting"
  data-interaction-intent="reddit-open-setting">
  ${strings.settings}
</button>`,
]
        },
        image: {
          imageUrl: "https://i.scdn.co/image/ae4d05f67fde1545aa300c8f11c814a455aedf03"
        },
        navBar: innerNavbar,
        title: "Reddit",
        useLargeTitle: true
      }
    };
  });
  
  },{"./application.selectors":33,"./profile.selectors":49,"reselect":533,"spotify-liburi":553}],43:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('../../../../libs/glue/templates/headers/page-header.hbs');
  HandlebarsCompiler.registerPartial('../../../../libs/glue/templates/headers/page-header.hbs', partial$0);
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = container.invokePartial(partials["../../../../libs/glue/templates/headers/page-header.hbs"],(depth0 != null ? depth0.pageHeader : depth0),{"name":"../../../../libs/glue/templates/headers/page-header.hbs","data":data,"indent":"  ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.isVisible : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"usePartial":true,"useData":true});
  
  },{"../../../../libs/glue/templates/headers/page-header.hbs":112,"hbsfy/runtime":368}],44:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      return "  <div class=\"legal-permission\">\n    <span>"
      + container.escapeExpression((helpers.loc || (depth0 && depth0.loc) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"header.LegalPermission",{"name":"loc","hash":{},"data":data}))
      + "</span>\n  </div>\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.displayLegalWarning : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"useData":true});
  
  },{"hbsfy/runtime":368}],45:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
  
    return "  <div class=\"upload-container\n    "
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.displayAlways : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n    "
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.displayOnHover : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\">\n    <div class=\"background-stripe\"></div>\n    <div class=\"background-big\"></div>\n    <label class=\"add-image-label\" >"
      + container.escapeExpression((helpers.loc || (depth0 && depth0.loc) || helpers.helperMissing).call(alias1,"header.change_short_profile_image",{"name":"loc","hash":{},"data":data}))
      + "</label>\n  </div>\n";
  },"2":function(container,depth0,helpers,partials,data) {
      return " display-always ";
  },"4":function(container,depth0,helpers,partials,data) {
      return " display-hover ";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.displaySometimes : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"useData":true});
  
  },{"hbsfy/runtime":368}],46:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  
  var _propTypes = require('prop-types');
  
  var _propTypes2 = _interopRequireDefault(_propTypes);
  
  var _react = require('react');
  
  var _react2 = _interopRequireDefault(_react);
  
  var _reactDom = require('react-dom');
  
  var _reactDom2 = _interopRequireDefault(_reactDom);
  
  var _spotifyEvents = require('../../../../libs/spotify-events');
  
  var _spotifyEvents2 = _interopRequireDefault(_spotifyEvents);
  
  var _imageUploadTemplateOverlay = require('./image-upload.template.overlay.hbs');
  
  var _imageUploadTemplateOverlay2 = _interopRequireDefault(_imageUploadTemplateOverlay);
  
  var _imageUploadTemplateLegalWarning = require('./image-upload.template.legal-warning.hbs');
  
  var _imageUploadTemplateLegalWarning2 = _interopRequireDefault(_imageUploadTemplateLegalWarning);
  
  var _offlineView = require('./offline-view.component');
  
  var _offlineView2 = _interopRequireDefault(_offlineView);
  
  var _errorView = require('./error-view.component');
  
  var _errorView2 = _interopRequireDefault(_errorView);
  
  var _content = require('./content.connected');
  
  var _content2 = _interopRequireDefault(_content);
  
  var _header = require('./header.connected');
  
  var _header2 = _interopRequireDefault(_header);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  class Main extends _react2.default.PureComponent {
    constructor(props) {
      super(props);
  
    }
  
    componentDidMount() {
      _spotifyEvents2.default.update();
      this.addEditMode();
  
      window.parent.postMessage({
        type: 'notify_ready',
        pageId: 'profile'
      }, '*');
    }
  
    componentDidUpdate(prevProps) {
      if (JSON.stringify(prevProps.data.appArgs) !== JSON.stringify(this.props.data.appArgs)) {
        window.parent.postMessage({
          type: 'notify_ready',
          pageId: 'profile'
        }, '*');
      }
  
      _spotifyEvents2.default.update();
      this.addEditMode();
    }

  
    addEditMode() {
      if (this.headerNode) {
        const imageNode = _reactDom2.default.findDOMNode(this.headerNode).querySelector('.glue-page-header__content .glue-page-header__image');
        if (imageNode) {
          const editNode = _reactDom2.default.findDOMNode(this.headerNode).querySelector('.upload-container');
          if (editNode) {
            editNode.parentNode.removeChild(editNode);
          }
  
          const data = this.props.data.header && this.props.data.header.imageUpload;
          if (data) {
            const tempNode = document.createElement('div');
            tempNode.innerHTML = (0, _imageUploadTemplateOverlay2.default)(data);
            if (tempNode.firstElementChild) {
              imageNode.appendChild(tempNode.firstElementChild);
              imageNode.classList.add('header-avatar-with-upload');
            }
          }
        }
  
        // This breakpoint is used in page-header.scss in libs/glue for showing
        // a different set of buttons (for layout constraints), so we need to
        // add the legal text to the right container based on that breakpoint.
        const isSmallWindow = window.matchMedia('(max-width: 859px)').matches;
        const buttonsNode = _reactDom2.default.findDOMNode(this.headerNode).querySelector(`
            .glue-page-header__${isSmallWindow ? 'content > ' : 'data'}
            .glue-page-header__buttons
          `.trim());
        if (buttonsNode) {
          const legalNode = _reactDom2.default.findDOMNode(this.headerNode).querySelector('.legal-permission');
          if (legalNode) {
            legalNode.parentNode.removeChild(legalNode);
          }
          const data = this.props.data.header && this.props.data.header.imageUpload;
          if (data) {
            const tempNode = document.createElement('div');
            tempNode.innerHTML = (0, _imageUploadTemplateLegalWarning2.default)(data);
            if (tempNode.firstElementChild) {
              buttonsNode.appendChild(tempNode.firstElementChild);
            }
          }
        }
      }
    }
  
    render() {
      const { data, displayErrorOverlay, displayOfflineOverlay } = this.props;
      if (!data) {
        return null;
      }
  
      if (displayOfflineOverlay) {
        return _react2.default.createElement(_offlineView2.default, null);
      }
  
      if (displayErrorOverlay) {
        return _react2.default.createElement(_errorView2.default, null);
      }
  
      return _react2.default.createElement(
        'div',
        null,
        _react2.default.createElement(
          'div',
          { className: 'app-header' },
          _react2.default.createElement(_header2.default, {
            ref: node => {
              this.headerNode = node;
            }
          })
        ),
        _react2.default.createElement(
          'div',
          { className: 'app-content', key: 'content' },
          _react2.default.createElement(_content2.default, null)
        )
      );
    }
  }
  
  exports.default = Main;
  Main.propTypes = {
    data: _propTypes2.default.object,
  };
  
  },{"../../../../libs/spotify-events":260,"./content.connected":35,"./error-view.component":38,"./header.connected":41,"./image-upload.template.legal-warning.hbs":44,"./image-upload.template.overlay.hbs":45,"./offline-view.component":48,"prop-types":486,"react":522,"react-dom":497}],47:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  
  var _reactRedux = require('react-redux');
  
  var _reselect = require('reselect');
  
  var _header = require('./header.selectors');
  
  var headerSelectors = _interopRequireWildcard(_header);
  
  var _application = require('./application.selectors');
  
  var applicationSelectors = _interopRequireWildcard(_application);
  
  var _profile = require('./profile.selectors');
  
  var profileSelectors = _interopRequireWildcard(_profile);
  
  var _main = require('./main.component');
  
  var _main2 = _interopRequireDefault(_main);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
  
  const displayOfflineOverlay = (0, _reselect.createSelector)(applicationSelectors.isOnline, x => x === false);
  
  const mapStateToProps = state => ({
    data: {
      header: headerSelectors.template(state), // needed because the new headers profile hack
      appArgs: applicationSelectors.applicationArguments(state)
    },
    displayErrorOverlay: profileSelectors.hasError(state) && !displayOfflineOverlay(state),
    displayOfflineOverlay: displayOfflineOverlay(state)
  });

  const MainComponentConnected = (0, _reactRedux.connect)(mapStateToProps)(_main2.default);
  
  exports.default = MainComponentConnected;
  
  },{"./application.selectors":33,"./header.selectors":42,"./main.component":46,"./profile.selectors":49,"react-redux":509,"reselect":533}],48:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  
  var _react = require('react');
  
  var _react2 = _interopRequireDefault(_react);
  
  var _handlebars = require('./handlebars.component');
  
  var _handlebars2 = _interopRequireDefault(_handlebars);
  
  var _errorAndOffline = require('../../../../libs/glue/templates/error-and-offline.hbs');
  
  var _errorAndOffline2 = _interopRequireDefault(_errorAndOffline);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  class Error extends _react2.default.PureComponent {
    render() {
      return _react2.default.createElement(_handlebars2.default, {
        data: { useDefaultOffline: true },
        templateFunction: _errorAndOffline2.default
      });
    }
  }
  exports.default = Error;
  
  },{"../../../../libs/glue/templates/error-and-offline.hbs":97,"./handlebars.component":39,"react":522}],49:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.hasError = exports.imageUri = exports.isLoaded = exports.contents = undefined;
  
  var _reselect = require('reselect');
  
  var _application = require('./application.selectors');
  
  var applicationSelectors = _interopRequireWildcard(_application);
  
  function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
  
  const currentProfileData = (0, _reselect.createSelector)(applicationSelectors.appName, state => state.profiles, (appName, profiles) => profiles[appName]);

  const isLoaded = exports.isLoaded = (0, _reselect.createSelector)(currentProfileData, x => !!x);
  
  const contents = exports.contents = (0, _reselect.createSelector)(currentProfileData, x => x && (x.contents));
  
  const imageUri = exports.imageUri = (0, _reselect.createSelector)(currentProfileData, x => x && x.imageUri);
  
  const hasError = exports.hasError = (0, _reselect.createSelector)(currentProfileData, x => x && x.hasError);
  
  },{"./application.selectors":33,"reselect":533}],50:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.default = setupView;
  
  var _react = require('react');
  
  var _react2 = _interopRequireDefault(_react);
  
  var _reactDom = require('react-dom');
  
  var _reactDom2 = _interopRequireDefault(_reactDom);
  
  var _reactRedux = require('react-redux');
  
  var _main = require('./main.connected');
  
  var _main2 = _interopRequireDefault(_main);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function setupView(reduxStore) {
    _reactDom2.default.render(_react2.default.createElement(
      _reactRedux.Provider,
      { store: reduxStore },
      _react2.default.createElement(_main2.default, null)
    ), document.getElementById('render-target'));
  }
  },{"./main.connected":47,"react":522,"react-dom":497,"react-redux":509}],51:[function(require,module,exports){
  'use strict';
  
  var _spotifyCosmosApi = require('spotify-cosmos-api');
  
  var _spotifyCosmosApi2 = _interopRequireDefault(_spotifyCosmosApi);
  
  var _jsonStableStringify = require('json-stable-stringify');
  
  var _jsonStableStringify2 = _interopRequireDefault(_jsonStableStringify);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  let subscriptions = [];
  
  module.exports = {
    get,
    subscribe,
    clearSubscriptions,
    fetch
  };
  
  /**
   * Debug API
   * Exposed on the window for debugging and TA tests
   */
  if (typeof window !== 'undefined') {
    window.abba = {
      get,
      // this should only be used by TA tests for logging purposes
      getAll,
      getRequestedFlagNames
    };
  }
  
  function _getFlagsFromCosmos(requestedFlags, callback) {
    _spotifyCosmosApi2.default.resolver.post({
      url: 'sp://abba/v1/flags',
      body: {
        flags: requestedFlags
      }
    }, (err, response) => {
      if (err) {
        callback(err);
      } else {
        const { flags = [] } = response.getJSONBody();
        callback(null, flags);
      }
    });
  }
  
  function fetch(flags) {
    if (typeof flags === 'string') {
      return fetch([flags]).then(cells => cells[flags]);
    }
  
    const cells = {};
    const overrides = readOverrides();
    const notOverridden = flags.reduce((acc, flag) => {
      if (overrides.hasOwnProperty(flag)) {
        console.warn(`Abba: Override for ${flag} : ${overrides[flag]}`);
        cells[flag] = overrides[flag];
      } else {
        acc.push(flag);
      }
      return acc;
    }, []);
  
    if (!notOverridden.length) {
      return Promise.resolve(cells);
    }
  
    return new Promise((resolve, reject) => {
      _getFlagsFromCosmos(notOverridden, (err, fetchedFlags) => {
        if (err) {
          reject(err);
          return;
        }
        fetchedFlags.forEach(flag => {
          cells[flag.featureName] = flag.cell;
        });
        if (Object.keys(cells).length !== flags.length) {
          flags.forEach(flag => {
            if (!cells.hasOwnProperty(flag)) {
              cells[flag] = null;
            }
          });
        }
        resolve(cells);
      });
    });
  }
  
  function get(flag, callback) {
    const overrides = readOverrides();
    if (overrides.hasOwnProperty(flag)) {
      console.warn(`Abba: Override for ${flag} : ${overrides[flag]}`);
      setTimeout(() => callback(null, overrides[flag]), 0);
      return;
    }
  
    _getFlagsFromCosmos([flag], (err, flags) => {
      if (err) {
        callback(err);
        return;
      }
      const featureFlagCell = flags[0] && flags[0].featureName === flag && flags[0].cell;
      callback(null, featureFlagCell || null);
    });
  }
  
  function getAll(callback) {
    console.warn('Abba:getAll should only be used by TA tests for logging purposes.');
    _spotifyCosmosApi2.default.resolver.get({
      url: 'sp://abba/v1/all_flags',
      body: {}
    }, (err, response) => {
      if (err) {
        callback(err);
      } else {
        const { flags = [] } = response.getJSONBody();
        // {"featureName":"s4a-hub-canvas","cell":"Control"}
        const flagMap = flags.reduce((acc, flagObj) => {
          acc[flagObj.featureName] = flagObj.cell;
          return acc;
        }, {});
        callback(null, (0, _jsonStableStringify2.default)(flagMap, { space: 2 }));
      }
    });
  }
  
  function getRequestedFlagNames(callback) {
    _spotifyCosmosApi2.default.resolver.get({
      url: 'sp://abba/v1/requested_flag_names',
      body: {}
    }, (err, response) => {
      if (err) {
        callback(err);
      } else {
        callback(null, JSON.stringify(response.getJSONBody().sort(), null, 2));
      }
    });
  }
  
  function subscribe(flag, callback) {
    let oldValue;
  
    const update = newValue => {
      // ABBA might pub even though the value we're after haven't actually
      // changed, so must make sure that the flag we're tracking actually
      // changed value.
      if (newValue !== oldValue) {
        oldValue = newValue;
        callback(null, newValue);
      }
    };
  
    let cosmosSubscription = _spotifyCosmosApi2.default.resolver.subscribe({
      url: 'sp://abba/v1/flags',
      body: {
        flags: [flag]
      }
    }, subscribeError => {
      if (subscribeError) {
        callback(subscribeError);
        return;
      }
  
      get(flag, (err, newValue) => {
        if (err) {
          callback(err);
          return;
        }
        update(newValue);
      });
    });
  
    let subscription = {
      cancel: () => {
        if (cosmosSubscription) cosmosSubscription.cancel();
        const index = subscriptions[flag].indexOf(subscription);
        if (index !== -1) {
          subscriptions[flag].splice(index, 1);
        }
        subscription = cosmosSubscription = null;
      },
      _update: update
    };
  
    if (!(flag in subscriptions)) {
      subscriptions[flag] = [];
    }
    subscriptions[flag].push(subscription);
  
    return subscription;
  }
  
  function clearSubscriptions() {
    subscriptions = [];
  }
  
  function readOverrides() {
    const value = typeof __spotify !== 'undefined' && __spotify.product_state ? __spotify.product_state.abbaOverrides || __spotify.product_state.abbaoverrides : '';
    if (value) {
      try {
        return JSON.parse(value);
      } catch (e) {
        console.error('error parsing value=%o, error=%o', value, e);
      }
    }
    return {};
  }
  
  },{"json-stable-stringify":373,"spotify-cosmos-api":543}],52:[function(require,module,exports){
  'use strict';
  
  const TEN_PER_SECOND_FOR_A_YEAR = 10 * 60 * 60 * 24 * 365;
  
  const COSMOS_VERBS = ['GET', 'HEAD', 'POST', 'PUT', 'SUB', 'PATCH', 'DELETE'];
  
  const ERROR_PREFIX = 'bridge.cosmosRequest: ';
  
  // Base function that doesn't have any dependencies on it's own. Expects to get
  // state (a plain, empty object) injected in addition to a bridge request
  // function.
  function cosmosRequest(state, bridge, opts, callback) {
    if (opts.url) {
      throw new Error(`${ERROR_PREFIX}"url" is not a valid option, did you mean "uri"?`);
    }
  
    if (typeof opts.uri !== 'string') {
      throw new Error(`${ERROR_PREFIX}Expected uri to be string.`);
    }
  
    if (COSMOS_VERBS.indexOf(opts.method) === -1) {
      throw new Error(`${ERROR_PREFIX}Method must match valid verb in uppercase (GET, POST etc)`);
    }
  
    if (opts.body && typeof opts.body !== 'string') {
      throw new Error(`${ERROR_PREFIX}If body is provided it should be a string.`);
    }
  
    if (opts.headers && Object.prototype.toString.call(opts.headers) !== '[object Object]') {
      throw new Error(`${ERROR_PREFIX}Expected headers be a plain object.`);
    }
  
    const cosmosOptions = {
      action: opts.method,
      uri: opts.uri
    };
    if (opts.body) cosmosOptions.body = opts.body;
    if (opts.headers) cosmosOptions.headers = opts.headers;
  
    /* Cosmos requires each request to have a unique ID. Ideally, this would be
    uuids, but unfortunately, it is integers. This means that in apps that for
    some reason need to use spotify-cosmos-api, there would between the two
    counters if cosmosRequest started from 0. To make it safe for the two
    implemenations to co-exist if needed, we begin counting at
    significantly higher number: */
    state.requestIDCounter = state.requestIDCounter || TEN_PER_SECOND_FOR_A_YEAR;
    state.requestIDCounter++;
  
    const requestArguments = [state.requestIDCounter, cosmosOptions];
  
    let isCanceled = false;
    const stateRequestIDCounter = state.requestIDCounter;
  
    function cancelFunction() {
      isCanceled = true;
      bridge('cosmos_request_cancel', [stateRequestIDCounter]);
    }
  
    // run is a recursive function that does a bridge request and then keeps
    // pulling values after every response in the case of a subscription. In
    // case we are not subscribing, or the cancelFunction has been called,
    // run sends cosmos_request_cancel and terminates.
    function run(isFirst, requestIDCounter) {
      const messageName = isFirst ? 'cosmos_request_create' : 'cosmos_request_pull';
      bridge(messageName, requestArguments, function (error, responseString) {
        // The request to cancel may not have gotten to the bridge
        // before the original request got resolved,
        // so make sure not to continue if we detect it's been canceled.
        if (isCanceled) {
          return;
        }
        try {
          if (callback) {
            if (error) {
              callback(error);
            } else {
              callback(null, responseString);
            }
          }
        } finally {
          if (opts.method !== 'SUB') {
            // If the request was a non-SUB, tell core to clean up the request.
            // NOTE: Not sure if this is strictly necessary,
            // have not been to investigate in C++-land, so for now
            // we are just mimicing the behavior of spotify-cosmos-api.
            bridge('cosmos_request_cancel', [requestIDCounter]);
          } else if (!isCanceled) {
            // maybe canceled in above callback
            run(false, requestIDCounter);
          }
        }
      });
    }
  
    run(true, stateRequestIDCounter);
  
    return cancelFunction;
  }
  
  module.exports = cosmosRequest;
  
  },{}],53:[function(require,module,exports){
  (function (global){
  'use strict';
  
  const debug = require('../debug')('bridge');
  const defer = require('spotify-deferred');
  
  const cosmosBaseFunction = require('./cosmos');
  
  // NOTE: Implicit global state.
  let scheduledCoreFlush = false;
  let cosmosState = null;
  
  exports.cosmos = cosmos;
  exports.cosmosJSON = cosmosJSON;
  exports.request = request;
  exports._request = _request;
  
  function cosmos() {
    if (!cosmosState) cosmosState = {};
  
    const baseArguments = [cosmosState, request];
    const cancelFunction = cosmosBaseFunction.apply(null, baseArguments.concat(Array.prototype.slice.call(arguments)));
    return cancelFunction;
  }
  
  function _createCallbackWrapper(callback) {
    return function callbackWrapper(err, response) {
      let newErr = err;
      if (!newErr) {
        if (response && !response.body && response.status >= 400) {
          newErr = new Error(`${response.uri} responded with status ${response.status}`);
          callback(newErr, response);
          return;
        }
        let parsedResponse;
        try {
          parsedResponse = JSON.parse(response.body);
        } catch (e) {
          e.message = `Failed to parse cosmos response: ${e.message}`;
          callback(e);
          return;
        }
        callback(null, parsedResponse);
      } else {
        callback(newErr, response);
      }
    };
  }
  
  function cosmosJSON(opts, callback) {
    if (opts.body) {
      opts.body = JSON.stringify(opts.body);
    }
    const callbackWrapper = callback ? _createCallbackWrapper(callback) : null;
    const cancelFunction = cosmos(opts, callbackWrapper);
    return cancelFunction;
  }
  
  function request(name, optArgs, optCallback) {
    const args = optArgs || [];
    const callback = getCallback(name, args, optCallback);
  
    debug(`req:${name}`, args);
  
    exports._request(name, args, callback);
  
    if (name.endsWith('_metadata') && !scheduledCoreFlush) {
      scheduledCoreFlush = true;
      defer(flushCore);
    }
  
    return exports;
  }
  
  function _request(name, args, callback) {
    if (global && typeof global._getSpotifyModule === 'function') {
      global._getSpotifyModule('bridge').executeRequest(JSON.stringify({
        name: name,
        args: args
      }), {
        onSuccess: getSuccessHandler(callback),
        onFailure: getFailureHandler(callback, name, args)
      });
    }
  }
  
  function getCallback(name, args, optUserCallback) {
    const userCallback = optUserCallback || function () {};
  
    return function (error, data) {
      if (error) {
        if (error.name === 'timeout') {
          // Set the delay between 300ms and 400ms
          const delay = 300 + Math.floor(Math.random() * 100);
  
          debug('timeout', error.message);
  
          // Retry the request
          setTimeout(function () {
            request(name, args, userCallback);
          }, delay);
          return;
        }
      }
  
      debug(`res:${name}`, args, data);
  
      userCallback(error, data);
    };
  }
  
  function getSuccessHandler(requestCallback) {
    return function (data) {
      let parsed;
  
      debug('success', data);
  
      try {
        parsed = JSON.parse(data);
      } catch (error) {
        requestCallback(error);
      }
  
      if (parsed) {
        requestCallback(null, parsed);
      }
    };
  }
  
  function getFailureHandler(requestCallback, name, args) {
    return function (data) {
      let parsed;
  
      debug('failure', data);
      try {
        parsed = JSON.parse(data);
      } catch (error) {
        if (data instanceof Error) {
          requestCallback(data);
        } else {
          error.message += `\nResponse Data: ${data}`;
          requestCallback(error);
        }
      }
  
      if (parsed) {
        requestCallback(createError(name, args, parsed));
      }
    };
  }
  
  function createError(name, args, response) {
    const argsString = JSON.stringify(args);
    const innerDebug = ` (bridge message: '${name}', args: ${argsString})`;
    const msg = response.message + innerDebug;
    const error = new Error(msg);
    error.name = response.error;
  
    return error;
  }
  
  function flushCore() {
    scheduledCoreFlush = false;
    request('core_flush');
  }
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../debug":58,"./cosmos":52,"spotify-deferred":546}],54:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  const operationCanceledError = {
    name: 'OperationCanceledError',
    message: 'An async operation was cancelled'
  };
  
  class CancellationToken {
    // Internal constructor: only a CancellationTokenSource should create
    // a CancellationToken.
    constructor(source) {
      this.source = source;
      this.requestId = source.requestId;
    }
  
    isCanceled() {
      return this.requestId !== this.source.requestId;
    }
  
    throwIfCanceled() {
      if (this.isCanceled()) {
        throw operationCanceledError;
      }
    }
  
    errorIfCanceled() {
      if (this.isCanceled()) {
        return operationCanceledError;
      }
      return undefined;
    }
  
    operationCanceledError() {
      return operationCanceledError;
    }
  }
  
  class CancellationTokenSource {
    constructor() {
      this.requestId = 0;
    }
  
    // Create a new token that can be cancelled by canlling cancel() in the
    // CancellationTokenSource.
    token() {
      return new CancellationToken(this);
    }
  
    // Cancel all the tokens created so far.
    cancel() {
      this.requestId++;
    }
  }
  exports.default = CancellationTokenSource;
  
  },{}],55:[function(require,module,exports){
  (function (global){
  "use strict";
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  const hasFlag = exports.hasFlag = name => {
    return !!(global.__spotify && global.__spotify.product_state && global.__spotify.product_state.hasOwnProperty(name));
  };
  
  const hasValue = exports.hasValue = (name, value) => {
    return hasFlag(name) && global.__spotify.product_state[name] === value;
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{}],56:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.ContextMenu = ContextMenu;
  const EventEmitter = require('spotify-eventemitter');
  
  const KEY_DOWN = 40;
  const KEY_UP = 38;
  const KEY_LEFT = 37;
  const KEY_RIGHT = 39;
  const KEY_ESCAPE = 27;
  const KEY_ENTER = 13;
  
  const config = exports.config = {
    // The edges of the menu should not be too close to the windows edges
    minDistanceFromLeftRight: 10,
    // The top edge of the menu should not be too close to the windows edges
    minDistanceFromTop: 40,
    // The top edge of the menu should not be too close to the windows edges
    minDistanceFromBottom: 10,
    // Keep a bit of space from the x position the menu was opened from.
    xOffset: 3
  };
  
  function ContextMenu(options = {}) {
    this._container = options.container;
    this._rawItems = [];
    this._items = {};
    this._visible = false;
    this._filter = options.filter;
    this._dropdown = options.dropdown;
    this._active = false;
    this._currentItem = null;
    this._parent = options.parent;
    this._mode = 'mouse';
    this._lastKeyEventTimestamp = 0;
  
    this._id = options.id;
    if (typeof options.id !== 'number' && !options.id) {
      this._id = 'context-menu';
    }
  
    if (!this._container) {
      this._container = document.getElementById(`${this._id}-container`);
  
      if (this._container) {
        this._container.innerHTML = '';
      } else {
        this._container = document.createElement('div');
        this._container.setAttribute('id', `${this._id}-container`);
        this._container.classList.add('context-menu-container');
        this._container.style.display = 'none';
        document.body.appendChild(this._container);
      }
    }
  
    this._element = document.getElementById(this._id);
  
    this._handleKeyDown = this._handleKeyDown.bind(this);
    this._handleKeyNavigation = this._handleKeyNavigation.bind(this);
    this._handleFilter = this._handleFilter.bind(this);
    this._handleCloseFilter = this._handleCloseFilter.bind(this);
    this._handleMouseDown = this._handleMouseDown.bind(this);
  
    if (!this._element) {
      this._element = document.createElement('div');
  
      this._element.setAttribute('class', 'context-menu');
      this._element.setAttribute('id', this._id);
      this._container.appendChild(this._element);
  
      // Using `mouseup` rathen than `click` here will enable the macOS-like
      // behaviour described in KM-11465, albeit only when triggering the
      // contextmenu from the same frame, ie: zlink, not other content apps.
      this._element.addEventListener('mouseup', this._handleMouseup.bind(this));
      this._element.addEventListener('mousemove', this._handlePotentialHover.bind(this));
  
      if (this._filter) {
        const filterInput = document.createElement('input');
        filterInput.setAttribute('type', 'text');
        filterInput.setAttribute('id', `${this._localIdToDom()}-filter`);
  
        const filterContainer = document.createElement('div');
        filterContainer.classList.add('filter-search');
        filterContainer.appendChild(filterInput);
  
        this._element.appendChild(filterContainer);
      }
  
      if (!this._parent) {
        this._container.addEventListener('mousedown', this._handleMouseDown);
        this._container.addEventListener('touchstart', this._handleMouseDown);
  
        this.setActive();
      }
    }
  
    this._renderItems(options.items || []);
  }
  
  ContextMenu.prototype = Object.create(EventEmitter.prototype);
  
  /**
   * Deletes completely the context menu and all its
   * associated DOM objects
   *
   * @return undefined
   */
  ContextMenu.prototype.remove = function () {
    if (this._visible) {
      this.hide();
    }
  
    this._container.removeEventListener('mousedown', this._handleMouseDown);
    this._container.removeEventListener('touchstart', this._handleMouseDown);
  
    this._container.removeChild(this._element);
  
    this.clearItems();
  };
  
  ContextMenu.prototype.moveContainerToElement = function (element) {
    this._container.parentNode.removeChild(this._container);
    element.appendChild(this._container);
  };
  
  /**
   * Hides the context menu but keeps its current items and
   * DOM objects
   *
   * @return undefined
   */
  ContextMenu.prototype.hide = function (hideAll) {
    if (!this._parent || hideAll) {
      this._container.style.display = 'none';
      document.body.removeEventListener('keydown', this._handleKeyDown);
    } else {
      this._element.style.display = 'none';
    }
    this._element.style.width = 'auto';
    document.body.removeEventListener('keydown', this._handleKeyNavigation);
  
    this.disableFilter();
  
    this._hideSubMenus();
    this._visible = false;
    this.setInactive();
    this._setCurrentItem(null);
    delete this._elementWidth;
  
    this.emit('hide');
  };
  
  /**
   * Shows the context menu in the specified position with
   * the specified items
   *
   * @param  {Object} position  (optional) contains {x, y} as position in pixels
   * @param  {Array} items      (optional) array of items that will be displayed
   *
   * @return undefined
   */
  ContextMenu.prototype.show = function (position, items) {
    if (!this._parent) {
      document.body.addEventListener('keydown', this._handleKeyDown);
      this.setActive();
    }
    document.body.addEventListener('keydown', this._handleKeyNavigation);
  
    this._element.style.opacity = '0';
    this._element.style.display = 'inline-block';
    this._container.style.display = 'block';
  
    if (items) {
      this.clearItems();
      this._renderItems(items);
    }
  
    this._visible = true;
  
    this._reposition(position);
  
    this.emit('show');
  };
  
  ContextMenu.prototype.enableFilter = function () {
    this._filterElement = document.getElementById(`${this._localIdToDom()}-filter`);
    this._filterElement.addEventListener('keydown', this._handleCloseFilter);
    this._filterElement.addEventListener('input', this._handleFilter);
    this._filterElement.focus();
  };
  
  ContextMenu.prototype.disableFilter = function () {
    if (this._filterElement) {
      this._filterElement.removeEventListener('keydown', this._handleCloseFilter);
      this._filterElement.removeEventListener('input', this._handleFilter);
      this._filterElement.value = '';
      this._filterElement.blur();
      this._filterFn();
      this._filterElement = null;
    }
  };
  
  /**
   * Returns whether or not the context menu is visible
   *
   * @return {Boolean} current visibility
   */
  ContextMenu.prototype.isVisible = function () {
    return this._visible;
  };
  
  /**
   * Check if an item exists in the contextmenu
   * @param  {string/Array} id  If it's an array, it looks for subitems based on the array
   *
   * @return bool
   */
  ContextMenu.prototype.hasItem = function (id) {
    return !!this._getItem(id).item;
  };
  
  /**
   * Updates an item/subitem of the context menu
   * @param  {string/Array} id  If it's an array, it looks for subitems based on the array
   * @param  {Object} data      The new item data
   *
   * @return undefined
   */
  ContextMenu.prototype.updateItem = function (id, data) {
    let item = this._getItem(id);
    const parent = item.parent;
    item = item.item;
  
    if (!item) {
      return;
    }
  
    const el = document.getElementById(parent._localIdToDom(item.id));
    if (el) {
      if (item.parent) {
        item.submenu.remove();
        item.submenu.off('click');
      }
      el.parentNode.replaceChild(parent._renderItem(item.id, data), el);
    }
  };
  
  /**
   * Add several items to the context menu
   * @param {Array} items     The items to add
   * @param {Array} parents  Where to add the items. If null, add them to the first level
   *
   * @return undefined
   */
  ContextMenu.prototype.addItems = function (items, parents) {
    items.forEach(item => {
      this.addItem(item, parents);
    });
  };
  
  /**
   * Add an item to the context menu
   * @param {Object} item     The item to add
   * @param {Array} parents  Where to add the item. If null, add them to the first level
   *
   * @return undefined
   */
  ContextMenu.prototype.addItem = function (data, parentsArg) {
    let id;
  
    if (parentsArg) {
      const parents = parentsArg.slice(0);
      id = parents.shift();
      if (id !== undefined && this._items[id] && this._items[id].submenu) {
        this._items[id].submenu.addItem(data, parents);
        return;
      }
    } else {
      id = data.id;
    }
  
    const item = this._items[id];
    if (item) {
      this.updateItem(id, data);
    } else {
      this._element.appendChild(this._renderItem(null, data));
    }
  
    if (this._visible) {
      this.show();
    }
  };
  
  /**
   * Removes an item/subitem from the context menu
   * @param  {string/Array} id  If it's an array, it looks for subitems based on the array
   *
   * @return undefined
   */
  ContextMenu.prototype.removeItem = function (id) {
    let item = this._getItem(id);
    const parent = item.parent;
    item = item.item;
  
    if (!item) {
      return;
    }
  
    if (item.submenu) {
      item.submenu.remove();
      item.submenu.off('click');
    }
  
    const el = document.getElementById(parent._localIdToDom(item.id));
    if (el) {
      // Rendering the item with no data will cause some clean up
      parent._renderItem(item.id, undefined);
  
      el.parentNode.removeChild(el);
    }
  
    delete parent._items[item.id];
  
    if (parent._visible && parent._isEmpty()) {
      parent.hide();
    }
    if (parent._visible) {
      this.dedupeSeps();
    }
  };
  
  /**
   * Removes all the items from the array
   *
   * @return undefined
   */
  ContextMenu.prototype.clearItems = function () {
    /* eslint-disable guard-for-in */
    for (const i in this._items) {
      this.removeItem(i);
    }
    /* eslint-enable guard-for-in */
  };
  
  /**
   * Removes unnecessary separators from the context menu. For example, separators
   * as the first items of the menu or at the end. Also separators that are together
   * will be unified
   *
   * @return undefined
   */
  ContextMenu.prototype.dedupeSeps = function () {
    // remove additional separators in the beginning
    while (this._rawItems.length > 0 && this._items[this._rawItems[0].id].separator) {
      this.removeItem(this._rawItems[0].id);
    }
  
    // remove additional separators at the end
    while (this._rawItems.length > 0 && this._items[this._rawItems[this._rawItems.length - 1].id].separator) {
      this.removeItem(this._rawItems[this._rawItems.length - 1].id);
    }
  
    // remove additional separators in the middle
    let sep = false;
    for (let i = 0; i < this._rawItems.length; i++) {
      const item = this._items[this._rawItems[i].id];
  
      if (item.separator) {
        if (sep) {
          this.removeItem(item.id);
          i--; // compensate the previous removed item
        }
        sep = true;
      } else {
        sep = false;
      }
    }
  };
  
  ContextMenu.prototype._getItem = function (idArg) {
    let id = idArg;
    let itemId;
    if (Array.isArray(id)) {
      id = id.slice(0);
  
      itemId = id.shift();
      if (id.length) {
        if (this._items[itemId] && this._items[itemId].submenu) {
          return this._items[itemId].submenu._getItem(id);
        }
        return {
          parent: this,
          item: null
        };
      }
    } else {
      itemId = id;
    }
  
    if (!this._items[itemId]) {
      return {
        parent: this,
        item: null
      };
    }
  
    return {
      item: this._items[itemId],
      parent: this
    };
  };
  
  ContextMenu.prototype._isEmpty = function () {
    return !Object.keys(this._items).length;
  };
  
  ContextMenu.prototype._localIdToDom = function (id) {
    const out = [];
  
    let rootId;
  
    /* eslint-disable consistent-this */
    let parent = this;
    while (parent) {
      rootId = parent._id;
      parent = parent._parent;
    }
    /* eslint-enable consistent-this */
  
    const thisId = this._id.substring(rootId.length + 1);
  
    out.push(rootId);
    out.push('item');
    if (thisId) {
      out.push(thisId);
    }
  
    if (id !== undefined) {
      out.push(id);
    }
  
    return out.join('-');
  };
  
  ContextMenu.prototype._domIdToLocal = function (id) {
    const substr = `${this._id}-item-`;
  
    return id.substring(substr.length);
  };
  
  ContextMenu.prototype._parseItem = function (idArg, item) {
    let id = idArg;
    let rawItem = this._rawItems.filter(el => el.id === id)[0];
  
    if (!item) {
      if (rawItem) {
        const usedId = rawItem.id;
        this._rawItems.splice(this._rawItems.indexOf(rawItem), 1);
        delete this._items[usedId];
      }
      return false;
    }
  
    // Update previous item
    if (rawItem) {
      /* eslint-disable guard-for-in */
      for (const i in item) {
        rawItem[i] = item[i];
      }
      /* eslint-enable guard-for-in */
    } else {
      rawItem = item;
      id = this._getAvailableId();
      this._rawItems.push(rawItem);
    }
  
    rawItem.id = rawItem.id || id;
    const el = {
      classes: [],
      styles: {},
      text: rawItem.text,
      secondaryText: rawItem.secondaryText,
      fn: rawItem.fn,
      id: rawItem.id
    };
  
    if (rawItem.items) {
      el.classes.push('parent');
      el.parent = true;
  
      // if the submenu has no items, set it as disabled by default
      if (rawItem.items.length === 0 && !('disabled' in rawItem)) {
        rawItem.disabled = true;
      }
  
      el.submenu = this._createSubMenu(rawItem.id, rawItem.items, !!rawItem.filter);
      el.submenu.on('click', this.emit.bind(this, 'click'));
    }
  
    if (rawItem.secondaryText) {
      el.classes.push('secondary');
    }
  
    if (rawItem.disabled) {
      el.disabled = true;
      el.classes.push('disabled');
    }
  
    if (rawItem.value !== undefined) {
      el.checkbox = true;
      el.value = rawItem.value;
      if (el.value) {
        el.classes.push('with-icon');
        el.classes.push('spoticon-check-16');
      }
    }
  
    if (rawItem.icon) {
      switch (rawItem.icon.type) {
        case 'spoticon':
          el.classes.push('with-icon');
          el.classes.push(`spoticon-${rawItem.icon.value}-16`);
          break;
        case 'svg':
          el.classes.push('with-icon');
          el.classes.push('with-icon-svg');
          el.styles['--svg-icon'] = `url('${window.location.origin}${rawItem.icon.value}')`;
          break;
        default:
          break;
      }
    }
  
    if (el.text === undefined) {
      el.separator = true;
      el.classes.push('sep');
      el.text = '';
    } else {
      el.classes.push('item');
    }
  
    this._items[rawItem.id] = el;
    return el;
  };
  
  ContextMenu.prototype._renderItems = function (items) {
    const fragment = document.createDocumentFragment();
    items.forEach(el => {
      fragment.appendChild(this._renderItem(null, el));
    });
    this._element.appendChild(fragment);
  };
  
  ContextMenu.prototype._renderItem = function (id, el) {
    const item = this._parseItem(id, el);
    if (!item) {
      return '';
    }
  
    const container = document.createElement('div');
    container.classList.add(...item.classes);
    container.setAttribute('id', this._localIdToDom(item.id));
  
    Object.keys(item.styles).forEach(prop => {
      container.style.setProperty(prop, item.styles[prop]);
    });
  
    const text = document.createElement('span');
    text.classList.add('text');
    text.textContent = item.text;
    container.appendChild(text);
  
    let secondaryText;
    if (item.secondaryText) {
      secondaryText = document.createElement('span');
      secondaryText.textContent = item.secondaryText;
      secondaryText.classList.add('secondary');
      container.appendChild(secondaryText);
    }
  
    return container;
  };
  
  ContextMenu.prototype._reposition = function (position) {
    if (position) {
      this._originalPosition = position;
    }
  
    if (!this._dropdown) {
      this._position = this._calcPosition(this._originalPosition || this._position);
    }
  
    if (this._position.height) {
      this._element.style.height = `${this._position.height}px`;
      this._element.style.overflowY = 'scroll';
    }
  
    this._element.style.top = `${this._position.y}px`;
    this._element.style.left = `${this._position.x}px`;
    this._element.style.opacity = '1';
  };
  
  ContextMenu.prototype._setCurrentItem = function (position, increment) {
    let itemId;
    let el;
    let item;
  
    let finalPosition = null;
  
    if (position !== null) {
      let currentPosition = Math.max(Math.min(parseInt(position, 10), this._rawItems.length - 1), 0);
  
      // try to find an item to select that can be selectable
      if (increment) {
        for (;;) {
          item = this._items[this._rawItems[currentPosition].id];
          el = document.getElementById(this._localIdToDom(this._rawItems[currentPosition].id));
  
          if (!item.separator && !item.disabled && !el.classList.contains('filtered')) {
            finalPosition = currentPosition;
            break;
          }
  
          currentPosition += increment;
  
          if (currentPosition >= this._rawItems.length || currentPosition < 0) {
            break;
          }
        }
      } else {
        finalPosition = currentPosition;
      }
  
      if (finalPosition === null || finalPosition === this._currentItem) {
        return;
      }
  
      itemId = this._rawItems[finalPosition].id;
      el = document.getElementById(this._localIdToDom(itemId));
      el.classList.add('hover');
    }
  
    if (this._currentItem !== null) {
      itemId = this._rawItems[this._currentItem].id;
      el = document.getElementById(this._localIdToDom(itemId));
      el.classList.remove('hover');
    }
  
    this._currentItem = finalPosition;
  };
  
  ContextMenu.prototype._getAvailableId = function () {
    let id = this._rawItems.length;
  
    while (this._items[id]) {
      id++;
    }
    return id;
  };
  
  ContextMenu.prototype._handleMouseup = function (evt) {
    let id = evt.target.getAttribute('id');
    if (!id) {
      return;
    }
  
    id = this._domIdToLocal(id);
  
    if (this._items[id] && this._items[id].fn && !this._items[id].disabled) {
      // Activate this menu to make sure we trigger the necessary events. This
      // includes making sure that the 'active-change' event is triggered even
      // when using touch (there is nothing to touch to trigger the submenu from
      // being activated, except actually tapping a menu item).
      if (!this._active) {
        this.setActive();
        if (this._parent) {
          this.setInactive();
        }
      }
  
      this._items[id].fn.call(this, id);
      this.emit('click', { id: id });
    }
  };
  
  ContextMenu.prototype._createSubMenu = function (idArg, items, filter) {
    const id = this._id !== null ? `${this._id}-${idArg}` : idArg;
  
    return new ContextMenu({
      filter: filter,
      items: items,
      container: this._container,
      parent: this,
      id: id
    });
  };
  
  ContextMenu.prototype._hideSubMenus = function () {
    for (const i in this._items) {
      if (this._items[i].submenu) {
        this._items[i].submenu.hide();
      }
    }
  };
  
  ContextMenu.prototype._scrollToItem = function (id) {
    const el = document.getElementById(this._localIdToDom(id));
  
    const position = el.offsetTop - el.parentNode.scrollTop;
  
    const minPosition = 0;
    const maxPosition = el.parentNode.offsetHeight - el.offsetHeight;
  
    if (position > maxPosition) {
      el.parentNode.scrollTop = el.offsetTop - maxPosition;
    }
  
    if (position < minPosition) {
      el.parentNode.scrollTop = el.offsetTop - minPosition;
    }
  };
  
  ContextMenu.prototype._handleHover = function (evt, id) {
    this._potentialHovers = [];
    this._hideSubMenus();
  
    if (!this._items[id]) {
      return;
    }
  
    if (this._items[id].submenu && !this._items[id].disabled) {
      const submenu = this._items[id].submenu;
      this._previousItem = id;
  
      const scrollOffset = evt.target.parentNode.scrollTop;
  
      const y = evt.target.offsetTop - scrollOffset + this._position.y - 5;
      const x = this._position.x + this._element.offsetWidth;
  
      submenu.show({ x: x, y: y, offsetX: this._element.offsetWidth });
    }
  
    // Activate the current menu
    if (!this._active) {
      this.setActive();
      if (this._parent) {
        this._parent.setInactive();
      }
    }
  };
  
  ContextMenu.prototype.setActive = function () {
    this._active = true;
    if (this._filter) {
      this.enableFilter();
    }
  
    const _this = this;
    let rootMenu = _this;
    while (rootMenu._parent) {
      rootMenu = rootMenu._parent;
    }
  
    rootMenu.emit('active-change', {
      menuId: this._id
    });
  };
  
  ContextMenu.prototype.setInactive = function () {
    this._active = false;
  };
  
  ContextMenu.prototype._handleKeyDown = function (evt) {
    if (evt.keyCode === KEY_ESCAPE) {
      this.hide();
    }
  };
  
  ContextMenu.prototype._handleKeyNavigation = function (evt) {
    if (!this._active) {
      return false;
    }
  
    if (evt.keyCode === KEY_DOWN || evt.keyCode === KEY_UP) {
      this._mode = 'keyboard';
      this._lastKeyEventTimestamp = Date.now();
    }
  
    switch (evt.keyCode) {
      case KEY_UP:
        return this._handleKeyNavigationUp(evt);
      case KEY_DOWN:
        return this._handleKeyNavigationDown(evt);
      case KEY_LEFT:
        return this._handleKeyNavigationLeft(evt);
      case KEY_RIGHT:
        return this._handleKeyNavigationRight(evt);
      case KEY_ENTER:
        return this._handleKeyNavigationEnter(evt);
      default:
        return undefined;
    }
  };
  
  ContextMenu.prototype._handleKeyNavigationUp = function (evt) {
    evt.preventDefault();
  
    let position;
    if (this._currentItem === null) {
      position = this._rawItems.length;
    } else {
      position = parseInt(this._currentItem, 10);
    }
  
    const rawItem = this._rawItems[this._currentItem];
    const item = rawItem && this._items[rawItem.id];
    this._setCurrentItem(position - 1, -1);
  
    // If we have moved, hide the previous' items submenu
    const newRawItem = this._rawItems[this._currentItem];
    if (rawItem !== newRawItem && item && item.submenu) {
      item.submenu.hide();
    }
  
    if (this._currentItem !== null) {
      this._scrollToItem(this._rawItems[this._currentItem].id);
    }
  };
  
  ContextMenu.prototype._handleKeyNavigationDown = function (evt) {
    evt.preventDefault();
  
    let position;
    if (this._currentItem === null) {
      position = -1;
    } else {
      position = parseInt(this._currentItem, 10);
    }
  
    const rawItem = this._rawItems[this._currentItem];
    const item = rawItem && this._items[rawItem.id];
    this._setCurrentItem(position + 1, 1);
  
    // If we have moved, hide the previous' items submenu
    const newRawItem = this._rawItems[this._currentItem];
    if (rawItem !== newRawItem && item && item.submenu) {
      item.submenu.hide();
    }
  
    if (this._currentItem !== null) {
      this._scrollToItem(this._rawItems[this._currentItem].id);
    }
  };
  
  ContextMenu.prototype._handleKeyNavigationLeft = function (evt) {
    evt.preventDefault();
  
    if (this._parent) {
      this.hide();
      this._parent._active = true;
      if (this._parent._filter) {
        this._parent._filterElement.focus();
      }
    }
  };
  
  ContextMenu.prototype._handleKeyNavigationRight = function (evt) {
    if (this._currentItem === null) {
      return;
    }
  
    const item = this._items[this._rawItems[this._currentItem].id];
  
    if (item.submenu) {
      const element = document.getElementById(this._localIdToDom(this._rawItems[this._currentItem].id));
  
      if (element.classList.contains('filtered')) {
        return;
      }
  
      const scrollOffset = element.parentNode.scrollTop;
  
      const y = element.offsetTop - scrollOffset + this._position.y - 5;
      const x = this._position.x + this._element.offsetWidth;
      item.submenu.show({ x: x, y: y, offsetX: this._element.offsetWidth });
      item.submenu._setCurrentItem(0, 1);
  
      this._active = false;
      item.submenu.setActive();
    }
  
    evt.preventDefault();
  };
  
  ContextMenu.prototype._handleKeyNavigationEnter = function () {
    if (this._currentItem === null) {
      return;
    }
  
    const id = this._rawItems[this._currentItem].id;
    const item = this._items[id];
  
    const elem = document.getElementById(this._localIdToDom(id));
  
    if (item && item.fn && !item.disabled && !elem.classList.contains('filtered')) {
      item.fn.call(this, id);
    }
  
    elem.classList.add('active');
  
    setTimeout(function () {
      elem.classList.remove('active');
    }, 80);
  };
  
  ContextMenu.prototype._handleCloseFilter = function (evt) {
    // check for esc key pressed
    if (evt.keyCode === KEY_ESCAPE) {
      const filterId = `${this._localIdToDom()}-filter`;
      if (document.getElementById(filterId).value) {
        document.getElementById(filterId).value = '';
        evt.stopPropagation();
        this._filterFn();
      }
    }
  };
  
  ContextMenu.prototype._handleFilter = function (evt) {
    // check for esc key pressed
    if (evt.keyCode === KEY_ESCAPE) {
      document.getElementById(`${this._localIdToDom()}-filter`).value = '';
    }
  
    this._filterFn();
  };
  
  ContextMenu.prototype._filterFn = function () {
    const query = document.getElementById(`${this._localIdToDom()}-filter`).value;
  
    /* eslint-disable guard-for-in */
    for (const i in this._items) {
      const el = document.getElementById(this._localIdToDom(i));
      const text = this._items[i].text;
  
      const pos = text.toLowerCase().indexOf(query.toLowerCase());
      if (pos === -1) {
        el.classList.add('filtered');
  
        if (this._items[i].submenu) {
          this._items[i].submenu.hide();
        }
      } else {
        if (query) {
          const html = [this._escapeHtml(text.substring(0, pos)), '<span class="highlight">', this._escapeHtml(text.substring(pos, pos + query.length)), '</span>', this._escapeHtml(text.substring(pos + query.length))].join('');
  
          el.querySelector('.text').innerHTML = html;
        } else {
          el.querySelector('.text').innerHTML = this._escapeHtml(this._items[i].text);
        }
        el.classList.remove('filtered');
      }
    }
    /* eslint-enable guard-for-in */
  
    this._reposition();
  };
  
  ContextMenu.prototype._getPositionById = function (id) {
    for (const i in this._rawItems) {
      if (String(this._rawItems[i].id) === id) {
        return i;
      }
    }
    return null;
  };
  
  ContextMenu.prototype._calcPosition = function (positionArg) {
    // clone the position object
    const position = {
      x: positionArg.x,
      y: positionArg.y,
      offsetX: positionArg.offsetX
    };
  
    this._element.style.height = 'auto';
  
    const elementBounding = this._element.getBoundingClientRect();
    const elementH = elementBounding.height;
    const elementW = this._elementWidth === undefined ? elementBounding.width : this._elementWidth;
    const containerH = this._container.offsetHeight;
    const containerW = this._container.offsetWidth;
  
    const isSubMenu = !!this._parent;
    const offset = position.offsetX || 0;
  
    let availableSpaceRight = containerW - config.minDistanceFromLeftRight - position.x;
    let availableSpaceLeft = position.x - offset - config.minDistanceFromLeftRight;
    if (!isSubMenu) {
      availableSpaceRight -= config.xOffset;
      availableSpaceLeft -= config.xOffset;
    }
  
    this._element.style.width = `${elementW}px`;
    this._elementWidth = elementW;
  
    // Deal with horizontal position.
    if (elementW < availableSpaceRight) {
      // default scenario, menu opens in the right
      if (!isSubMenu) {
        position.x += config.xOffset;
      }
    } else if (elementW < availableSpaceLeft) {
      // the menu doesn't fit in the right
      position.x = position.x - elementW - offset;
      if (!isSubMenu) {
        position.x -= config.xOffset;
      }
    } else {
      // the menu doesn't fit on either side, so shrink it
      if (availableSpaceRight >= availableSpaceLeft) {
        this._element.style.width = `${availableSpaceRight}px`;
      } else {
        this._element.style.width = `${availableSpaceLeft}px`;
        position.x = position.x - availableSpaceLeft - offset;
        if (!isSubMenu) {
          position.x -= config.xOffset;
        }
      }
    }
  
    // Deal with vertical position and height.
    const yEdgeMin = config.minDistanceFromTop;
    const yEdgeMax = containerH - config.minDistanceFromBottom;
  
    // Move it up if the bottom edge is below the max.
    let yEdge = position.y + elementH;
    let distanceToBottomEdge = yEdgeMax - yEdge;
    if (distanceToBottomEdge < 0) {
      position.y += distanceToBottomEdge;
    }
  
    // Move it down if the top edge is above the min.
    const distanceToTop = position.y - yEdgeMin;
    if (distanceToTop < 0) {
      position.y -= distanceToTop;
    }
  
    // If the bottom edge is too low now, make the height fit and add overflow.
    yEdge = position.y + elementH;
    distanceToBottomEdge = yEdgeMax - yEdge;
  
    if (distanceToBottomEdge < 0) {
      position.height = elementH + distanceToBottomEdge;
    }
  
    return position;
  };
  
  ContextMenu.prototype._cancelPotentialHover = function () {
    if (this._potentialHovers && this._potentialHovers.length) {
      if (this._previousItem) {
        this._setCurrentItem(this._getPositionById(this._previousItem));
      }
      this._potentialHovers.forEach(window.clearTimeout);
    }
  };
  
  ContextMenu.prototype._handlePotentialHover = function (evt) {
    // We want to ignore mouse events that come very quickly after a key event,
    // since navigating with the keyboard might cause the list to scroll and that
    // will trigger mouse events. Using mouse within a short amount of time after
    // using the keyboard is very unusual, so this should not be a problem.
    if (Date.now() - this._lastKeyEventTimestamp >= 500) {
      this._mode = 'mouse';
    }
  
    if (this._mode === 'keyboard') {
      return;
    }
  
    let id = evt.target.getAttribute('id');
    if (!id) {
      return;
    }
    id = this._domIdToLocal(id);
  
    if (!id) {
      return;
    }
  
    this._setCurrentItem(this._getPositionById(id));
  
    if (this._parent) {
      this._parent._cancelPotentialHover();
    }
  
    if (!this._potentialHovers) {
      this._potentialHovers = [];
    }
  
    this._potentialHovers.push(setTimeout(this._handleHover.bind(this, evt, id), 100));
  };
  
  ContextMenu.prototype._handleMouseDown = function (evt) {
    if (evt.target === this._container) {
      evt.preventDefault();
      this.hide();
    }
  };
  
  ContextMenu.prototype._escapeHtml = function (str) {
    const div = document.createElement('div');
    div.appendChild(document.createTextNode(str));
    return div.innerHTML;
  };
  
  },{"spotify-eventemitter":548}],57:[function(require,module,exports){
  'use strict';
  
  const parser = require('../live/util/parser');
  
  const sortMap = {
    track: {
      _replace: '',
      artists: {
        _replace: 'artist'
      },
      disc: {
        _replace: 'discNumber'
      },
      number: {
        _replace: 'trackNumber'
      },
      duration: {
        _replace: 'length'
      }
    },
    timeSinceAdded: {
      _reverse: true,
      _replace: 'addTime'
    },
    addedBy: {
      _replace: '',
      name: {
        _replace: 'addedByDisplayName'
      }
    }
  };
  
  function reverse(direction) {
    return direction === 'ASC' ? 'DESC' : 'ASC';
  }
  
  function walk(query, mapping, optDirection, incomingCollector) {
    const results = [];
    const collector = incomingCollector && incomingCollector.slice() || [];
  
    query.mask.forEach(item => {
      const itemCollector = collector.slice();
  
      if (item.key) {
        const hasReplace = mapping && mapping[item.key] && mapping[item.key]._replace !== null && mapping[item.key]._replace !== undefined;
  
        let value = hasReplace ? mapping[item.key]._replace : item.key;
        if (value !== '') {
          // Only add the direction if there's not another sub object
          if (!item.mask) {
            const doReverse = !!(mapping && mapping[item.key] && mapping[item.key]._reverse);
            if (optDirection) {
              const newOptDirection = optDirection.toUpperCase();
              const direction = doReverse ? reverse(newOptDirection) : newOptDirection;
              value += ` ${direction}`;
            }
          }
          itemCollector.push(value);
        }
      }
  
      if (item.mask) {
        const subLookUp = walk(item, mapping[item.key], optDirection, itemCollector);
        if (subLookUp.length) {
          results.push(subLookUp);
        }
      } else {
        if (itemCollector.length) {
          results.push(itemCollector.join('.'));
        }
      }
    });
    return results;
  }
  
  function getParams(query, optDirection, optMap) {
    const parsed = parser(query);
    if (!query) return null;
  
    const map = optMap || sortMap;
    const result = walk(parsed, map, optDirection);
  
    return result.join(',');
  }
  
  module.exports = getParams;
  
  },{"../live/util/parser":175}],58:[function(require,module,exports){
  'use strict';
  
  let initialized = false;
  let debug;
  
  /**
   * This module is needed so that the debug module can be enabled before it's used
   * via the 'debugEnabled' product state flag or the 'data-debug' iframe attribute
   * assigned by zlink. If debug is 'import'ed instead of required it gets evaluated
   * and potentially used by other 'import'ed modules before getting enabled.
   *
   * This works exactly the same as the debug module except we dynamically
   * require 'debug' only the first time a debug namespace is created.
   * @param {String} namespace
   * @return {Function}
   */
  const spDebug = function spDebug(namespace) {
    if (!initialized) {
      debug = require('debug');
      spDebug.enable = debug.enable.bind(debug);
      spDebug.disable = debug.disable.bind(debug);
      /**
       * This attribute is updated by zlink when it loads an app's iframe
       */
      if (typeof window !== 'undefined' && window.frameElement) {
        const enabled = window.frameElement.getAttribute('data-debug');
        if (enabled) debug.enable(enabled);
      }
  
      const productState = typeof __spotify !== 'undefined' && __spotify.product_state ? __spotify.product_state : {};
      const debugEnable = productState.debugEnable || productState.debugenable;
      if (debugEnable) {
        debug.enable(debugEnable);
      }
  
      if (typeof __spotify !== 'undefined' && __spotify.product_state && __spotify.product_state['ta-environment'] === '1') {
        /**
         * Normal CEF object-formatting results in missing data in Selenium logs, so we use
         * JSON.stringify objects to force a string representation.
         * Also, colors are not supported in Selenium-exported logs, and attempted use will result in junk characters.
         */
        debug.useColors = () => false;
        debug.formatters.o = obj => {
          try {
            return JSON.stringify(obj);
          } catch (e) {
            console.error('Unable to stringify object', e);
            return '';
          }
        };
      }
      initialized = true;
    }
  
    return debug(namespace);
  };
  
  module.exports = spDebug;
  
  },{"debug":330}],59:[function(require,module,exports){
  (function (global){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.addDebouncedScrollListener = addDebouncedScrollListener;
  exports.removeDebouncedScrollListener = removeDebouncedScrollListener;
  exports.requestFrame = requestFrame;
  exports.cancelFrame = cancelFrame;
  exports._reset = _reset;
  let scrollNodeToListenerInfoList = new global.Map();
  let idToScrollNode = {};
  let frameListenerInfoList = [];
  let hasQueuedScroll = false;
  let lastScrollEvent = null;
  let lastFrameRequestId = 0;
  let globalIdCounter = 0;
  
  function onScroll(event) {
    lastScrollEvent = event;
  
    if (!hasQueuedScroll) {
      hasQueuedScroll = true;
  
      const listenerInfoList = scrollNodeToListenerInfoList.get(event.target);
      if (listenerInfoList) {
        let list;
        requestFrame(() => {
          hasQueuedScroll = false;
  
          // Make a copy so that a potential handler that removes a handler
          // doesn't disturb the loop. This same copy will also be used in the
          // write handler, to prevent disturbing the write loop when handlers are
          // removed in the read loop.
          list = listenerInfoList.slice();
  
          callReadHandlers(list, lastScrollEvent);
        }, () => {
          callWriteHandlers(list, lastScrollEvent);
        });
      }
    }
  }
  
  function onFrame(timestamp) {
    lastFrameRequestId = 0;
  
    // Remove all current listeners as they are only valid for one frame. Just
    // like the regular requestAnimationFrame the handler needs to request a new
    // frame if it needs one.
    const listenerInfoList = frameListenerInfoList;
    frameListenerInfoList = [];
  
    callReadHandlers(listenerInfoList, timestamp);
    callWriteHandlers(listenerInfoList, timestamp);
  }
  
  function callReadHandlers(listenerInfoList, handlerArgument) {
    listenerInfoList.forEach(listenerInfo => {
      if (listenerInfo.readFunction) {
        listenerInfo.readFunction(handlerArgument);
      }
    });
  }
  
  function callWriteHandlers(listenerInfoList, handlerArgument) {
    listenerInfoList.forEach(listenerInfo => {
      if (typeof listenerInfo.writeFunction === 'function') {
        listenerInfo.writeFunction(handlerArgument);
      }
    });
  }
  
  function addDebouncedScrollListener(scrollNode, readFunction, writeFunction) {
    if (readFunction !== null && typeof readFunction !== 'function') {
      throw new TypeError('The callback provided to addDebouncedScrollListener as parameter 2 is ' + 'not a function.');
    }
    if (writeFunction !== undefined && typeof writeFunction !== 'function') {
      throw new TypeError('The callback provided to addDebouncedScrollListener as parameter 3 is ' + 'not a function.');
    }
  
    if (!scrollNodeToListenerInfoList.has(scrollNode)) {
      scrollNode.addEventListener('scroll', onScroll);
      scrollNodeToListenerInfoList.set(scrollNode, []);
    }
  
    const id = ++globalIdCounter;
    const listenerInfo = {
      id,
      readFunction,
      writeFunction
    };
  
    idToScrollNode[id] = scrollNode;
    scrollNodeToListenerInfoList.get(scrollNode).push(listenerInfo);
  
    return id;
  }
  
  function removeDebouncedScrollListener(id) {
    const scrollNode = idToScrollNode[id];
    if (scrollNode) {
      delete idToScrollNode[id];
  
      const listenerInfoList = scrollNodeToListenerInfoList.get(scrollNode);
      if (listenerInfoList) {
        for (let i = 0; i < listenerInfoList.length; i++) {
          if (listenerInfoList[i].id === id) {
            listenerInfoList.splice(i, 1);
            break;
          }
        }
      }
  
      if (listenerInfoList.length === 0) {
        scrollNode.removeEventListener('scroll', onScroll);
        scrollNodeToListenerInfoList.delete(scrollNode);
      }
    }
  }
  
  function requestFrame(readFunction, writeFunction) {
    if (readFunction !== null && typeof readFunction !== 'function') {
      throw new TypeError('The callback provided to requestFrame as parameter 1 is not a function.');
    }
    if (writeFunction !== undefined && typeof writeFunction !== 'function') {
      throw new TypeError('The callback provided to requestFrame as parameter 2 is not a function.');
    }
  
    if (lastFrameRequestId === 0) {
      lastFrameRequestId = window.requestAnimationFrame(onFrame);
    }
  
    const id = ++globalIdCounter;
    const listenerInfo = {
      id,
      readFunction,
      writeFunction
    };
  
    frameListenerInfoList.push(listenerInfo);
  
    return id;
  }
  
  function cancelFrame(id) {
    for (let i = 0; i < frameListenerInfoList.length; i++) {
      if (frameListenerInfoList[i].id === id) {
        frameListenerInfoList.splice(i, 1);
        break;
      }
    }
  
    if (lastFrameRequestId !== 0 && frameListenerInfoList.length === 0) {
      window.cancelAnimationFrame(lastFrameRequestId);
      lastFrameRequestId = 0;
    }
  }
  
  // Use only for testing, to be able to reset the state for each test
  function _reset() {
    scrollNodeToListenerInfoList = new global.Map();
    idToScrollNode = {};
    frameListenerInfoList = [];
    hasQueuedScroll = false;
    lastScrollEvent = null;
    lastFrameRequestId = 0;
    globalIdCounter = 0;
  }
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{}],60:[function(require,module,exports){
  module.exports={
    "Album": "Album",
    "AndMore": "a {0} další(ch)",
    "Artist": "Umělec",
    "By": "od",
    "ByLabel": "Od",
    "Create Similar Playlist": "Vytvořit podobný playlist",
    "DateAdded": "Datum přidání",
    "Duration": "Délka",
    "Recommended Songs": "Doporučené skladby",
    "Filter": "Filtr",
    "Follow": "Sledovat",
    "Follower": "Sledující",
    "FollowersLabel": {
      "one": "Sledující",
      "other": "sledujících"
    },
    "FollowersCount": {
      "one": "{0} sledující",
      "other": "{0} sledujících"
    },
    "Following": "Sleduješ",
    "FollowingLabel": "Sleduješ",
    "ListenersLabel": "Posluchačů měsíčně",
    "FollowsYou": "Sleduje tě",
    "HoldToPreview": "Pro náhled klikni a podrž",
    "ListenCount": {
      "one": "1 přehrání",
      "other": "{0} přehrání"
    },
    "ListenReactionMulti": "{0} posluchačů ve tvé síti",
    "ListenReactionSingle": "{0} uživatelů tohle poslouchá",
    "LocalFile": "Místní soubory",
    "More": "dalších",
    "MoreSongsLoad": "Během přehrávání se načtou další skladby.",
    "Pause": "Pozastavit",
    "Play": "Přehrát",
    "Playlist": "Playlist",
    "PressEnterToPlay": "Přehrávání spustíš stisknutím klávesy Enter",
    "Popularity": "Popularita",
    "Progress": "Postup",
    "Remove": "Odstranit",
    "RemoveYourMusic": "Odstranit ze sbírky Tvoje knihovna",
    "Save": "Uložit",
    "SaveYourMusic": "Uložit do sbírky Tvoje knihovna",
    "Saved": "Uloženo",
    "StartRadio": "Spustit rádio",
    "Time": "Čas",
    "Title": "Název",
    "Toplist": "Top skladby",
    "Track": "Skladba",
    "Unfollow": "Přestat sledovat",
    "User": "Uživatel",
    "ViewAll": "Zobrazit vše",
    "HoursShort": "{0} h",
    "MinutesShort": "{0} min",
    "SecondsShort": "{0} s",
    "ErrorTitle": "Při zobrazování této stránky došlo k problému!",
    "ErrorMessage": "Tato stránka buď neexistuje, nebo došlo k chybě.",
    "OfflineTitle": "Tato stránka není dostupná offline!",
    "OfflineMessage": "Pro načtení přejdi prosím online.",
    "MadeFor": "Speciálně pro uživatele {0}",
    "heart_label": "Líbí se mi",
    "undo_heart_label": "Líbilo se mi",
    "ban_label": "Odstranit",
    "format_list.ban_label": "Nelíbí se mi",
    "format_list.undo_ban_label": "Tohle se ti nelíbí",
    "VariousArtistsCapitalized": "Různí umělci"
  }
  },{}],61:[function(require,module,exports){
  module.exports={
    "Album": "Album",
    "AndMore": "und {0} weitere",
    "Artist": "Künstler",
    "By": "von",
    "ByLabel": "Von",
    "Create Similar Playlist": "Ähnliche Playlist erstellen",
    "DateAdded": "Datum hinzugefügt",
    "Duration": "Dauer",
    "Recommended Songs": "Empfohlene Songs",
    "Filter": "Filter",
    "Follow": "Folgen",
    "Follower": "Follower",
    "FollowersLabel": {
      "one": "Follower",
      "other": "Follower"
    },
    "FollowersCount": {
      "one": "{0} Follower",
      "other": "{0} Follower"
    },
    "Following": "Folge ich",
    "FollowingLabel": "Folge ich",
    "ListenersLabel": "Monatliche Hörer",
    "FollowsYou": "Folgt dir",
    "HoldToPreview": "Zum Reinhören gedrückt halten",
    "ListenCount": {
      "one": "1 Wiedergabe",
      "other": "{0} Mal abgespielt"
    },
    "ListenReactionMulti": "{0} Hörer in Deinem Netzwerk",
    "ListenReactionSingle": "{0} hört sich das an",
    "LocalFile": "Lokale Datei",
    "More": "Mehr",
    "MoreSongsLoad": "Während du Musik hörst, werden weitere Songs geladen.",
    "Pause": "Pause",
    "Play": "Play",
    "Playlist": "Playlist",
    "PressEnterToPlay": "Drück die Eingabetaste zum Abspielen",
    "Popularity": "Beliebtheit",
    "Progress": "Fortschritt",
    "Remove": "Entfernen",
    "RemoveYourMusic": "Aus Bibliothek entfernen",
    "Save": "Speichern",
    "SaveYourMusic": "In Bibliothek speichern",
    "Saved": "Gespeichert",
    "StartRadio": "Radio starten",
    "Time": "Dauer",
    "Title": "Titel",
    "Toplist": "Top-Songs",
    "Track": "Song",
    "Unfollow": "Nicht mehr folgen",
    "User": "Benutzer",
    "ViewAll": "Mehr Darstellung",
    "HoursShort": "{0} Std.",
    "MinutesShort": "{0} Min.",
    "SecondsShort": "{0} Sek.",
    "ErrorTitle": "Bei der Anzeige dieser Seite ist ein Fehler aufgetreten.",
    "ErrorMessage": "Diese Seite ist entweder nicht vorhanden oder es ist ein Fehler aufgetreten.",
    "OfflineTitle": "Diese Seite ist offline nicht verfügbar.",
    "OfflineMessage": "Geh zum Laden online.",
    "MadeFor": "Für {0}",
    "heart_label": "Gefällt mir",
    "undo_heart_label": "Gefällt mir",
    "ban_label": "Entfernen",
    "format_list.ban_label": "Gefällt mir nicht",
    "format_list.undo_ban_label": "Das gefällt dir nicht",
    "VariousArtistsCapitalized": "Diverse Künstler"
  }
  },{}],62:[function(require,module,exports){
  module.exports={
    "Album": "Άλμπουμ",
    "AndMore": "και άλλοι {0}",
    "Artist": "Καλλιτέχνης",
    "By": "από",
    "ByLabel": "Από",
    "Create Similar Playlist": "Δημιουργία παρόμοιας playlist",
    "DateAdded": "Προστέθηκε ημερομηνία",
    "Duration": "Διάρκεια",
    "Recommended Songs": "Προτεινόμενα τραγούδια",
    "Filter": "Φίλτρο",
    "Follow": "Ακολούθησε",
    "Follower": "Οπαδός",
    "FollowersLabel": {
      "one": "Οπαδός",
      "other": "Ακόλουθοι"
    },
    "FollowersCount": {
      "one": "{0} οπαδός",
      "other": "{0} οπαδοί"
    },
    "Following": "Aκολουθείται",
    "FollowingLabel": "Aκολουθείται",
    "ListenersLabel": "Μηνιαίοι ακροατές",
    "FollowsYou": "Σε ακολουθεί",
    "HoldToPreview": "Πάτησε και κράτησε πατημένο για προεπισκόπηση",
    "ListenCount": {
      "one": "1 αναπαραγωγή",
      "other": "{0} αναπαραγωγές"
    },
    "ListenReactionMulti": "{0} ακροατές στο δίκτυό σου",
    "ListenReactionSingle": "Ο χρήστης {0} ακούει αυτό",
    "LocalFile": "Τοπικό αρχείο",
    "More": "Περισσότερα",
    "MoreSongsLoad": "Περισσότερα τραγούδια φορτώνουν ενώ ακούς",
    "Pause": "Παύση",
    "Play": "Αναπαραγωγή",
    "Playlist": "Λίστα",
    "PressEnterToPlay": "Πάτησε Enter για αναπαραγωγή",
    "Popularity": "Δημοφιλία",
    "Progress": "Πρόοδος",
    "Remove": "Αφαίρεση",
    "RemoveYourMusic": "Αφαίρεση από τη Βιβλιοθήκη",
    "Save": "Αποθήκευση",
    "SaveYourMusic": "Αποθήκευση στη Βιβλιοθήκη",
    "Saved": "Αποθηκεύτηκε",
    "StartRadio": "Έναρξη ράδιο",
    "Time": "Διάρκεια",
    "Title": "Τίτλος",
    "Toplist": "Κορυφαία τραγούδια",
    "Track": "Τραγούδι",
    "Unfollow": "Άρση ακολούθησης",
    "User": "Χρήστης",
    "ViewAll": "Δες τα όλα",
    "HoursShort": "{0} ώρα",
    "MinutesShort": "{0} λεπ.",
    "SecondsShort": "{0} δευτ.",
    "ErrorTitle": "Παρουσιάστηκε πρόβλημα κατά την προβολή αυτής της σελίδας!",
    "ErrorMessage": "Αυτή η σελίδα δεν υπάρχει ή προέκυψε κάποιο σφάλμα.",
    "OfflineTitle": "Αυτή η σελίδα δεν είναι διαθέσιμη εκτός σύνδεσης!",
    "OfflineMessage": "Συνδέσου στο διαδίκτυο για να πραγματοποιηθεί φόρτωση.",
    "MadeFor": "Ειδικά για: {0}",
    "heart_label": "Μου αρέσει",
    "undo_heart_label": "Αρέσει σε",
    "ban_label": "Αφαίρεση",
    "format_list.ban_label": "Δεν μου αρέσει",
    "format_list.undo_ban_label": "Δεν σου αρέσει αυτό",
    "VariousArtistsCapitalized": "Διάφοροι καλλιτέχνες"
  }
  },{}],63:[function(require,module,exports){
  module.exports={
    "Album": "Album",
    "AndMore": "and {0} more",
    "Artist": "Artist",
    "By": "by",
    "ByLabel": "By",
    "Create Similar Playlist": "Create Similar Playlist",
    "DateAdded": "Date Added",
    "Duration": "Duration",
    "Recommended Songs": "Recommended Songs",
    "Filter": "Filter",
    "Follow": "Follow",
    "Follower": "Follower",
    "FollowersLabel": {
      "one": "Follower",
      "other": "Followers"
    },
    "FollowersCount": {
      "one": "{0} follower",
      "other": "{0} followers"
    },
    "Following": "Following",
    "FollowingLabel": "Following",
    "ListenersLabel": "Monthly Listeners",
    "FollowsYou": "Follows You",
    "HoldToPreview": "Click and Hold to Preview",
    "ListenCount": {
      "one": "1 play",
      "other": "{0} plays"
    },
    "ListenReactionMulti": "{0} listeners in your network",
    "ListenReactionSingle": "{0} listens to this",
    "LocalFile": "Local File",
    "More": "More",
    "MoreSongsLoad": "More songs load as you listen",
    "Pause": "Pause",
    "Play": "Play",
    "Playlist": "Playlist",
    "PressEnterToPlay": "Press Enter to play",
    "Popularity": "Popularity",
    "Progress": "Progress",
    "Remove": "Remove",
    "RemoveYourMusic": "Remove from Your Library",
    "Save": "Save",
    "SaveYourMusic": "Save to Your Library",
    "Saved": "Saved",
    "StartRadio": "Start Radio",
    "Time": "Time",
    "Title": "Title",
    "Toplist": "Top songs",
    "Track": "Song",
    "Unfollow": "Unfollow",
    "User": "User",
    "ViewAll": "View All",
    "HoursShort": "{0} hr",
    "MinutesShort": "{0} min",
    "SecondsShort": "{0} sec",
    "ErrorTitle": "There was a problem displaying this page!",
    "ErrorMessage": "This page either does not exist or an error occurred.",
    "OfflineTitle": "This page is not available offline!",
    "OfflineMessage": "Please go online to load.",
    "MadeFor": "Made for {0}",
    "heart_label": "Like",
    "undo_heart_label": "Liked",
    "ban_label": "Remove",
    "format_list.ban_label": "Don't like",
    "format_list.undo_ban_label": "You don't like this",
    "VariousArtistsCapitalized": "Various artists"
  }
  },{}],64:[function(require,module,exports){
  module.exports={
    "Album": "Álbum",
    "AndMore": "y {0} más",
    "Artist": "Artista",
    "By": "por",
    "ByLabel": "De",
    "Create Similar Playlist": "Crear playlist similar",
    "DateAdded": "Fecha agregada",
    "Duration": "Duración",
    "Recommended Songs": "Canciones recomendadas",
    "Filter": "Filtrar",
    "Follow": "Seguir",
    "Follower": "Seguidor",
    "FollowersLabel": {
      "one": "Seguidor",
      "other": "Seguidores"
    },
    "FollowersCount": {
      "one": "{0} seguidor",
      "other": "{0} seguidores"
    },
    "Following": "Siguiendo",
    "FollowingLabel": "Siguiendo",
    "ListenersLabel": "Oyentes mensuales",
    "FollowsYou": "Te sigue",
    "HoldToPreview": "Haz clic y mantén pulsado para oír la muestra preliminar",
    "ListenCount": {
      "one": "1 reproducción",
      "other": "{0} reproducciones"
    },
    "ListenReactionMulti": "{0} oyentes en tu red",
    "ListenReactionSingle": "{0} escucha esto",
    "LocalFile": "Archivo local",
    "More": "Más",
    "MoreSongsLoad": "Se cargarán más canciones a medida que escuchas",
    "Pause": "Pausa",
    "Play": "Reproducir",
    "Playlist": "Lista",
    "PressEnterToPlay": "Presiona Enter para reproducir",
    "Popularity": "Popularidad",
    "Progress": "Progreso",
    "Remove": "Eliminar",
    "RemoveYourMusic": "Eliminar de Tu Biblioteca",
    "Save": "Guardar",
    "SaveYourMusic": "Guardar en Tu Biblioteca",
    "Saved": "Guardado",
    "StartRadio": "Comenzar Radio",
    "Time": "Tiempo",
    "Title": "Título",
    "Toplist": "Canciones más reproducidas",
    "Track": "Canción",
    "Unfollow": "Dejar de seguir",
    "User": "Usuario",
    "ViewAll": "Ver todos",
    "HoursShort": "{0} hr",
    "MinutesShort": "{0} min",
    "SecondsShort": "{0} seg",
    "ErrorTitle": "Hubo un problema al mostrar esta página.",
    "ErrorMessage": "Puede ser que esta página no exista o que haya ocurrido un error.",
    "OfflineTitle": "Esta página no se encuentra disponible sin conexión.",
    "OfflineMessage": "Conéctate a la red para cargar.",
    "MadeFor": "Hecho para {0}",
    "heart_label": "Me gusta",
    "undo_heart_label": "Te gusta esto",
    "ban_label": "Eliminar",
    "format_list.ban_label": "No me gusta",
    "format_list.undo_ban_label": "No te gusta esto",
    "VariousArtistsCapitalized": "Varios artistas"
  }
  },{}],65:[function(require,module,exports){
  module.exports={
    "Album": "Álbum",
    "AndMore": "y {0} más",
    "Artist": "Artista",
    "By": "por",
    "ByLabel": "De",
    "Create Similar Playlist": "Crear playlist similar",
    "DateAdded": "Fecha en la que se añadió",
    "Duration": "Duración",
    "Recommended Songs": "Canciones recomendadas",
    "Filter": "Filtrar",
    "Follow": "Seguir",
    "Follower": "Seguidor",
    "FollowersLabel": {
      "one": "Seguidor",
      "other": "Seguidores"
    },
    "FollowersCount": {
      "one": "{0} seguidor",
      "other": "{0} seguidores"
    },
    "Following": "Siguiendo",
    "FollowingLabel": "Siguiendo",
    "ListenersLabel": "Oyentes mensuales",
    "FollowsYou": "Te sigue",
    "HoldToPreview": "Pulsa y mantén pulsado para oír la muestra preliminar",
    "ListenCount": {
      "one": "1 reproducción",
      "other": "{0} reproducciones"
    },
    "ListenReactionMulti": "{0} oyentes en tu red",
    "ListenReactionSingle": "{0} escucha esto",
    "LocalFile": "Archivo local",
    "More": "Más",
    "MoreSongsLoad": "Se cargarán más canciones mientras escuchas",
    "Pause": "Pausar",
    "Play": "Reproducir",
    "Playlist": "Lista",
    "PressEnterToPlay": "Pulsa Intro para reproducir",
    "Popularity": "Popularidad",
    "Progress": "Completado",
    "Remove": "Retirar",
    "RemoveYourMusic": "Eliminar de Tu biblioteca",
    "Save": "Guardar",
    "SaveYourMusic": "Guardar en Tu biblioteca",
    "Saved": "Guardada",
    "StartRadio": "Comenzar radio",
    "Time": "Tiempo",
    "Title": "Título",
    "Toplist": "Canciones más escuchadas",
    "Track": "Canción",
    "Unfollow": "Dejar de seguir",
    "User": "Usuario",
    "ViewAll": "Ver todos",
    "HoursShort": "{0} hr",
    "MinutesShort": "{0} min",
    "SecondsShort": "{0} seg",
    "ErrorTitle": "Se ha producido un problema al mostrar esta página.",
    "ErrorMessage": "Esta página no existe o se ha producido un error.",
    "OfflineTitle": "Esta página no está disponible sin conexión.",
    "OfflineMessage": "Por favor, conéctate para cargar.",
    "MadeFor": "Hecha para {0}",
    "heart_label": "Me gusta",
    "undo_heart_label": "Te gusta",
    "ban_label": "Retirar",
    "format_list.ban_label": "No me gusta",
    "format_list.undo_ban_label": "No te gusta esto",
    "VariousArtistsCapitalized": "Varios artistas"
  }
  },{}],66:[function(require,module,exports){
  module.exports={
    "Album": "Albumi",
    "AndMore": "ja {0} muuta",
    "Artist": "Artisti",
    "By": "tekijältä",
    "ByLabel": "tekijältä",
    "Create Similar Playlist": "Luo samankaltainen soittolista",
    "DateAdded": "Lisäyspäivä",
    "Duration": "Kesto",
    "Recommended Songs": "Suositellut kappaleet",
    "Filter": "Suodatin",
    "Follow": "Seuraa",
    "Follower": "Seuraaja",
    "FollowersLabel": {
      "one": "Seuraaja",
      "other": "Seuraajat"
    },
    "FollowersCount": {
      "one": "{0} seuraaja",
      "other": "{0} seuraajaa"
    },
    "Following": "Seurataan",
    "FollowingLabel": "Seurataan",
    "ListenersLabel": "Kuuntelijat kuukauden aikana",
    "FollowsYou": "Seuraa sinua",
    "HoldToPreview": "Kuuntele pätkä pitämällä tätä painettuna",
    "ListenCount": {
      "one": "1 toisto",
      "other": "{0} toistoa"
    },
    "ListenReactionMulti": "{0} kuuntelijaa verkostossasi",
    "ListenReactionSingle": "{0} kuuntelee tätä",
    "LocalFile": "Paikallinen tiedosto",
    "More": "Lisää",
    "MoreSongsLoad": "Lisää kappaleita ladataan kuuntelun aikana",
    "Pause": "Tauko",
    "Play": "Toista",
    "Playlist": "Soittolista",
    "PressEnterToPlay": "Toista painamalla Enter-näppäintä",
    "Popularity": "Suosio",
    "Progress": "Edistyminen",
    "Remove": "Poista",
    "RemoveYourMusic": "Poista omasta kirjastosta",
    "Save": "Tallenna",
    "SaveYourMusic": "Tallenna omaan kirjastoon",
    "Saved": "Tallennettu",
    "StartRadio": "Käynnistä radio",
    "Time": "Kesto",
    "Title": "Nimi",
    "Toplist": "Suosituimmat kappaleet",
    "Track": "Kappale",
    "Unfollow": "Lopeta seuraaminen",
    "User": "Käyttäjä",
    "ViewAll": "Näytä kaikki",
    "HoursShort": "{0} h",
    "MinutesShort": "{0} min",
    "SecondsShort": "{0} s",
    "ErrorTitle": "Sivun näyttämisessä ilmeni ongelma!",
    "ErrorMessage": "Sivua ei ole, tai tapahtui virhe.",
    "OfflineTitle": "Sivu ei ole saatavilla offline-tilassa!",
    "OfflineMessage": "Siirry online-tilaan, jotta voit ladata.",
    "MadeFor": "Luotu käyttäjälle {0}",
    "heart_label": "Tykkää",
    "undo_heart_label": "Tykätty",
    "ban_label": "Poista",
    "format_list.ban_label": "En tykkää",
    "format_list.undo_ban_label": "Et tykkää tästä",
    "VariousArtistsCapitalized": "Eri esittäjiä"
  }
  },{}],67:[function(require,module,exports){
  module.exports={
    "Album": "Album",
    "AndMore": "et {0} autres",
    "Artist": "Artiste",
    "By": "par",
    "ByLabel": "Par",
    "Create Similar Playlist": "Créer une liste de lecture semblable",
    "DateAdded": "Ajouté le",
    "Duration": "Durée",
    "Recommended Songs": "Chansons recommandées",
    "Filter": "Filtrer",
    "Follow": "Suivre",
    "Follower": "Abonné",
    "FollowersLabel": {
      "one": "Abonné",
      "other": "Abonné"
    },
    "FollowersCount": {
      "one": "{0} abonné",
      "other": "{0} abonnés"
    },
    "Following": "Suivis",
    "FollowingLabel": "Suivis",
    "ListenersLabel": "Auditeurs mensuels",
    "FollowsYou": "Vous suit",
    "HoldToPreview": "Cliquez longuement pour avoir un aperçu",
    "ListenCount": {
      "one": "1 lecture",
      "other": "{0} écoutes"
    },
    "ListenReactionMulti": "{0} auditeurs dans votre réseau",
    "ListenReactionSingle": "{0} écoute ceci",
    "LocalFile": "Fichier local",
    "More": "Plus",
    "MoreSongsLoad": "D'autres chansons se chargent pendant que vous écoutez",
    "Pause": "Pause",
    "Play": "Lecture",
    "Playlist": "Playlist",
    "PressEnterToPlay": "Appuyez sur Entrée pour faire la lecture",
    "Popularity": "Popularité",
    "Progress": "Progression",
    "Remove": "Supprimer",
    "RemoveYourMusic": "Supprimer de la Bibliothèque",
    "Save": "Sauvegarder",
    "SaveYourMusic": "Enregistrer dans la Bibliothèque",
    "Saved": "Sauvegardé",
    "StartRadio": "Lancer la radio",
    "Time": "Durée",
    "Title": "Titre",
    "Toplist": "Meilleures chansons",
    "Track": "Titre",
    "Unfollow": "Ne plus suivre",
    "User": "Utilisateur",
    "ViewAll": "Voir tout",
    "HoursShort": "{0} h",
    "MinutesShort": "{0} min",
    "SecondsShort": "{0} s",
    "ErrorTitle": "Un problème est survenu lors de l'affichage de cette page!",
    "ErrorMessage": "Cette page n'existe pas ou une erreur s'est produite.",
    "OfflineTitle": "Cette page n'est pas accessible hors ligne!",
    "OfflineMessage": "Veuillez accéder à Internet pour charger le contenu.",
    "MadeFor": "Créée pour {0}",
    "heart_label": "J'aime",
    "undo_heart_label": "A aimé",
    "ban_label": "Supprimer",
    "format_list.ban_label": "Je n'aime pas",
    "format_list.undo_ban_label": "Vous n’aimez pas cela",
    "VariousArtistsCapitalized": "Différents artistes"
  }
  },{}],68:[function(require,module,exports){
  module.exports={
    "Album": "Album",
    "AndMore": "et {0} autres",
    "Artist": "Artiste",
    "By": "par",
    "ByLabel": "Par",
    "Create Similar Playlist": "Créer une playlist similaire",
    "DateAdded": "Ajouté le",
    "Duration": "Durée",
    "Recommended Songs": "Titres recommandés",
    "Filter": "Filtre",
    "Follow": "S'abonner",
    "Follower": "Abonné",
    "FollowersLabel": {
      "one": "Abonné",
      "other": "Abonnés"
    },
    "FollowersCount": {
      "one": "{0} abonné",
      "other": "{0} abonnés"
    },
    "Following": "Abonné",
    "FollowingLabel": "Abonné",
    "ListenersLabel": "Nombres de personnes qui écoutent par mois",
    "FollowsYou": "Est abonné à vous",
    "HoldToPreview": "Cliquez longuement pour avoir un aperçu",
    "ListenCount": {
      "one": "1 lecture",
      "other": "{0} écoutes"
    },
    "ListenReactionMulti": "{0} auditeurs dans votre réseau",
    "ListenReactionSingle": "{0} écoute ceci.",
    "LocalFile": "Fichier local",
    "More": "Plus",
    "MoreSongsLoad": "D'autres titres se chargent pendant que vous écoutez.",
    "Pause": "Pause",
    "Play": "Lire",
    "Playlist": "Playlist",
    "PressEnterToPlay": "Appuyez sur Entrée pour écouter",
    "Popularity": "Popularité",
    "Progress": "Progression",
    "Remove": "Supprimer",
    "RemoveYourMusic": "Supprimer de Bibliothèque",
    "Save": "Sauvegarder",
    "SaveYourMusic": "Sauvegarder dans Bibliothèque",
    "Saved": "Sauvegardé",
    "StartRadio": "Ecouter la radio",
    "Time": "Durée",
    "Title": "Titre",
    "Toplist": "Top titres",
    "Track": "Titre",
    "Unfollow": "Se désabonner",
    "User": "Utilisateur",
    "ViewAll": "Présentation tout",
    "HoursShort": "{0} h",
    "MinutesShort": "{0} min",
    "SecondsShort": "{0} s",
    "ErrorTitle": "Problème d'affichage !",
    "ErrorMessage": "Cette page n'existe pas, ou une erreur s'est produite.",
    "OfflineTitle": "Cette page n'est pas disponible hors connexion !",
    "OfflineMessage": "Connectez-vous pour procéder au chargement.",
    "MadeFor": "Concocté pour {0}",
    "heart_label": "J'aime",
    "undo_heart_label": "A aimé",
    "ban_label": "Supprimer",
    "format_list.ban_label": "Je n'aime pas",
    "format_list.undo_ban_label": "Vous n'aimez pas ce contenu",
    "VariousArtistsCapitalized": "Différents artistes"
  }
  },{}],69:[function(require,module,exports){
  module.exports={
    "Album": "Album",
    "AndMore": "és még {0} felhasználó",
    "Artist": "Előadó",
    "By": "tőle:",
    "ByLabel": "Tőle:",
    "Create Similar Playlist": "Hasonló műsorlista létrehozása",
    "DateAdded": "Dátum hozzáadva",
    "Duration": "Időtartam",
    "Recommended Songs": "Ajánlott dalok",
    "Filter": "Szűrő",
    "Follow": "Követés",
    "Follower": "Követő",
    "FollowersLabel": {
      "one": "Követő",
      "other": "Követők"
    },
    "FollowersCount": {
      "one": "{0} követő",
      "other": "{0} követő"
    },
    "Following": "Követések",
    "FollowingLabel": "Követések",
    "ListenersLabel": "Hallgatók havonta",
    "FollowsYou": "Téged követ",
    "HoldToPreview": "Tartsd nyomva a belehallgatáshoz",
    "ListenCount": {
      "one": "1 lejátszás",
      "other": "{0} lejátszás"
    },
    "ListenReactionMulti": "{0} ismerősöd hallgatta meg",
    "ListenReactionSingle": "{0} már meghallgatta",
    "LocalFile": "Helyi fájl",
    "More": "Több",
    "MoreSongsLoad": "Lesz még szám, csak hallgasd tovább",
    "Pause": "Szünet",
    "Play": "Lejátszás",
    "Playlist": "Lejátszási lista",
    "PressEnterToPlay": "Lejátszás az Enterrel",
    "Popularity": "Népszerűség",
    "Progress": "Állapot",
    "Remove": "Eltávolítás",
    "RemoveYourMusic": "Törlés a Gyűjteményemből",
    "Save": "Mentés",
    "SaveYourMusic": "Mentés a Gyűjteményedbe",
    "Saved": "Mentett",
    "StartRadio": "Rádió bekapcsolása",
    "Time": "Idő",
    "Title": "Cím",
    "Toplist": "Toplista",
    "Track": "Dal",
    "Unfollow": "Nem követem",
    "User": "Felhasználó",
    "ViewAll": "Mindent mutat",
    "HoursShort": "{0} óra",
    "MinutesShort": "{0} perc",
    "SecondsShort": "{0} mp",
    "ErrorTitle": "Hiba történt az oldal megjelenítésekor.",
    "ErrorMessage": "Az oldal nem létezik, vagy hiba történt.",
    "OfflineTitle": "Ez az oldal offline módban nem érhető el.",
    "OfflineMessage": "Csatlakozz az internethez, majd indítsd el.",
    "MadeFor": "{0} felhasználónak készült",
    "heart_label": "Tetszik",
    "undo_heart_label": "Kedvelted",
    "ban_label": "Eltávolítás",
    "format_list.ban_label": "Nem tetszik",
    "format_list.undo_ban_label": "Ez nem tetszett neked",
    "VariousArtistsCapitalized": "Különböző előadók"
  }
  },{}],70:[function(require,module,exports){
  module.exports={
    "Album": "Album",
    "AndMore": "dan {0} lagi",
    "Artist": "Artis",
    "By": "menurut",
    "ByLabel": "Dari",
    "Create Similar Playlist": "Buat Playlist Serupa",
    "DateAdded": "Tanggal Dimuat",
    "Duration": "Durasi",
    "Recommended Songs": "Lagu yang Direkomendasikan",
    "Filter": "Filter",
    "Follow": "Ikuti",
    "Follower": "Pengikut",
    "FollowersLabel": {
      "one": "Pengikut",
      "other": "Pengikut"
    },
    "FollowersCount": {
      "one": "{0} pengikut",
      "other": "{0} pengikut"
    },
    "Following": "Mengikuti",
    "FollowingLabel": "Mengikuti",
    "ListenersLabel": "Pendengar Bulanan",
    "FollowsYou": "Mengikutimu",
    "HoldToPreview": "Klik dan Tahan untuk Pratinjau",
    "ListenCount": {
      "one": "1 pemutaran",
      "other": "{0} permainan"
    },
    "ListenReactionMulti": "{0} pendengar di jaringanmu",
    "ListenReactionSingle": "{0} mendengarkan ini",
    "LocalFile": "File Lokal",
    "More": "Lainnya",
    "MoreSongsLoad": "Lebih banyak lagu dimuat saat kamu mendengarkan",
    "Pause": "Jeda",
    "Play": "Play",
    "Playlist": "Playlist",
    "PressEnterToPlay": "Tekan Enter untuk memainkan",
    "Popularity": "Popularitas",
    "Progress": "Kemajuan",
    "Remove": "Hapus",
    "RemoveYourMusic": "Hapus dari Koleksi Kamu",
    "Save": "Simpan",
    "SaveYourMusic": "Simpan ke Koleksi Kamu",
    "Saved": "Tersimpan",
    "StartRadio": "Mulai Radio",
    "Time": "Waktu",
    "Title": "Judul",
    "Toplist": "Lagu teratas",
    "Track": "Lagu",
    "Unfollow": "Berhenti mengikuti",
    "User": "Pengguna",
    "ViewAll": "Lihat Semua",
    "HoursShort": "{0} jam",
    "MinutesShort": "{0} mnt",
    "SecondsShort": "{0} dtk",
    "ErrorTitle": "Ada masalah saat menampilkan halaman ini!",
    "ErrorMessage": "Halaman ini tidak ada atau terjadi kesalahan.",
    "OfflineTitle": "Halaman ini tidak tersedia secara offline!",
    "OfflineMessage": "Alihkan ke online untuk memuat.",
    "MadeFor": "Dibuat untuk {0}",
    "heart_label": "Suka",
    "undo_heart_label": "Disukai",
    "ban_label": "Hapus",
    "format_list.ban_label": "Tidak suka",
    "format_list.undo_ban_label": "Kamu tidak suka ini",
    "VariousArtistsCapitalized": "Berbagai artis"
  }
  },{}],71:[function(require,module,exports){
  module.exports={
    "Album": "Album",
    "AndMore": "e altri {0}",
    "Artist": "Artista",
    "By": "per",
    "ByLabel": "di",
    "Create Similar Playlist": "Crea playlist simile",
    "DateAdded": "Aggiunto il",
    "Duration": "Durata",
    "Recommended Songs": "Brani consigliati",
    "Filter": "Filtra",
    "Follow": "Segui",
    "Follower": "Follower",
    "FollowersLabel": {
      "one": "Follower",
      "other": "Follower"
    },
    "FollowersCount": {
      "one": "{0} follower",
      "other": "{0} follower"
    },
    "Following": "Following",
    "FollowingLabel": "Following",
    "ListenersLabel": "Ascoltatori questo mese",
    "FollowsYou": "Ti segue",
    "HoldToPreview": "Tieni premuto per un'anteprima",
    "ListenCount": {
      "one": "1 riproduzione",
      "other": "{0} riproduzioni"
    },
    "ListenReactionMulti": "{0} ascoltatori nella tua rete",
    "ListenReactionSingle": "{0} ascolta questo",
    "LocalFile": "File locale",
    "More": "Più",
    "MoreSongsLoad": "Durante l'ascolto vengono caricati altri brani",
    "Pause": "Pausa",
    "Play": "Play",
    "Playlist": "Playlist",
    "PressEnterToPlay": "Premi Invio per riprodurre",
    "Popularity": "Popolarità",
    "Progress": "Progress",
    "Remove": "Rimuovi",
    "RemoveYourMusic": "Elimina da La tua libreria",
    "Save": "Salva",
    "SaveYourMusic": "Salva in La tua libreria",
    "Saved": "Salvato",
    "StartRadio": "Crea una radio",
    "Time": "Durata",
    "Title": "Titolo",
    "Toplist": "Brani top",
    "Track": "Brano",
    "Unfollow": "Non seguire più",
    "User": "Utente",
    "ViewAll": "Visualizza tutto",
    "HoursShort": "{0} ore",
    "MinutesShort": "{0} min.",
    "SecondsShort": "{0} sec",
    "ErrorTitle": "Si è verificato un errore nella visualizzazione di questa pagina.",
    "ErrorMessage": "Questa pagina non esiste o si è verificato un errore.",
    "OfflineTitle": "Questa pagina non è disponibile offline.",
    "OfflineMessage": "Passa online per caricare.",
    "MadeFor": "Realizzata per {0}",
    "heart_label": "Preferiti",
    "undo_heart_label": "Preferiti",
    "ban_label": "Rimuovi",
    "format_list.ban_label": "Non mi piace",
    "format_list.undo_ban_label": "Questo elemento non ti piace",
    "VariousArtistsCapitalized": "Artisti vari"
  }
  },{}],72:[function(require,module,exports){
  module.exports={
    "Album": "アルバム",
    "AndMore": "その他{0}人",
    "Artist": "アーティスト",
    "By": "/",
    "ByLabel": "/",
    "Create Similar Playlist": "同様のプレイリストを作成",
    "DateAdded": "追加日",
    "Duration": "期間",
    "Recommended Songs": "おすすめの曲",
    "Filter": "フィルター",
    "Follow": "フォロー",
    "Follower": "フォロワー",
    "FollowersLabel": {
      "one": "フォロワー",
      "other": "フォロワー"
    },
    "FollowersCount": {
      "one": "{0}人のフォロワー",
      "other": "{0}人のフォロワー"
    },
    "Following": "フォロー中",
    "FollowingLabel": "フォロー中",
    "ListenersLabel": "今月のリスナー",
    "FollowsYou": "あなたをフォローしています",
    "HoldToPreview": "プレビューするには、クリックを押しします",
    "ListenCount": {
      "one": "1回再生",
      "other": "{0}回再生"
    },
    "ListenReactionMulti": "ネットワーク内で{0}人が聴いています",
    "ListenReactionSingle": "{0}さんはこれを聴いています",
    "LocalFile": "ローカルファイル",
    "More": "詳細",
    "MoreSongsLoad": "再生すると、さらに曲がロードされます",
    "Pause": "一時停止",
    "Play": "曲の再生",
    "Playlist": "プレイリスト",
    "PressEnterToPlay": "再生するにはEnterキーを押します",
    "Popularity": "人気",
    "Progress": "進捗",
    "Remove": "削除",
    "RemoveYourMusic": "My Musicから削除",
    "Save": "保存",
    "SaveYourMusic": "My Musicに保存",
    "Saved": "保存済み",
    "StartRadio": "Radioを開始",
    "Time": "時間",
    "Title": "タイトル",
    "Toplist": "トップ曲",
    "Track": "ソング",
    "Unfollow": "フォローをやめる",
    "User": "ユーザー",
    "ViewAll": "すべて表示",
    "HoursShort": "{0} 時間",
    "MinutesShort": "{0} 分",
    "SecondsShort": "{0} 秒",
    "ErrorTitle": "このページの表示中に問題が発生しました。",
    "ErrorMessage": "このページは存在しないか、表示中にエラーが発生しました。",
    "OfflineTitle": "このページはオフラインでは利用できません。",
    "OfflineMessage": "ロードするには、インターネットに接続してください。",
    "MadeFor": " {0}さんのために作られたプレイリスト",
    "heart_label": "いいね！",
    "undo_heart_label": "お気に入り",
    "ban_label": "削除",
    "format_list.ban_label": "この曲を削除する",
    "format_list.undo_ban_label": "この曲を削除する",
    "VariousArtistsCapitalized": "様々なアーティスト"
  }
  },{}],73:[function(require,module,exports){
  module.exports={
    "Album": "Album",
    "AndMore": "en {0} meer",
    "Artist": "Artiest",
    "By": "van",
    "ByLabel": "Van",
    "Create Similar Playlist": "Vergelijkbare afspeellijst maken",
    "DateAdded": "Datum toegevoegd",
    "Duration": "Duur",
    "Recommended Songs": "Aanbevolen nummers",
    "Filter": "Filter",
    "Follow": "Volgen",
    "Follower": "Volger",
    "FollowersLabel": {
      "one": "Volger",
      "other": "Volger"
    },
    "FollowersCount": {
      "one": "{0} volger",
      "other": "{0} volgers"
    },
    "Following": "Volgend",
    "FollowingLabel": "Volgend",
    "ListenersLabel": "Luisteraars per maand",
    "FollowsYou": "Volgt jou",
    "HoldToPreview": "Klikken en vasthouden om een stukje te horen",
    "ListenCount": {
      "one": "1 keer afgespeeld",
      "other": "{0} keer afgespeeld"
    },
    "ListenReactionMulti": "{0} luisteraars in jouw netwerk",
    "ListenReactionSingle": "{0} luistert hiernaar",
    "LocalFile": "Lokaal bestand",
    "More": "Meer",
    "MoreSongsLoad": "Meer nummers worden geladen terwijl je luistert",
    "Pause": "Pauze",
    "Play": "Afspelen",
    "Playlist": "Afspeellijst",
    "PressEnterToPlay": "Druk op Enter om af te spelen",
    "Popularity": "Populariteit",
    "Progress": "Voortgang",
    "Remove": "Verwijderen",
    "RemoveYourMusic": "Verwijderen uit Bibliotheek",
    "Save": "Opslaan",
    "SaveYourMusic": "Opslaan in Bibliotheek",
    "Saved": "Opgeslagen",
    "StartRadio": "Radio starten",
    "Time": "Tijd",
    "Title": "Titel",
    "Toplist": "Topnummers",
    "Track": "Nummer",
    "Unfollow": "Niet meer volgen",
    "User": "Gebruiker",
    "ViewAll": "Alles bekijken",
    "HoursShort": "{0} uur",
    "MinutesShort": "{0} min",
    "SecondsShort": "{0} sec",
    "ErrorTitle": "Er is een fout opgetreden bij het weergeven van deze pagina.",
    "ErrorMessage": "Deze weergave bestaat niet of er is een fout opgetreden.",
    "OfflineTitle": "Deze pagina is niet offline beschikbaar.",
    "OfflineMessage": "Ga online om te laden.",
    "MadeFor": "Gemaakt voor {0}",
    "heart_label": "Vind ik leuk",
    "undo_heart_label": "Geliked",
    "ban_label": "Verwijderen",
    "format_list.ban_label": "Niet leuk",
    "format_list.undo_ban_label": "Je vindt dit niet leuk",
    "VariousArtistsCapitalized": "Diverse artiesten"
  }
  },{}],74:[function(require,module,exports){
  module.exports={
    "Album": "Album",
    "AndMore": "i {0} więcej",
    "Artist": "Wykonawca",
    "By": "według",
    "ByLabel": "-",
    "Create Similar Playlist": "Utwórz podobną playlistę",
    "DateAdded": "Data dodania",
    "Duration": "Czas trwania",
    "Recommended Songs": "Polecane utwory",
    "Filter": "Filtruj",
    "Follow": "Obserwuj",
    "Follower": "Obserwujący",
    "FollowersLabel": {
      "one": "Obserwujący",
      "other": "Obserwatorzy"
    },
    "FollowersCount": {
      "one": "{0} obserwujący",
      "other": "{0} obserwujących"
    },
    "Following": "Obserwujesz",
    "FollowingLabel": "Obserwujesz",
    "ListenersLabel": "Słuchacze w tym miesiącu",
    "FollowsYou": "Obserwuje Cię",
    "HoldToPreview": "Aby odsłuchać, kliknij i przytrzymaj.",
    "ListenCount": {
      "one": "1 odtworzenie",
      "other": "Liczba odtworzeń: {0}"
    },
    "ListenReactionMulti": "Liczba słuchaczy w Twojej sieci: {0}",
    "ListenReactionSingle": "Użytkownik {0} słucha tego",
    "LocalFile": "Plik lokalny",
    "More": "Więcej",
    "MoreSongsLoad": "Więcej utworów będzie się pojawiać w miarę słuchania",
    "Pause": "Pauza",
    "Play": "Odtwórz",
    "Playlist": "Playlista",
    "PressEnterToPlay": "Naciśnij Enter, aby odtworzyć",
    "Popularity": "Popularność",
    "Progress": "Postęp",
    "Remove": "Usuń",
    "RemoveYourMusic": "Usuń z Biblioteki",
    "Save": "Zapisz",
    "SaveYourMusic": "Zapisz w Bibliotece",
    "Saved": "Zapisany",
    "StartRadio": "Włącz radio",
    "Time": "Czas",
    "Title": "Tytuł",
    "Toplist": "Najpopularniejsze utwory",
    "Track": "Utwór",
    "Unfollow": "Przestań obserwować",
    "User": "Użytkownik",
    "ViewAll": "Wyświetl wszystko",
    "HoursShort": "{0} godz.",
    "MinutesShort": "{0} min",
    "SecondsShort": "{0} s",
    "ErrorTitle": "Wystąpił błąd podczas wyświetlania tej strony!",
    "ErrorMessage": "Ta strona nie istnieje lub wystąpił błąd.",
    "OfflineTitle": "Ta strona nie jest dostępna w trybie offline!",
    "OfflineMessage": "Przejdź do trybu online, aby załadować.",
    "MadeFor": "Przygotowana dla {0}",
    "heart_label": "Lubię to",
    "undo_heart_label": "Polubiono",
    "ban_label": "Usuń",
    "format_list.ban_label": "Nie lubię",
    "format_list.undo_ban_label": "Nie podoba Ci się",
    "VariousArtistsCapitalized": "Różni wykonawcy"
  }
  },{}],75:[function(require,module,exports){
  module.exports={
    "Album": "Álbum",
    "AndMore": "e mais {0}",
    "Artist": "Artista",
    "By": "de",
    "ByLabel": "De",
    "Create Similar Playlist": "Criar playlist similar",
    "DateAdded": "Adicionado em",
    "Duration": "Duração",
    "Recommended Songs": "Músicas recomendadas",
    "Filter": "Filtrar",
    "Follow": "Seguir",
    "Follower": "Seguidor",
    "FollowersLabel": {
      "one": "Seguidor",
      "other": "Seguidores"
    },
    "FollowersCount": {
      "one": "{0} seguidor",
      "other": "{0} seguidores"
    },
    "Following": "Seguindo",
    "FollowingLabel": "Seguindo",
    "ListenersLabel": "Ouvintes mensais",
    "FollowsYou": "Segue você",
    "HoldToPreview": "Clique e segure para a prévia",
    "ListenCount": {
      "one": "1 reprodução",
      "other": "{0} reproduções"
    },
    "ListenReactionMulti": "{0} ouvintes na sua rede",
    "ListenReactionSingle": "{0} ouvem isso",
    "LocalFile": "Arquivo local",
    "More": "Mais",
    "MoreSongsLoad": "Mais músicas serão carregadas à medida que você for escutando",
    "Pause": "Pause",
    "Play": "Play",
    "Playlist": "Playlist",
    "PressEnterToPlay": "Pressione Enter para tocar",
    "Popularity": "Popularidade",
    "Progress": "Progresso",
    "Remove": "Remover",
    "RemoveYourMusic": "Remover da Sua Biblioteca",
    "Save": "Salvar",
    "SaveYourMusic": "Salvar na Sua Biblioteca",
    "Saved": "Salvo",
    "StartRadio": "Abrir rádio",
    "Time": "Tempo",
    "Title": "Título",
    "Toplist": "Músicas mais tocadas",
    "Track": "Música",
    "Unfollow": "Deixar de seguir",
    "User": "Usuário",
    "ViewAll": "Ver tudo",
    "HoursShort": "{0} h",
    "MinutesShort": "{0} m",
    "SecondsShort": "{0} s",
    "ErrorTitle": "Tivemos um problema ao mostrar esta página!",
    "ErrorMessage": "Essa página não existe ou ocorreu um erro.",
    "OfflineTitle": "Essa página não está disponível offline!",
    "OfflineMessage": "Fique online para carregar.",
    "MadeFor": "Feito para {0}",
    "heart_label": "Curtir",
    "undo_heart_label": "Curtiu",
    "ban_label": "Remover",
    "format_list.ban_label": "Não curto",
    "format_list.undo_ban_label": "Você não curte isso",
    "VariousArtistsCapitalized": "Vários artistas"
  }
  },{}],76:[function(require,module,exports){
  module.exports={
    "Album": "Album",
    "AndMore": "och {0} till",
    "Artist": "Artist",
    "By": "av",
    "ByLabel": "Av",
    "Create Similar Playlist": "Skapa en liknande spellista",
    "DateAdded": "Tillagd",
    "Duration": "Längd",
    "Recommended Songs": "Rekommenderade låtar",
    "Filter": "Filtrera",
    "Follow": "Följ",
    "Follower": "Följare",
    "FollowersLabel": {
      "one": "Följare",
      "other": "Följare"
    },
    "FollowersCount": {
      "one": "{0} följare",
      "other": "{0} följare"
    },
    "Following": "Följer",
    "FollowingLabel": "Följer",
    "ListenersLabel": "Lyssnare per månad",
    "FollowsYou": "Följer dig",
    "HoldToPreview": "Klicka och håll kvar för att förhandslyssna",
    "ListenCount": {
      "one": "1 uppspelning",
      "other": "{0} uppspelningar"
    },
    "ListenReactionMulti": "{0} lyssnare i ditt nätverk",
    "ListenReactionSingle": "{0} lyssnar på detta",
    "LocalFile": "Lokal fil",
    "More": "Mer",
    "MoreSongsLoad": "Fler låtar läses in medan du lyssnar",
    "Pause": "Pausa",
    "Play": "Spela upp",
    "Playlist": "Spellista",
    "PressEnterToPlay": "Tryck på Retur om du vill lyssna",
    "Popularity": "Popularitet",
    "Progress": "Lyssnat",
    "Remove": "Ta bort",
    "RemoveYourMusic": "Ta bort från Ditt bibliotek",
    "Save": "Spara",
    "SaveYourMusic": "Spara i Ditt bibliotek",
    "Saved": "Sparade",
    "StartRadio": "Starta radio",
    "Time": "Tid",
    "Title": "Titel",
    "Toplist": "Topplåtar",
    "Track": "Låt",
    "Unfollow": "Sluta följa",
    "User": "Användare",
    "ViewAll": "Visa alla",
    "HoursShort": "{0} tim",
    "MinutesShort": "{0} min",
    "SecondsShort": "{0} sek",
    "ErrorTitle": "Det uppstod ett fel när den här sidan skulle visas!",
    "ErrorMessage": "Den här sidan finns inte eller så uppstod ett fel.",
    "OfflineTitle": "Den här sidan är inte tillgänglig offline!",
    "OfflineMessage": "Anslut till internet om du vill läsa in appen.",
    "MadeFor": "Skapad för {0}",
    "heart_label": "Gilla",
    "undo_heart_label": "Gillar",
    "ban_label": "Ta bort",
    "format_list.ban_label": "Gillar inte",
    "format_list.undo_ban_label": "Du gillar inte det här",
    "VariousArtistsCapitalized": "Olika artister"
  }
  },{}],77:[function(require,module,exports){
  module.exports={
    "Album": "อัลบั้ม",
    "AndMore": "และอีก {0} คน",
    "Artist": "ศิลปิน",
    "By": "โดย",
    "ByLabel": "จาก",
    "Create Similar Playlist": "สร้างเพลย์ลิสต์ที่คล้ายกัน",
    "DateAdded": "เพิ่มวันที่แล้ว",
    "Duration": "ระยะเวลา",
    "Recommended Songs": "เพลงที่แนะนำ",
    "Filter": "ตัวกรอง",
    "Follow": "ติดตาม",
    "Follower": "ผู้ติดตาม",
    "FollowersLabel": {
      "one": "ผู้ติดตาม",
      "other": "ผู้ติดตาม"
    },
    "FollowersCount": {
      "one": "ผู้ติดตาม {0} คน",
      "other": "ผู้ติดตาม {0} คน"
    },
    "Following": "กำลังติดตาม",
    "FollowingLabel": "กำลังติดตาม",
    "ListenersLabel": "ผู้ฟังรายเดือน",
    "FollowsYou": "ติดตามคุณ",
    "HoldToPreview": "คลิกค้างไว้เพื่อฟังตัวอย่าง",
    "ListenCount": {
      "one": "เล่น 1 ครั้ง",
      "other": "เล่น {0} ครั้ง"
    },
    "ListenReactionMulti": "ผู้ฟัง {0} คนในเครือข่ายของคุณ",
    "ListenReactionSingle": "{0} ฟังรายการนี้",
    "LocalFile": "ไฟล์ในเครื่อง",
    "More": "เพิ่มเติม",
    "MoreSongsLoad": "เราจะโหลดเพลงเพิ่มระหว่างที่คุณฟัง",
    "Pause": "หยุดชั่วคราว",
    "Play": "เล่น",
    "Playlist": "เพลย์ลิสต์",
    "PressEnterToPlay": "กด Enter เพื่อเล่น",
    "Popularity": "ความนิยม",
    "Progress": "ความคืบหน้า",
    "Remove": "ลบ",
    "RemoveYourMusic": "ลบจากคอลเลกชันของคุณ",
    "Save": "บันทึก",
    "SaveYourMusic": "บันทึกในคอลเลกชันของคุณ",
    "Saved": "บันทึกแล้ว",
    "StartRadio": "เริ่มต้นวิทยุ",
    "Time": "เวลา",
    "Title": "ชื่อ",
    "Toplist": "เพลงยอดนิยม",
    "Track": "เพลง",
    "Unfollow": "เลิกติดตาม",
    "User": "ผู้ใช้",
    "ViewAll": "ดูทั้งหมด",
    "HoursShort": "{0} ชั่วโมง",
    "MinutesShort": "{0} นาที",
    "SecondsShort": "{0} วินาที",
    "ErrorTitle": "เกิดปัญหาในการแสดงผลหน้านี้!",
    "ErrorMessage": "ไม่มีหน้านี้หรือเกิดข้อผิดพลาดขึ้น",
    "OfflineTitle": "ไม่สามารถใช้งานหน้านี้ขณะออฟไลน์ได้!",
    "OfflineMessage": "โปรดใช้งานแบบออนไลน์เพื่อโหลด",
    "MadeFor": "จัดทำเพื่อ {0}",
    "heart_label": "ถูกใจ",
    "undo_heart_label": "ถูกใจแล้ว",
    "ban_label": "ลบ",
    "format_list.ban_label": "ไม่ชอบ",
    "format_list.undo_ban_label": "คุณไม่ชอบเนื้อหานี้",
    "VariousArtistsCapitalized": "รวมศิลปิน"
  }
  },{}],78:[function(require,module,exports){
  module.exports={
    "Album": "Albüm",
    "AndMore": "ve {0} daha",
    "Artist": "Sanatçı",
    "By": "-",
    "ByLabel": "-",
    "Create Similar Playlist": "Benzer Çalma Listesi Oluştur",
    "DateAdded": "Tarih Eklendi",
    "Duration": "Süre",
    "Recommended Songs": "Önerilen Şarkılar",
    "Filter": "Filtrele",
    "Follow": "Takip Et",
    "Follower": "Takipçi",
    "FollowersLabel": {
      "one": "Takipçi",
      "other": "Takipçi"
    },
    "FollowersCount": {
      "one": "{0} takipçi",
      "other": "{0} takipçi"
    },
    "Following": "Takip Ediliyor",
    "FollowingLabel": "Takip Ediliyor",
    "ListenersLabel": "Aylık Dinleyici",
    "FollowsYou": "Seni Takip Ediyor",
    "HoldToPreview": "Basılı Tut ve Önizle",
    "ListenCount": {
      "one": "1 çalma",
      "other": "{0} dinleme"
    },
    "ListenReactionMulti": "Ağında {0} dinleyici",
    "ListenReactionSingle": "{0} bunu dinliyor",
    "LocalFile": "Yerel Dosya",
    "More": "Daha fazla",
    "MoreSongsLoad": "Sen dinledikçe daha fazla şarkı yüklenir",
    "Pause": "Duraklat",
    "Play": "Çal",
    "Playlist": "Çalma listesi",
    "PressEnterToPlay": "Çalmak için Enter'a bas",
    "Popularity": "Popülerlik",
    "Progress": "Progress",
    "Remove": "Çıkar",
    "RemoveYourMusic": "Kitaplığın'dan kaldır",
    "Save": "Kaydet",
    "SaveYourMusic": "Kitaplığın'a kaydet",
    "Saved": "Kaydedildi",
    "StartRadio": "Radyoyu Başlat",
    "Time": "Saat",
    "Title": "Başlık",
    "Toplist": "En çok dinlenen şarkılar",
    "Track": "Şarkı",
    "Unfollow": "Takip Etmeyi Bırak",
    "User": "Kullanıcı",
    "ViewAll": "Tümünü Görüntüle",
    "HoursShort": "{0} sa",
    "MinutesShort": "{0} dk",
    "SecondsShort": "{0} sn",
    "ErrorTitle": "Bu sayfa görüntülenirken bir sorun oluştu!",
    "ErrorMessage": "Bu sayfa yok veya bir hata oluştu.",
    "OfflineTitle": "Bu sayfa çevrimdışı kullanılamaz!",
    "OfflineMessage": "Yüklemek için lütfen çevrimiçi ol.",
    "MadeFor": "{0} İçin Yapıldı",
    "heart_label": "Beğen",
    "undo_heart_label": "Beğenildi",
    "ban_label": "Çıkar",
    "format_list.ban_label": "Beğenme",
    "format_list.undo_ban_label": "Bunu beğenmedin",
    "VariousArtistsCapitalized": "Çeşitli sanatçılar"
  }
  },{}],79:[function(require,module,exports){
  module.exports={
    "Album": "Album",
    "AndMore": "và thêm {0}",
    "Artist": "Nghệ sĩ",
    "By": "của",
    "ByLabel": "Của",
    "Create Similar Playlist": "Tạo Playlist Tương tự",
    "DateAdded": "Đã Thêm Ngày",
    "Duration": "Thời lượng",
    "Recommended Songs": "Bài hát Gợi ý",
    "Filter": "Bộ lọc",
    "Follow": "Theo dõi",
    "Follower": "Người theo dõi",
    "FollowersLabel": {
      "one": "Người theo dõi",
      "other": "Người theo dõi"
    },
    "FollowersCount": {
      "one": "{0} người theo dõi",
      "other": "{0} người theo dõi"
    },
    "Following": "Đang theo dõi",
    "FollowingLabel": "Đang theo dõi",
    "ListenersLabel": "Người nghe Hàng tháng",
    "FollowsYou": "Theo dõi Bạn",
    "HoldToPreview": "Nhấp và Giữ để Xem trước",
    "ListenCount": {
      "one": "1 lượt phát",
      "other": "{0} phát"
    },
    "ListenReactionMulti": "{0} người nghe trong mạng lưới của bạn",
    "ListenReactionSingle": "{0} nghe bản này",
    "LocalFile": "File trên máy",
    "More": "Tuỳ chọn...",
    "MoreSongsLoad": "Tải được nhiều bài hát hơn khi bạn nghe",
    "Pause": "Tạm dừng",
    "Play": "Phát",
    "Playlist": "Playlist",
    "PressEnterToPlay": "Nhấn Enter để phát",
    "Popularity": "Mức độ phổ biến",
    "Progress": "Tiến độ",
    "Remove": "Xóa",
    "RemoveYourMusic": "Xóa khỏi Thư viện",
    "Save": "Lưu",
    "SaveYourMusic": "Lưu vào Thư viện",
    "Saved": "Đã lưu",
    "StartRadio": "Bắt đầu Radio",
    "Time": "Thời gian",
    "Title": "Tiêu đề",
    "Toplist": "Các bài hát hàng đầu",
    "Track": "Bài hát",
    "Unfollow": "Hủy theo dõi",
    "User": "Người dùng",
    "ViewAll": "Xem Tất cả",
    "HoursShort": "{0} giờ",
    "MinutesShort": "{0} phút",
    "SecondsShort": "{0} giây",
    "ErrorTitle": "Đã có sự cố xảy ra khi hiển thị trang này!",
    "ErrorMessage": "Nội dung này hoặc không tồn tại hoặc đã có lỗi xảy ra.",
    "OfflineTitle": "Trang này không có ở chế độ offline!",
    "OfflineMessage": "Vui lòng truy cập trực tuyến để tải.",
    "MadeFor": "Dành cho {0}",
    "heart_label": "Thích",
    "undo_heart_label": "Đã thích",
    "ban_label": "Xóa",
    "format_list.ban_label": "Không thích",
    "format_list.undo_ban_label": "Bạn không thích điều này",
    "VariousArtistsCapitalized": "Nhiều nghệ sĩ"
  }
  },{}],80:[function(require,module,exports){
  module.exports={
    "Album": "專輯",
    "AndMore": "還有 {0} 首",
    "Artist": "藝人",
    "By": "－",
    "ByLabel": "來自",
    "Create Similar Playlist": "建立相似的播放清單",
    "DateAdded": "已加入日期",
    "Duration": "長度",
    "Recommended Songs": "推薦歌曲",
    "Filter": "篩選",
    "Follow": "關注",
    "Follower": "粉絲",
    "FollowersLabel": {
      "one": "粉絲",
      "other": "粉絲"
    },
    "FollowersCount": {
      "one": "{0} 粉絲",
      "other": "{0} 位粉絲"
    },
    "Following": "正在關注",
    "FollowingLabel": "正在關注",
    "ListenersLabel": "每月聽眾",
    "FollowsYou": "關注你",
    "HoldToPreview": "按住即可預覽",
    "ListenCount": {
      "one": "1 個播放項目",
      "other": "播放了 {0} 次"
    },
    "ListenReactionMulti": "在你的社群網路中有 {0} 名聽眾",
    "ListenReactionSingle": "{0} 收聽了這首歌曲",
    "LocalFile": "本機檔案",
    "More": "更多",
    "MoreSongsLoad": "聆聽時載入更多歌曲",
    "Pause": "暫停",
    "Play": "播放",
    "Playlist": "播放列表",
    "PressEnterToPlay": "請按 Enter 播放",
    "Popularity": "流行",
    "Progress": "進度",
    "Remove": "移除",
    "RemoveYourMusic": "從你的音樂庫中移除",
    "Save": "儲存",
    "SaveYourMusic": "儲存至你的音樂庫",
    "Saved": "已儲存",
    "StartRadio": "啟用電臺",
    "Time": "時間",
    "Title": "標題",
    "Toplist": "當紅歌曲",
    "Track": "歌曲",
    "Unfollow": "取消關注",
    "User": "使用者",
    "ViewAll": "檢視全部",
    "HoursShort": "{0} 小時",
    "MinutesShort": "{0} 分鐘",
    "SecondsShort": "{0} 秒鐘",
    "ErrorTitle": "顯示這個頁面時出現問題！",
    "ErrorMessage": "這個頁面可能不存在或發生錯誤。",
    "OfflineTitle": "離線時無法進入這個頁面！",
    "OfflineMessage": "請上網以載入。",
    "MadeFor": "專為 {0} 精心打造",
    "heart_label": "讚",
    "undo_heart_label": "已按讚",
    "ban_label": "移除",
    "format_list.ban_label": "不喜歡",
    "format_list.undo_ban_label": "你不喜歡這個",
    "VariousArtistsCapitalized": "多位藝人"
  }
  },{}],81:[function(require,module,exports){
  module.exports={
    "Album": "Album",
    "AndMore": "dan {0} lagi",
    "Artist": "Artis",
    "By": "oleh",
    "ByLabel": "Lebih banyak lagi oleh",
    "Create Similar Playlist": "Cipta Senarai Main Serupa",
    "DateAdded": "Tarikh Ditambah",
    "Duration": "Tempoh",
    "Recommended Songs": "Lagu yang Dicadangkan",
    "Filter": "Penapis",
    "Follow": "Ikut",
    "Follower": "Pengikut",
    "FollowersLabel": {
      "one": "Pengikut",
      "other": "Pengikut"
    },
    "FollowersCount": {
      "one": "{0} pengikut",
      "other": "{0} pengikut"
    },
    "Following": "Mengikuti",
    "FollowingLabel": "Mengikuti",
    "ListenersLabel": "Pendengar Bulanan",
    "FollowsYou": "Megikuti Anda",
    "HoldToPreview": "Klik dan Tahan untuk Pratonton",
    "ListenCount": {
      "one": "1 main",
      "other": "{0} main"
    },
    "ListenReactionMulti": "{0} pendengar dalam rangkaian anda",
    "ListenReactionSingle": "{0} mendengar ini",
    "LocalFile": "Fail Setempat",
    "More": "Lebih banyak",
    "MoreSongsLoad": "Lebih banyak lagu dimuatkan semasa anda mendengar",
    "Pause": "Jeda",
    "Play": "Main",
    "Playlist": "Senarai main",
    "PressEnterToPlay": "Tekan Enter untuk main",
    "Popularity": "Populariti",
    "Progress": "Perkembangan",
    "Remove": "Keluarkan",
    "RemoveYourMusic": "Keluarkan daripada Pustaka Anda",
    "Save": "Simpan",
    "SaveYourMusic": "Simpan ke Pustaka Anda",
    "Saved": "Disimpan",
    "StartRadio": "Mulakan Radio",
    "Time": "Masa",
    "Title": "Tajuk",
    "Toplist": "Lagu popular",
    "Track": "Lagu",
    "Unfollow": "Nyahikut",
    "User": "Pengguna",
    "ViewAll": "Lihat Semua",
    "HoursShort": "{0} jam",
    "MinutesShort": "{0} min",
    "SecondsShort": "{0} saat",
    "ErrorTitle": "Terdapat masalah untuk memaparkan laman ini!",
    "ErrorMessage": "Laman ini sama ada tidak wujud atau ralat telah berlaku.",
    "OfflineTitle": "Laman ini tidak tersedia di luar talian!",
    "OfflineMessage": "Sila ke online untuk memuatkan.",
    "MadeFor": "Dibuat untuk {0}",
    "heart_label": "Suka",
    "undo_heart_label": "Disukai",
    "ban_label": "Keluarkan",
    "format_list.ban_label": "Tidak suka",
    "format_list.undo_ban_label": "Anda tidak sukakannya",
    "VariousArtistsCapitalized": "Pelbagai artis"
  }
  },{}],82:[function(require,module,exports){
  'use strict';
  
  var _gridOverlay = require('./src/gridOverlay');
  
  var gridOverlay = _interopRequireWildcard(_gridOverlay);
  
  var _responsiveTitleType = require('./src/responsiveTitleType');
  
  var responsiveTitleType = _interopRequireWildcard(_responsiveTitleType);
  
  var _attentionHighlight = require('./src/attentionHighlight');
  
  var attentionHighlight = _interopRequireWildcard(_attentionHighlight);
  
  function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
  
  exports.gridOverlay = gridOverlay;
  exports.responsiveTitleType = responsiveTitleType;
  exports.attentionHighlight = attentionHighlight;
  
  },{"./src/attentionHighlight":83,"./src/gridOverlay":84,"./src/responsiveTitleType":85}],83:[function(require,module,exports){
  "use strict";
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  const HIDE_DELAY = exports.HIDE_DELAY = 2000;
  
  },{}],84:[function(require,module,exports){
  (function (global){
  'use strict';
  
  const cosmos = require('spotify-cosmos-api');
  
  const DEFAULT_GRID_COLOR = 'rgba(251, 74, 131, 0.22)';
  const DEFAULT_GRID_COLOR_HIGHLIGHT = 'rgba(251, 74, 131, 0.8)';
  const GRID_BASELINE = 8;
  
  let overlayElement = null;
  let baselineHighlightElement = null;
  let gridColor = '';
  let highlightGridColor = '';
  const gridModes = ['off', 'column', 'baseline'];
  let currentModeIndex = 0;
  
  /**
   * Listen for control messages to toggle the grid when a menu item is clicked
   * or a keyboard shortcut is pressed. This only happens if the user is a global
   * app developer.
   */
  function listen() {
    const isDeveloper = global.__spotify.developer_mode;
    if (isDeveloper) {
      cosmos.resolver.subscribe({
        url: 'sp://messages/v1/container/control'
      }, function (error, response) {
        if (error) return;
        const data = response.getJSONBody();
        if (data && data.type === 'toggle_grid') {
          toggle();
        }
      });
    }
  }
  
  /**
   * Toggle the grid overlay.
   */
  function toggle() {
    let newIndex = currentModeIndex + 1;
    if (newIndex > gridModes.length - 1) {
      newIndex = 0;
    }
  
    const mode = gridModes[newIndex];
  
    if (mode === 'off') {
      disable();
    } else {
      enable(mode);
    }
  }
  
  /**
   * Enable the grid overlay.
   *
   * @param {string} mode The grid mode to enable, 'column' or 'baseline'.
   */
  function enable(mode) {
    if (!mode) {
      return;
    }
  
    if (gridModes[currentModeIndex] === mode) {
      return;
    }
  
    currentModeIndex = gridModes.indexOf(mode);
  
    if (overlayElement && overlayElement.parentNode) {
      overlayElement.parentNode.removeChild(overlayElement);
    }
  
    if (mode === 'baseline') {
      document.addEventListener('mousemove', onMouseMove, false);
    }
  
    overlayElement = createOverlayElement();
  
    document.body.appendChild(overlayElement);
  }
  
  /**
   * Disable the grid overlay.
   */
  function disable() {
    if (gridModes[currentModeIndex] === 'off') {
      return;
    }
  
    if (overlayElement && overlayElement.parentNode) {
      overlayElement.parentNode.removeChild(overlayElement);
    }
  
    currentModeIndex = gridModes.indexOf('off');
    overlayElement = null;
    baselineHighlightElement = null;
  
    document.removeEventListener('mousemove', onMouseMove, false);
  }
  
  /**
   * Set the color used for each grid column or baseline line.
   *
   * @param {string} color Any valid CSS color.
   * @param {string=} highlightColor Any valid CSS color. Can be omitted for
   *     column grid.
   */
  function setColor(color, highlightColor) {
    gridColor = color;
    highlightGridColor = highlightColor;
  }
  
  /**
   * Reset all state in this module.
   */
  function reset() {
    overlayElement = null;
    baselineHighlightElement = null;
    gridColor = '';
    highlightGridColor = '';
    currentModeIndex = 0;
  }
  
  /**
   * Mouse move handler that highlights the hovered baseline line.
   *
   * @param {Event} event A mousemove event object.
   *
   * @private
   */
  function onMouseMove(event) {
    if (!baselineHighlightElement) {
      baselineHighlightElement = document.createElement('div');
      baselineHighlightElement.className = 'grid-overlay-baseline-highlight';
      baselineHighlightElement.style.backgroundColor = highlightGridColor || DEFAULT_GRID_COLOR_HIGHLIGHT;
      overlayElement.appendChild(baselineHighlightElement);
    }
  
    // Calculate the Y position for the baseline line closest to the pointer
    const pointerPos = event.clientY + window.scrollY;
    const yLineAbove = Math.floor(pointerPos / GRID_BASELINE) * GRID_BASELINE;
    const y = yLineAbove + (pointerPos % GRID_BASELINE > 4 ? GRID_BASELINE : 0);
  
    baselineHighlightElement.style.top = `${y - 1}px`;
  }
  
  /**
   * Create the DOM nodes needed for the overlay, with the correct class names
   * and styles.
   *
   * @return {HTMLElement} The container element for the overlay.
   *
   * @private
   */
  function createOverlayElement() {
    const color = gridColor || DEFAULT_GRID_COLOR;
  
    const container = document.createElement('div');
    container.className = 'grid-overlay container';
  
    if (gridModes[currentModeIndex] === 'column') {
      const row = document.createElement('div');
      row.className = 'grid-overlay-row row';
      container.appendChild(row);
  
      const sizeLabel = createSizeLabel();
      container.appendChild(sizeLabel);
  
      const columnClassNames = 'col-xs-1 col-sm-1 col-md-1 col-lg-1';
  
      for (let i = 0; i < 12; i++) {
        const column = document.createElement('div');
        column.className = `grid-overlay-col-${i + 1} ${columnClassNames}`;
  
        column.style.backgroundColor = color;
  
        row.appendChild(column);
      }
    } else if (gridModes[currentModeIndex] === 'baseline') {
      container.classList.add('grid-overlay-baseline');
  
      const baselinePercentage = `${(GRID_BASELINE - 1) / GRID_BASELINE * 100}%`;
  
      const backgroundImage = ['linear-gradient(', 'to bottom, ', 'transparent, ', `transparent ${baselinePercentage}, `, `${color} ${baselinePercentage}`, ')'].join('');
  
      container.style.backgroundImage = backgroundImage;
  
      // Because JSDOM is using the package 'cssstyle', which is stupid and
      // doesn't support gradients as values...
      container.style._backgroundImage = backgroundImage;
    }
  
    return container;
  }
  
  /**
   * Create the DOM nodes needed for the grid size label.
   *
   * @return {HTMLElement} A DOM node.
   *
   * @private
   */
  function createSizeLabel() {
    const sizes = [{ name: 'Extra Small', id: 'xs' }, { name: 'Small', id: 'sm' }, { name: 'Medium', id: 'md' }, { name: 'Large', id: 'lg' }];
  
    const labelContainer = document.createElement('div');
    labelContainer.className = 'grid-overlay-label';
  
    sizes.forEach(function (size) {
      const label = document.createElement('span');
      label.className = `visible-${size.id}`;
      label.textContent = `${size.name} (${size.id})`;
      labelContainer.appendChild(label);
    });
  
    return labelContainer;
  }
  
  exports.listen = listen;
  exports.toggle = toggle;
  exports.enable = enable;
  exports.disable = disable;
  exports.setColor = setColor;
  exports.reset = reset;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"spotify-cosmos-api":543}],85:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.loadFont = loadFont;
  exports.setSizeForTitle = setSizeForTitle;
  /**
   * Usage:
   *
   * loadFont().then(() => {
   *   setSizeForTitle(titleNode, {
   *     useLargeTitle: true,
   *   });
   * });
   */
  
  // There is a bottom padding in the title element, to allow the bottom border
  // of links to be rendered correctly. This number here is specified as a
  // percentage of the height of a single line.
  const PADDING_BOTTOM_PERCENTAGE = 0.07;
  function getSizeSpecs() {
    // Some characters in Thai are too big and get cropped at the top.
    if (window.__spotify.locale === 'th') {
      return {
        default: {
          fontSizes: [[48, 36], [36, 28]],
          lineHeights: [[75, 58], [58, 44]]
        },
        large: {
          fontSizes: [[96, 72, 48], [48, 36, 28]],
          lineHeights: [[148, 111, 75], [75, 58, 44]]
        }
      };
    }
    return {
      default: {
        fontSizes: [[48, 36], [36, 28]],
        lineHeights: [[56, 44], [44, 36]]
      },
      large: {
        fontSizes: [[96, 72, 48], [48, 36, 28]],
        lineHeights: [[110, 82, 56], [56, 44, 36]]
      }
    };
  }
  
  /**
   * The font must be loaded before any sizing can be done.
   *
   * @return {Promise}
   */
  function loadFont() {
    return document.fonts.load('500 48px spotify-circular');
  }
  
  /**
   * Set the type size for the provided title node. The font must be loaded
   * before calling this function.
   *
   * @param {HTMLElement} node The title node.
   * @param {Object=} options An optional object with options:
   *   - useLargeTitle: true/false (default: false)
   */
  function setSizeForTitle(node, options = {}) {
    const sizeSpecId = options.useLargeTitle ? 'large' : 'default';
    const { fontSizes, lineHeights } = getSizeSpecs()[sizeSpecId];
  
    let lineCount = 1;
    let sizeIndex = 0;
    let fontSize = fontSizes[0][0];
    let lineHeight = lineHeights[0][0];
    let renderedLineCount = lineCount + 1;
    let lastBottomPadding = Math.ceil(PADDING_BOTTOM_PERCENTAGE * lineHeight);
  
    // Set initial values (largest type, one line, but clamped to two lines to
    // allow for the text to wrap so we can detect that it doesn't fit).
    node.style.webkitLineClamp = renderedLineCount;
    node.style.fontSize = `${fontSize}px`;
    node.style.lineHeight = `${lineHeight}px`;
    node.style.paddingBottom = `${lastBottomPadding}px`;
    node.style.marginBottom = `-${lastBottomPadding}px`;
    node.removeAttribute('title');
  
    while (lineCount <= fontSizes.length && node.offsetHeight - lastBottomPadding > lineHeight * lineCount) {
      sizeIndex++;
  
      if (!fontSizes[lineCount - 1][sizeIndex]) {
        sizeIndex = 0;
        lineCount++;
      }
  
      if (lineCount <= fontSizes.length) {
        fontSize = fontSizes[lineCount - 1][sizeIndex];
        lineHeight = lineHeights[lineCount - 1][sizeIndex];
        renderedLineCount = lineCount + 1;
        lastBottomPadding = Math.ceil(PADDING_BOTTOM_PERCENTAGE * lineHeight);
  
        node.style.webkitLineClamp = renderedLineCount;
        node.style.fontSize = `${fontSize}px`;
        node.style.lineHeight = `${lineHeight}px`;
        node.style.paddingBottom = `${lastBottomPadding}px`;
        node.style.marginBottom = `-${lastBottomPadding}px`;
      }
    }
  
    if (lineCount > fontSizes.length) {
      // Add title attribute if the text still doesn't fit and gets truncated
      node.setAttribute('title', node.textContent.trim());
    }
  
    node.style.webkitLineClamp = renderedLineCount - 1;
  }
  
  },{}],86:[function(require,module,exports){
  'use strict';
  
  module.exports = {
    'cs': require('../i18n/json/cs.json'),
    'de': require('../i18n/json/de.json'),
    'el': require('../i18n/json/el.json'),
    'en': require('../i18n/json/en.json'),
    'es': require('../i18n/json/es.json'),
    'es-419': require('../i18n/json/es-419.json'),
    'fi': require('../i18n/json/fi.json'),
    'fr': require('../i18n/json/fr.json'),
    'fr-CA': require('../i18n/json/fr-CA.json'),
    'hu': require('../i18n/json/hu.json'),
    'id': require('../i18n/json/id.json'),
    'it': require('../i18n/json/it.json'),
    'ja': require('../i18n/json/ja.json'),
    'nl': require('../i18n/json/nl.json'),
    'pl': require('../i18n/json/pl.json'),
    'pt-BR': require('../i18n/json/pt-BR.json'),
    'sv': require('../i18n/json/sv.json'),
    'th': require('../i18n/json/th.json'),
    'tr': require('../i18n/json/tr.json'),
    'vi': require('../i18n/json/vi.json'),
    'zh-Hant': require('../i18n/json/zh-Hant.json'),
    'zsm': require('../i18n/json/zsm.json')
  };
  
  },{"../i18n/json/cs.json":60,"../i18n/json/de.json":61,"../i18n/json/el.json":62,"../i18n/json/en.json":63,"../i18n/json/es-419.json":64,"../i18n/json/es.json":65,"../i18n/json/fi.json":66,"../i18n/json/fr-CA.json":67,"../i18n/json/fr.json":68,"../i18n/json/hu.json":69,"../i18n/json/id.json":70,"../i18n/json/it.json":71,"../i18n/json/ja.json":72,"../i18n/json/nl.json":73,"../i18n/json/pl.json":74,"../i18n/json/pt-BR.json":75,"../i18n/json/sv.json":76,"../i18n/json/th.json":77,"../i18n/json/tr.json":78,"../i18n/json/vi.json":79,"../i18n/json/zh-Hant.json":80,"../i18n/json/zsm.json":81}],87:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3=container.escapeExpression;
  
    return "  <div class=\"b-add-text\">"
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"Save",{"name":"loc","hash":{},"data":data}))
      + "</div>\n  <div class=\"b-added-text b-alt-text\">"
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"Saved",{"name":"loc","hash":{},"data":data}))
      + "</div>\n  <div class=\"b-remove-text b-alt-text\">"
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"Remove",{"name":"loc","hash":{},"data":data}))
      + "</div>\n";
  },"3":function(container,depth0,helpers,partials,data) {
      var alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3=container.escapeExpression;
  
    return "  <div class=\"b-add-text\">"
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"Follow",{"name":"loc","hash":{},"data":data}))
      + "</div>\n  <div class=\"b-added-text b-alt-text\">"
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"Following",{"name":"loc","hash":{},"data":data}))
      + "</div>\n  <div class=\"b-remove-text b-alt-text\">"
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"Unfollow",{"name":"loc","hash":{},"data":data}))
      + "</div>\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing;
  
    return ((stack1 = (helpers.type || (depth0 && depth0.type) || alias2).call(alias1,(depth0 != null ? depth0.uri : depth0),"track","album",{"name":"type","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n"
      + ((stack1 = (helpers.type || (depth0 && depth0.type) || alias2).call(alias1,(depth0 != null ? depth0.uri : depth0),"playlist","user","artist","station","show",{"name":"type","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"useData":true});
  
  },{"hbsfy/runtime":368}],88:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  module.exports = HandlebarsCompiler.template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3=container.escapeExpression;
  
    return "<div class=\"b-play-text\">"
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"Play",{"name":"loc","hash":{},"data":data}))
      + "</div>\n<div class=\"b-pause-text b-alt-text\">"
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"Pause",{"name":"loc","hash":{},"data":data}))
      + "</div>\n";
  },"useData":true});
  
  },{"hbsfy/runtime":368}],89:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('./internal/image-with-overlay.hbs');
  HandlebarsCompiler.registerPartial('./internal/image-with-overlay.hbs', partial$0);
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      return "      card-video\n";
  },"3":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = (helpers.compare || (depth0 && depth0.compare) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.isVideo : depth0),false,{"name":"compare","hash":{},"fn":container.program(4, data, 0),"inverse":container.program(6, data, 0),"data":data})) != null ? stack1 : "");
  },"4":function(container,depth0,helpers,partials,data) {
      return "";
  },"6":function(container,depth0,helpers,partials,data) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing;
  
    return ((stack1 = (helpers.compare || (depth0 && depth0.compare) || alias2).call(alias1,(helpers.type || (depth0 && depth0.type) || alias2).call(alias1,(depth0 != null ? depth0.uri : depth0),{"name":"type","hash":{},"data":data}),"episode",{"name":"compare","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"7":function(container,depth0,helpers,partials,data) {
      return "          card-video\n";
  },"9":function(container,depth0,helpers,partials,data) {
      return "card-type-station-"
      + container.escapeExpression((helpers.type || (depth0 && depth0.type) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.seedUri : depth0),{"name":"type","hash":{},"data":data}));
  },"11":function(container,depth0,helpers,partials,data) {
      return "      card-show-attention-highlight\n";
  },"13":function(container,depth0,helpers,partials,data) {
      return "no-image-url";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3=container.escapeExpression, alias4="function";
  
    return "<div\n  class=\"\n    card\n    card-type-"
      + alias3((helpers.type || (depth0 && depth0.type) || alias2).call(alias1,(depth0 != null ? depth0.uri : depth0),{"name":"type","hash":{},"data":data}))
      + "\n"
      + ((stack1 = (helpers.compare || (depth0 && depth0.compare) || alias2).call(alias1,(depth0 != null ? depth0.isVideo : depth0),true,{"name":"compare","hash":{},"fn":container.program(1, data, 0),"inverse":container.program(3, data, 0),"data":data})) != null ? stack1 : "")
      + "    "
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.seedUri : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showAttentionHighlight : depth0),{"name":"if","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "    "
      + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.imageUrl : depth0),{"name":"unless","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n  \"\n  data-uri=\""
      + alias3(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : alias2),(typeof helper === alias4 ? helper.call(alias1,{"name":"uri","hash":{},"data":data}) : helper)))
      + "\"\n  data-contextmenu\n  data-drag-text=\""
      + alias3(((helper = (helper = helpers.dragText || (depth0 != null ? depth0.dragText : depth0)) != null ? helper : alias2),(typeof helper === alias4 ? helper.call(alias1,{"name":"dragText","hash":{},"data":data}) : helper)))
      + "\"\n  draggable=\"true\"\n  data-ta-id=\"card\"\n>\n  <div class=\"card-attention-highlight-box\">\n  </div>\n  <div class=\"card-image-wrapper\" data-contextmenu-spy data-ta-id=\"card-image-wrapper\">\n"
      + ((stack1 = container.invokePartial(partials["./internal/image-with-overlay.hbs"],depth0,{"name":"./internal/image-with-overlay.hbs","data":data,"indent":"    ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "  </div>\n</div>\n";
  },"usePartial":true,"useData":true});
  
  },{"./internal/image-with-overlay.hbs":93,"hbsfy/runtime":368}],90:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('./internal/image-with-overlay.hbs');
  HandlebarsCompiler.registerPartial('./internal/image-with-overlay.hbs', partial$0);
  var partial$1 = require('./internal/controls.hbs');
  HandlebarsCompiler.registerPartial('./internal/controls.hbs', partial$1);
  var partial$2 = require('./internal/linked-image.hbs');
  HandlebarsCompiler.registerPartial('./internal/linked-image.hbs', partial$2);
  var partial$3 = require('./internal/controls.hbs');
  HandlebarsCompiler.registerPartial('./internal/controls.hbs', partial$3);
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      return "      card-video\n";
  },"3":function(container,depth0,helpers,partials,data) {
      return "card-type-station-"
      + container.escapeExpression((helpers.type || (depth0 && depth0.type) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.seedUri : depth0),{"name":"type","hash":{},"data":data}));
  },"5":function(container,depth0,helpers,partials,data) {
      return "      card-show-attention-highlight\n";
  },"7":function(container,depth0,helpers,partials,data) {
      return "added";
  },"9":function(container,depth0,helpers,partials,data) {
      return "no-image-url";
  },"11":function(container,depth0,helpers,partials,data) {
      return "data-context";
  },"13":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "      <div class=\"card-image-hit-area\" >\n"
      + ((stack1 = container.invokePartial(partials["./internal/image-with-overlay.hbs"],depth0,{"name":"./internal/image-with-overlay.hbs","data":data,"indent":"        ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "\n"
      + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.noPlayButton : depth0),{"name":"unless","hash":{},"fn":container.program(14, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "      </div>\n";
  },"14":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = container.invokePartial(partials["./internal/controls.hbs"],depth0,{"name":"./internal/controls.hbs","data":data,"indent":"          ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
  },"16":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "      <div class=\"card-image-hit-area\" data-card-trigger-pressed-state>\n"
      + ((stack1 = container.invokePartial(partials["./internal/linked-image.hbs"],depth0,{"name":"./internal/linked-image.hbs","data":data,"indent":"        ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "\n"
      + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.noPlayButton : depth0),{"name":"unless","hash":{},"fn":container.program(14, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "      </div>\n";
  },"18":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "      <div class=\"card-progress\">\n        <div class=\"card-progress__bar\" style=\"--progress: "
      + container.escapeExpression(((helper = (helper = helpers.progress || (depth0 != null ? depth0.progress : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"progress","hash":{},"data":data}) : helper)))
      + "\"></div>\n      </div>\n";
  },"20":function(container,depth0,helpers,partials,data,blockParams,depths) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});

    return "  <div class=\"card-info-wrapper card-info-with-description card-info-with-metadata\">\n"
      + "<div class=\"card-info-title\">\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.noLink : depth0),{"name":"if","hash":{},"fn":container.program(28, data, 0, blockParams, depths),"inverse":container.program(30, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
      + "    </div>\n\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.subtitleLinks : depth0),{"name":"if","hash":{},"fn":container.program(35, data, 0, blockParams, depths),"inverse":container.program(46, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
      + "\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.metadata : depth0),{"name":"if","hash":{},"fn":container.program(49, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "  </div>\n";
  },"23":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.description : depth0),{"name":"if","hash":{},"fn":container.program(24, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"24":function(container,depth0,helpers,partials,data) {
      return "          card-info-with-description\n";
  },"26":function(container,depth0,helpers,partials,data) {
      return "        card-info-with-metadata\n";
  },"28":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "        <span>\n          "
      + container.escapeExpression(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"title","hash":{},"data":data}) : helper)))
      + "\n        </span>\n";
  },"30":function(container,depth0,helpers,partials,data) {
      var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
  
    return "      <a\n        href=\""
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.targetUri : depth0),{"name":"if","hash":{},"fn":container.program(31, data, 0),"inverse":container.program(33, data, 0),"data":data})) != null ? stack1 : "")
      + "\"\n        data-uri=\""
      + alias4(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"uri","hash":{},"data":data}) : helper)))
      + "\"\n data-ta-id=\"card-title-link\"\n        title=\""
      + alias4(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"title","hash":{},"data":data}) : helper)))
      + "\"\n        data-interaction-target=\"title\"\n      >\n        "
      + alias4(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"title","hash":{},"data":data}) : helper)))
      + "\n      </a>\n";
  },"31":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return container.escapeExpression(((helper = (helper = helpers.targetUri || (depth0 != null ? depth0.targetUri : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"targetUri","hash":{},"data":data}) : helper)));
  },"33":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return container.escapeExpression(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"uri","hash":{},"data":data}) : helper)));
  },"35":function(container,depth0,helpers,partials,data,blockParams,depths) {
      var stack1;
  
    return "    <div class=\"card-info-subtitle-links\" data-interaction-context=\"subtitle-links\">\n"
      + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.subtitleLinks : depth0),{"name":"each","hash":{},"fn":container.program(36, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "    </div>\n";
  },"36":function(container,depth0,helpers,partials,data,blockParams,depths) {
      var stack1;
  
    return ((stack1 = (helpers.compare || (depth0 && depth0.compare) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.uri : depth0),"spotify:artist:0LyfQWJT6nXafLPZqxe9Of",{"name":"compare","hash":{},"fn":container.program(37, data, 0, blockParams, depths),"inverse":container.program(39, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
  },"37":function(container,depth0,helpers,partials,data) {
      return "        "
      + container.escapeExpression((helpers.loc || (depth0 && depth0.loc) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"VariousArtistsCapitalized",{"name":"loc","hash":{},"data":data}))
      + "\n";
  },"39":function(container,depth0,helpers,partials,data,blockParams,depths) {
      var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
  
    return "        <a\n          title=\""
      + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
      + "\"\n          href=\""
      + alias4(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"uri","hash":{},"data":data}) : helper)))
      + "\"\n          data-uri=\""
      + alias4(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"uri","hash":{},"data":data}) : helper)))
      + "\"\n          data-ta-id=\"card-subtitle-link\"\n          data-interaction-target=\""
      + alias4(((helper = (helper = helpers.index || (data && data.index)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"index","hash":{},"data":data}) : helper)))
      + "\"\n"
      + ((stack1 = (helpers.compare || (depth0 && depth0.compare) || alias2).call(alias1,(helpers.type || (depth0 && depth0.type) || alias2).call(alias1,(depths[1] != null ? depths[1].uri : depths[1]),{"name":"type","hash":{},"data":data}),"album",{"name":"compare","hash":{"operator":"=="},"fn":container.program(40, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + ((stack1 = (helpers.compare || (depth0 && depth0.compare) || alias2).call(alias1,(helpers.type || (depth0 && depth0.type) || alias2).call(alias1,(depths[1] != null ? depths[1].uri : depths[1]),{"name":"type","hash":{},"data":data}),"playlist",{"name":"compare","hash":{"operator":"=="},"fn":container.program(42, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "        >"
      + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
      + "</a>"
      + ((stack1 = helpers.unless.call(alias1,(data && data.last),{"name":"unless","hash":{},"fn":container.program(44, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n";
  },"40":function(container,depth0,helpers,partials,data) {
      return "";
  },"42":function(container,depth0,helpers,partials,data) {
      return "";
  },"44":function(container,depth0,helpers,partials,data) {
      return ", ";
  },"46":function(container,depth0,helpers,partials,data) {
      var stack1;

    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.description : depth0),{"name":"if","hash":{},"fn":container.program(47, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"47":function(container,depth0,helpers,partials,data) {
      var helper;

    return "      <div class=\"card-info-subtitle-description\" data-ta-id=\"card-description\">\n        "
      + container.escapeExpression(((helper = (helper = helpers.description || (depth0 != null ? depth0.description : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"description","hash":{},"data":data}) : helper)))
      + "\n      </div>\n";
  },"49":function(container,depth0,helpers,partials,data) {
      var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {});
  
    return "    <div class=\"card-info-subtitle-metadata\" data-ta-id=\"card-metadata\">\n"
      + container.escapeExpression(((helper = (helper = helpers.metadata || (depth0 != null ? depth0.type : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"metadata","hash":{},"data":data}) : helper)))
      + "\n    </div>\n"
      + "    <div class=\"card-info-subtitle-metadata\" data-ta-id=\"card-metadata\">\n"
      + container.escapeExpression(((helper = (helper = helpers.metadata || (depth0 != null ? depth0.metadata : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"metadata","hash":{},"data":data}) : helper)))
      + "\n    </div>\n";
  },"50":function(container,depth0,helpers,partials,data) {
      return "      <div class=\"card-info-subtitle-new-mark\"></div>\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
      var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3=container.escapeExpression, alias4="function", buffer = 
    "<div\n  class=\"\n    card\n    card-type-"
      + alias3((helpers.type || (depth0 && depth0.type) || alias2).call(alias1,(depth0 != null ? depth0.uri : depth0),{"name":"type","hash":{},"data":data}))
      + "\n"
      + ((stack1 = (helpers.compare || (depth0 && depth0.compare) || alias2).call(alias1,(depth0 != null ? depth0.isVideo : depth0),true,{"name":"compare","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "    "
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.seedUri : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showAttentionHighlight : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "    "
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.added : depth0),{"name":"if","hash":{},"fn":container.program(7, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n    "
      + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.imageUrl : depth0),{"name":"unless","hash":{},"fn":container.program(9, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n  \"\n data-uri=\""
      + alias3(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : alias2),(typeof helper === alias4 ? helper.call(alias1,{"name":"uri","hash":{},"data":data}) : helper)))
      + "\"\n  data-contextmenu\n  ";
    stack1 = ((helper = (helper = helpers.noContext || (depth0 != null ? depth0.noContext : depth0)) != null ? helper : alias2),(options={"name":"noContext","hash":{},"fn":container.noop,"inverse":container.program(11, data, 0, blockParams, depths),"data":data}),(typeof helper === alias4 ? helper.call(alias1,options) : helper));
    if (!helpers.noContext) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
    if (stack1 != null) { buffer += stack1; }
    return buffer + "\n  data-card-pressed-container\n  data-ta-id=\"card\"\n>\n  <div class=\"card-attention-highlight-box\">\n  </div>\n  <div class=\"card-image-wrapper\" data-contextmenu-spy data-ta-id=\"card-image-wrapper\">\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.noLink : depth0),{"name":"if","hash":{},"fn":container.program(13, data, 0, blockParams, depths),"inverse":container.program(16, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.progress : depth0),{"name":"if","hash":{},"fn":container.program(18, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "  </div>\n\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.title : depth0),{"name":"if","hash":{},"fn":container.program(20, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n</div>\n";
  },"usePartial":true,"useData":true,"useDepths":true});
  
  },{"./internal/controls.hbs":92,"./internal/image-with-overlay.hbs":93,"./internal/linked-image.hbs":95,"hbsfy/runtime":368}],91:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "  <button\n    type=\"button\"\n    class=\"button button-play button-icon-with-stroke card-button-play\"\n    data-ta-id=\"card-button-play\"\n  data-interaction-target=\"play-pause-button\"\n    data-interaction-intent=\"play\"\n"
      + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.range : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.program(5, data, 0),"data":data})) != null ? stack1 : "")
      + "  >\n  </button>\n";
  },"2":function(container,depth0,helpers,partials,data) {
      var stack1, helper, options, buffer = 
    "      data-button=\"play\"\n      ";
    stack1 = ((helper = (helper = helpers.range || (depth0 != null ? depth0.range : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"range","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
    if (!helpers.range) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
    if (stack1 != null) { buffer += stack1; }
    return buffer + "\n";
  },"3":function(container,depth0,helpers,partials,data) {
      var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
  
    return "data-range=\""
      + alias4(((helper = (helper = helpers.start || (depth0 != null ? depth0.start : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"start","hash":{},"data":data}) : helper)))
      + ","
      + alias4(((helper = (helper = helpers.end || (depth0 != null ? depth0.end : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"end","hash":{},"data":data}) : helper)))
      + "\"";
  },"5":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.contextPage : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.program(9, data, 0),"data":data})) != null ? stack1 : "");
  },"6":function(container,depth0,helpers,partials,data) {
      var stack1, helper, options, buffer = 
    "        data-button=\"play\"\n        ";
    stack1 = ((helper = (helper = helpers.contextPage || (depth0 != null ? depth0.contextPage : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"contextPage","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
    if (!helpers.contextPage) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
    if (stack1 != null) { buffer += stack1; }
    return buffer + "\n";
  },"7":function(container,depth0,helpers,partials,data) {
      return "data-context-page=\""
      + container.escapeExpression(container.lambda(depth0, depth0))
      + "\"";
  },"9":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.trackUri : depth0),{"name":"if","hash":{},"fn":container.program(10, data, 0),"inverse":container.program(13, data, 0),"data":data})) != null ? stack1 : "");
  },"10":function(container,depth0,helpers,partials,data) {
      var stack1, helper, options, buffer = 
    "          ";
    stack1 = ((helper = (helper = helpers.trackUri || (depth0 != null ? depth0.trackUri : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"trackUri","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
    if (!helpers.trackUri) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
    if (stack1 != null) { buffer += stack1; }
    return buffer + "\n          data-button=\"play\"\n";
  },"11":function(container,depth0,helpers,partials,data) {
      return "data-track-uri=\""
      + container.escapeExpression(container.lambda(depth0, depth0))
      + "\"";
  },"13":function(container,depth0,helpers,partials,data) {
      return "          data-button=\"play-context\"\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = (helpers.type || (depth0 && depth0.type) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.uri : depth0),"playlist","track","album","station","artist","episode","collection-album","collection-artist","show",{"name":"type","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"useData":true});
  
  },{"hbsfy/runtime":368}],92:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('./controls-play-button.hbs');
  HandlebarsCompiler.registerPartial('./controls-play-button.hbs', partial$0);
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = (helpers.type || (depth0 && depth0.type) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.uri : depth0),"playlist","track","album","station","artist","show","collection-album","collection-artist",{"name":"type","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"2":function(container,depth0,helpers,partials,data) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing;
  
    return ((stack1 = (helpers.type || (depth0 && depth0.type) || alias2).call(alias1,(depth0 != null ? depth0.uri : depth0),"track","album","collection-album",{"name":"type","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + ((stack1 = (helpers.type || (depth0 && depth0.type) || alias2).call(alias1,(depth0 != null ? depth0.uri : depth0),"playlist","artist","show","collection-artist",{"name":"type","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + ((stack1 = (helpers.type || (depth0 && depth0.type) || alias2).call(alias1,(depth0 != null ? depth0.uri : depth0),"station",{"name":"type","hash":{},"fn":container.program(8, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"3":function(container,depth0,helpers,partials,data) {
      var alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3=container.escapeExpression;
  
    return "      <button\n        type=\"button\"\n        data-button=\"add\"\n        class=\"button button-add card-button-add\"\n        data-ta-id=\"card-button-add\"\n        data-tooltip=\""
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"Save",{"name":"loc","hash":{},"data":data}))
      + "\"\n        data-tooltip-add=\""
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"Save",{"name":"loc","hash":{},"data":data}))
      + "\"\n        data-tooltip-remove=\""
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"Remove",{"name":"loc","hash":{},"data":data}))
      + "\"\n  data-interaction-target=\"save-remove-button\"\n        data-interaction-intent=\"save\"></button>\n";
  },"5":function(container,depth0,helpers,partials,data) {
      var stack1, helper, options, buffer = "";
  
    stack1 = ((helper = (helper = helpers.currentUserPlaylist || (depth0 != null ? depth0.currentUserPlaylist : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"currentUserPlaylist","hash":{},"fn":container.noop,"inverse":container.program(6, data, 0),"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
    if (!helpers.currentUserPlaylist) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
    if (stack1 != null) { buffer += stack1; }
    return buffer;
  },"6":function(container,depth0,helpers,partials,data) {
      var alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3=container.escapeExpression;
  
    return "      <button\n        type=\"button\"\n        data-button=\"add\"\n        class=\"button button-add card-button-add\"\n        data-ta-id=\"card-button-add\"\n        data-tooltip=\""
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"Follow",{"name":"loc","hash":{},"data":data}))
      + "\"\n        data-tooltip-add=\""
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"Follow",{"name":"loc","hash":{},"data":data}))
      + "\"\n        data-tooltip-remove=\""
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"Unfollow",{"name":"loc","hash":{},"data":data}))
      + "\"\n data-interaction-target=\"toggle-follow-state-button\"\n        data-interaction-intent=\"follow\"></button>\n";
  },"8":function(container,depth0,helpers,partials,data) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing;
  
    return ((stack1 = (helpers.compare || (depth0 && depth0.compare) || alias2).call(alias1,(helpers.type || (depth0 && depth0.type) || alias2).call(alias1,(depth0 != null ? depth0.seedUri : depth0),{"name":"type","hash":{},"data":data}),"cluster",{"name":"compare","hash":{"operator":"!="},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"10":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = (helpers.type || (depth0 && depth0.type) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.uri : depth0),"playlist","track","album","station","artist","collection-album","collection-artist",{"name":"type","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"11":function(container,depth0,helpers,partials,data) {
      return "    <button\n      type=\"button\"\n      data-button=\"contextmenu\"\n      class=\"button button-more button-no-border card-button-more\"\n      data-ta-id=\"card-button-context-menu\"\n      data-tooltip=\""
      + container.escapeExpression((helpers.loc || (depth0 && depth0.loc) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"More",{"name":"loc","hash":{},"data":data}))
      + "\"\n data-interaction-target=\"context-menu-button\"\n      data-interaction-intent=\"show-options\"></button>\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
  
    return ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.playButtonOnly : depth0),{"name":"unless","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n"
      + ((stack1 = container.invokePartial(partials["./controls-play-button.hbs"],depth0,{"name":"./controls-play-button.hbs","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "\n"
      + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.playButtonOnly : depth0),{"name":"unless","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"usePartial":true,"useData":true});
  
  },{"./controls-play-button.hbs":91,"hbsfy/runtime":368}],93:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('./image.hbs');
  HandlebarsCompiler.registerPartial('./image.hbs', partial$0);
  module.exports = HandlebarsCompiler.template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = container.invokePartial(partials["./image.hbs"],depth0,{"name":"./image.hbs","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
  },"usePartial":true,"useData":true});
  
  },{"./image.hbs":94,"hbsfy/runtime":368}],94:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "style=\"background-image: url('"
      + container.escapeExpression(((helper = (helper = helpers.imageUrl || (depth0 != null ? depth0.imageUrl : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"imageUrl","hash":{},"data":data}) : helper)))
      + "')\"";
  },"3":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "      data-image-url=\""
      + container.escapeExpression(((helper = (helper = helpers.imageUrl || (depth0 != null ? depth0.imageUrl : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"imageUrl","hash":{},"data":data}) : helper)))
      + "\"\n";
  },"5":function(container,depth0,helpers,partials,data) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing;
  
    return ((stack1 = (helpers.compare || (depth0 && depth0.compare) || alias2).call(alias1,(helpers.type || (depth0 && depth0.type) || alias2).call(alias1,(depth0 != null ? depth0.seedUri : depth0),{"name":"type","hash":{},"data":data}),"cluster",{"name":"compare","hash":{"operator":"!="},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"6":function(container,depth0,helpers,partials,data) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
  
    return ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.dominantColor : depth0),{"name":"if","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "      <div class=\"card-station-layer card-station-mask\"></div>\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.imageUrl : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"7":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "        <div class=\"card-station-layer card-station-highlight\" style=\"background-color:"
      + container.escapeExpression(((helper = (helper = helpers.dominantColor || (depth0 != null ? depth0.dominantColor : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"dominantColor","hash":{},"data":data}) : helper)))
      + "\"></div>\n";
  },"9":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "        <div class=\"card-station-overlay\" style=\"background-image: url("
      + container.escapeExpression(((helper = (helper = helpers.imageUrl || (depth0 != null ? depth0.imageUrl : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"imageUrl","hash":{},"data":data}) : helper)))
      + ")\"></div>\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
  
    return "\n<div class=\"card-image-content-wrapper\">\n  <div\n    class=\"card-image\"\n    "
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.imageUrl : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n    data-ta-id=\"card-image\"\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.imageUrl : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "  >\n  </div>\n\n"
      + ((stack1 = (helpers.type || (depth0 && depth0.type) || helpers.helperMissing).call(alias1,(depth0 != null ? depth0.uri : depth0),"station",{"name":"type","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "</div>\n";
  },"usePartial":true,"useData":true});
  
  },{"hbsfy/runtime":368}],95:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('./image-with-overlay.hbs');
  HandlebarsCompiler.registerPartial('./image-with-overlay.hbs', partial$0);
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return container.escapeExpression(((helper = (helper = helpers.dragText || (depth0 != null ? depth0.dragText : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"dragText","hash":{},"data":data}) : helper)));
  },"3":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return container.escapeExpression(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"title","hash":{},"data":data}) : helper)));
  },"5":function(container,depth0,helpers,partials,data) {
      return "no-image-url";
  },"6":function(container,depth0,helpers,partials,data) {
    return "data-html=\"" + depth0.html + "\"";
  },"7":function(container,depth0,helpers,partials,data) {
    var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
    if (depth0 != null && depth0.uri == "youtube") {
      return ""
    }
    return "href=\""
    + alias4(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"uri","hash":{},"data":data}) : helper)))
    +"\"";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
  
    return "<a\n "
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.uri : depth0),{"name":"if","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n  data-uri=\""
      + alias4(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"uri","hash":{},"data":data}) : helper)))
      + "\"\n draggable=\"true\"\n  data-drag-text=\""
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.dragText : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.program(3, data, 0),"data":data})) != null ? stack1 : "")
      + "\"\n  class=\"card-image-link "
      + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.imageUrl : depth0),{"name":"unless","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\"\n "
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.html : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\"\n  data-interaction-target=\"image\"\n  aria-hidden=\"true\"\n>\n  <div class=\"card-hit-area-counter-scale\"></div>\n"
      + ((stack1 = container.invokePartial(partials["./image-with-overlay.hbs"],depth0,{"name":"./image-with-overlay.hbs","data":data,"indent":"  ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "</a>\n";
  },"usePartial":true,"useData":true});
  
  },{"./image-with-overlay.hbs":93,"hbsfy/runtime":368}],97:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing;
  
    return "        <h1 class=\"hdr-l\">"
      + container.escapeExpression((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"ErrorTitle",{"name":"loc","hash":{},"data":data}))
      + "</h1>\n        <div class=\"message\">"
      + ((stack1 = (helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"ErrorMessage",{"name":"loc","hash":{},"data":data})) != null ? stack1 : "")
      + "</div>\n";
  },"3":function(container,depth0,helpers,partials,data) {
      var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function";
  
    return "        <h1 class=\"hdr-l\">"
      + container.escapeExpression(((helper = (helper = helpers.errorTitle || (depth0 != null ? depth0.errorTitle : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"errorTitle","hash":{},"data":data}) : helper)))
      + "</h1>\n        <div class=\"message\">"
      + ((stack1 = ((helper = (helper = helpers.errorMessage || (depth0 != null ? depth0.errorMessage : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"errorMessage","hash":{},"data":data}) : helper))) != null ? stack1 : "")
      + "</div>\n";
  },"5":function(container,depth0,helpers,partials,data) {
      var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
  
    return "        <a class=\"button button-white\" href=\""
      + alias4(((helper = (helper = helpers.errorEscapeUrl || (depth0 != null ? depth0.errorEscapeUrl : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"errorEscapeUrl","hash":{},"data":data}) : helper)))
      + "\">"
      + alias4(((helper = (helper = helpers.errorEscapeMessage || (depth0 != null ? depth0.errorEscapeMessage : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"errorEscapeMessage","hash":{},"data":data}) : helper)))
      + "</a>\n";
  },"7":function(container,depth0,helpers,partials,data) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing;
  
    return "        <h1 class=\"hdr-l\">"
      + container.escapeExpression((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"OfflineTitle",{"name":"loc","hash":{},"data":data}))
      + "</h1>\n        <div class=\"message\">"
      + ((stack1 = (helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"OfflineMessage",{"name":"loc","hash":{},"data":data})) != null ? stack1 : "")
      + "</div>\n";
  },"9":function(container,depth0,helpers,partials,data) {
      var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function";
  
    return "        <h1 class=\"hdr-l\">"
      + container.escapeExpression(((helper = (helper = helpers.offlineTitle || (depth0 != null ? depth0.offlineTitle : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"offlineTitle","hash":{},"data":data}) : helper)))
      + "</h1>\n        <div class=\"message\">"
      + ((stack1 = ((helper = (helper = helpers.offlineMessage || (depth0 != null ? depth0.offlineMessage : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"offlineMessage","hash":{},"data":data}) : helper))) != null ? stack1 : "")
      + "</div>\n";
  },"11":function(container,depth0,helpers,partials,data) {
      var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
  
    return "        <a class=\"button button-white\" href=\""
      + alias4(((helper = (helper = helpers.offlineEscapeUrl || (depth0 != null ? depth0.offlineEscapeUrl : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"offlineEscapeUrl","hash":{},"data":data}) : helper)))
      + "\">"
      + alias4(((helper = (helper = helpers.offlineEscapeMessage || (depth0 != null ? depth0.offlineEscapeMessage : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"offlineEscapeMessage","hash":{},"data":data}) : helper)))
      + "</a>\n";
  },"13":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "  <div class=\"error-version text-muted\">"
      + container.escapeExpression(((helper = (helper = helpers.errorVersion || (depth0 != null ? depth0.errorVersion : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"errorVersion","hash":{},"data":data}) : helper)))
      + "</div>\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
  
    return "<div class=\"error-and-offline\" data-log-context=\"error-and-offline\">\n  <div class=\"container\">\n    <div class=\"error-content\">\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.useDefaultError : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.program(3, data, 0),"data":data})) != null ? stack1 : "")
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.errorEscapeMessage : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "    </div>\n    <div class=\"offline-content\">\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.useDefaultOffline : depth0),{"name":"if","hash":{},"fn":container.program(7, data, 0),"inverse":container.program(9, data, 0),"data":data})) != null ? stack1 : "")
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.offlineEscapeMessage : depth0),{"name":"if","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "    </div>\n  </div>\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.errorVersion : depth0),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "</div>\n";
  },"useData":true});
  
  },{"hbsfy/runtime":368}],98:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('./cards/card.hbs');
  HandlebarsCompiler.registerPartial('./cards/card.hbs', partial$0);
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var stack1, helper;
  
    return "  <div class=\"facepile\" data-tooltip title=\""
      + container.escapeExpression(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"name","hash":{},"data":data}) : helper)))
      + "\" data-tooltip-instant>\n"
      + ((stack1 = container.invokePartial(partials["./cards/card.hbs"],depth0,{"name":"./cards/card.hbs","data":data,"indent":"    ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "  </div>\n";
  },"3":function(container,depth0,helpers,partials,data,blockParams,depths) {
      var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
  
    return "  <button\n    type=\"button\"\n    class=\"button facepile-badge\"\n    title=\""
      + alias4(((helper = (helper = helpers.badgeTooltip || (depth0 != null ? depth0.badgeTooltip : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"badgeTooltip","hash":{},"data":data}) : helper)))
      + "\"\n    data-tooltip\n    data-tooltip-instant\n    data-modal=\"spotify:modal:socialproof:"
      + alias4(container.lambda((depths[1] != null ? depths[1].uri : depths[1]), depth0))
      + "\"\n  >\n    +"
      + alias4(((helper = (helper = helpers.badgeCount || (depth0 != null ? depth0.badgeCount : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"badgeCount","hash":{},"data":data}) : helper)))
      + "\n  </button>\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
      var stack1;
  
    return ((stack1 = (helpers.facepile || (depth0 && depth0.facepile) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.users : depth0),{"name":"facepile","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.program(3, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
  },"usePartial":true,"useData":true,"useDepths":true});
  
  },{"./cards/card.hbs":90,"hbsfy/runtime":368}],99:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('./facepile-common.hbs');
  HandlebarsCompiler.registerPartial('./facepile-common.hbs', partial$0);
  module.exports = HandlebarsCompiler.template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "<div class=\"facepile-list\">\n"
      + ((stack1 = container.invokePartial(partials["./facepile-common.hbs"],depth0,{"name":"./facepile-common.hbs","data":data,"indent":"\t","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "</div>\n";
  },"usePartial":true,"useData":true});
  
  },{"./facepile-common.hbs":98,"hbsfy/runtime":368}],100:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('../../../buttons/add-interior.hbs');
  HandlebarsCompiler.registerPartial('../../../buttons/add-interior.hbs', partial$0);
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      return "added";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {});
  
    return "<button\n  class=\"button button-with-stroke button-add "
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isAdded : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\"\n  data-button=\"add\"\n  data-uri=\""
      + container.escapeExpression(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"uri","hash":{},"data":data}) : helper)))
      + "\"\n data-ta-id=\"page-header-button-add\"\n  data-interaction-target=\"add-remove-button\"\n  data-interaction-intent=\"add\"\n>\n"
      + ((stack1 = container.invokePartial(partials["../../../buttons/add-interior.hbs"],depth0,{"name":"../../../buttons/add-interior.hbs","data":data,"indent":"  ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "</button>";
  },"usePartial":true,"useData":true});
  
  },{"../../../buttons/add-interior.hbs":87,"hbsfy/runtime":368}],101:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  module.exports = HandlebarsCompiler.template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3=container.escapeExpression;
  
    return "<button\n  class=\"button button-icon-with-stroke spoticon-more-16\"\n  data-button=\"contextmenu\"\n  data-tooltip=\""
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"More",{"name":"loc","hash":{},"data":data}))
      + "\"\n  data-ta-id=\"page-header-button-more\"\n  aria-label=\""
      + alias3((helpers.loc || (depth0 && depth0.loc) || alias2).call(alias1,"More",{"name":"loc","hash":{},"data":data}))
      + "\"\n  data-interaction-target=\"context-menu-button\"\n  data-interaction-intent=\"show-options\"\n>\n</button>\n";
  },"useData":true});
  
  },{"hbsfy/runtime":368}],102:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('../../../buttons/play-interior.hbs');
  HandlebarsCompiler.registerPartial('../../../buttons/play-interior.hbs', partial$0);
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "    data-uri=\""
      + container.escapeExpression(((helper = (helper = helpers.playContextUri || (depth0 != null ? depth0.playContextUri : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"playContextUri","hash":{},"data":data}) : helper)))
      + "\"\n    data-context\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "<button\n  class=\"button button-green button-play\"\n  data-button=\"play-context\"\n  data-ta-id=\"page-header-button-play\"\n"
      + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.playContextUri : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "  data-interaction-target=\"play-pause-button\"\n>\n"
      + ((stack1 = container.invokePartial(partials["../../../buttons/play-interior.hbs"],depth0,{"name":"../../../buttons/play-interior.hbs","data":data,"indent":"  ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "</button>";
  },"usePartial":true,"useData":true});
  
  },{"../../../buttons/play-interior.hbs":88,"hbsfy/runtime":368}],103:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
  
    return "    <div\n      class=\"\n        glue-page-header__background-image-loader\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isLoaded : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "      \"\n      data-glue-page-header-background-image-loader\n    >\n      <div\n        class=\"glue-page-header__background-image\"\n"
      + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.background : depth0)) != null ? stack1.imageUrl : stack1),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "        data-glue-page-header-background-image\n        data-glue-page-header-background-image-url=\""
      + container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.background : depth0)) != null ? stack1.imageUrl : stack1), depth0))
      + "\"\n        data-ta-id=\"page-header-background-image\"\n      >\n      </div>\n    </div>\n    <div class=\"glue-page-header__background-overlay\">\n    </div>\n    <div class=\"glue-page-header__background-image-overlay-gradient\">\n    </div>\n    <div\n      class=\"glue-page-header__background-scroll-overlay-ellipse-backdrop\"\n      data-glue-page-header-background-scroll-overlay-ellipse-backdrop\n    >\n    </div>\n    <div\n      class=\"glue-page-header__background-scroll-overlay-ellipse\"\n      data-glue-page-header-background-scroll-overlay-ellipse\n    >\n    </div>\n";
  },"2":function(container,depth0,helpers,partials,data) {
      return "          glue-page-header__background-image-loader--is-loaded\n";
  },"4":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "          style=\"background-image: url("
      + container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.background : depth0)) != null ? stack1.imageUrl : stack1), depth0))
      + ")\"\n";
  },"6":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "    <div\n      class=\"glue-page-header__background-color\"\n"
      + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.background : depth0)) != null ? stack1.color : stack1),{"name":"if","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "    >\n    </div>\n    <div class=\"glue-page-header__background-overlay\">\n    </div>\n    <div\n      class=\"glue-page-header__background-scroll-overlay\"\n      data-glue-page-header-background-scroll-overlay\n    >\n    </div>\n";
  },"7":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "        style=\"background-color: "
      + container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.background : depth0)) != null ? stack1.color : stack1), depth0))
      + "\"\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "<div\n  class=\"glue-page-header__background\"\n  data-glue-page-header-background\n>\n"
      + ((stack1 = (helpers.compare || (depth0 && depth0.compare) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.background : depth0)) != null ? stack1.type : stack1),"image",{"name":"compare","hash":{},"fn":container.program(1, data, 0),"inverse":container.program(6, data, 0),"data":data})) != null ? stack1 : "")
      + "</div>\n";
  },"useData":true});
  
  },{"hbsfy/runtime":368}],104:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('../generic/button-play.hbs');
  HandlebarsCompiler.registerPartial('../generic/button-play.hbs', partial$0);
  var partial$1 = require('../generic/button-add.hbs');
  HandlebarsCompiler.registerPartial('../generic/button-add.hbs', partial$1);
  var partial$2 = require('../generic/button-more.hbs');
  HandlebarsCompiler.registerPartial('../generic/button-more.hbs', partial$2);
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "    <div class=\"glue-page-header__button\">\n"
      + ((stack1 = container.invokePartial(partials["../generic/button-play.hbs"],depth0,{"name":"../generic/button-play.hbs","data":data,"indent":"      ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "    </div>\n";
  },"3":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "    <div class=\"glue-page-header__button\">\n"
      + ((stack1 = container.invokePartial(partials["../generic/button-add.hbs"],depth0,{"name":"../generic/button-add.hbs","data":data,"indent":"      ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "    </div>\n";
  },"5":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "    <div class=\"glue-page-header__button\">\n"
      + ((stack1 = container.invokePartial(partials["../generic/button-more.hbs"],depth0,{"name":"../generic/button-more.hbs","data":data,"indent":"      ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "    </div>\n";
  },"7":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.buttons : depth0)) != null ? stack1.customButtons : stack1),{"name":"each","hash":{},"fn":container.program(8, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"8":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "      <div class=\"glue-page-header__button\">\n        "
      + ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "")
      + "\n      </div>\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
  
    return "<div class=\"glue-page-header__buttons\">\n"
      + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.buttons : depth0)) != null ? stack1.playButton : stack1),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n"
      + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.buttons : depth0)) != null ? stack1.addButton : stack1),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n"
      + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.buttons : depth0)) != null ? stack1.moreButton : stack1),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n"
      + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.buttons : depth0)) != null ? stack1.customButtons : stack1),{"name":"if","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "</div>";
  },"usePartial":true,"useData":true});
  
  },{"../generic/button-add.hbs":100,"../generic/button-more.hbs":101,"../generic/button-play.hbs":102,"hbsfy/runtime":368}],105:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('../../../facepile.hbs');
  HandlebarsCompiler.registerPartial('../../../facepile.hbs', partial$0);
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.escapeExpression;
  
    return "  <div class=\"glue-page-header__count-label\">\n"
      + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.countLabel : depth0)) != null ? stack1.link : stack1),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "      <div class=\"glue-page-header__count-label-text\">\n        "
      + alias2(container.lambda(((stack1 = (depth0 != null ? depth0.countLabel : depth0)) != null ? stack1.label : stack1), depth0))
      + "\n      </div>\n      <div class=\"glue-page-header__count-label-number\">\n        "
      + alias2((helpers.numeral || (depth0 && depth0.numeral) || helpers.helperMissing).call(alias1,((stack1 = (depth0 != null ? depth0.countLabel : depth0)) != null ? stack1.number : stack1),{"name":"numeral","hash":{},"data":data}))
      + "\n      </div>\n"
      + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.countLabel : depth0)) != null ? stack1.link : stack1),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n"
      + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.countLabel : depth0)) != null ? stack1.facePile : stack1),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "  </div>\n";
  },"2":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "    <a\n      href=\""
      + container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.countLabel : depth0)) != null ? stack1.link : stack1), depth0))
      + "\"\n      class=\"glue-page-header__count-label-link\"\n      data-glue-page-header-count-label-link\n      data-interaction-target=\"monthly-listeners-count\"\n    >\n";
  },"4":function(container,depth0,helpers,partials,data) {
      return "    </a>\n";
  },"6":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "      <div class=\"glue-page-header__count-label-facepile\">\n"
      + ((stack1 = container.invokePartial(partials["../../../facepile.hbs"],((stack1 = (depth0 != null ? depth0.countLabel : depth0)) != null ? stack1.facePile : stack1),{"name":"../../../facepile.hbs","data":data,"indent":"        ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "      </div>\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.countLabel : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"usePartial":true,"useData":true});
  
  },{"../../../facepile.hbs":99,"hbsfy/runtime":368}],106:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var stack1, helper;
  
    return "  <div\n    class=\"glue-page-header__description\"\n    data-ta-id=\"page-header-description\"\n    data-glue-page-header-description\n  >\n    "
      + ((stack1 = ((helper = (helper = helpers.descriptionHTML || (depth0 != null ? depth0.descriptionHTML : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"descriptionHTML","hash":{},"data":data}) : helper))) != null ? stack1 : "")
      + "\n  </div>\n";
  },"3":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.description : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"4":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "    <div\n      class=\"glue-page-header__description\"\n      data-ta-id=\"page-header-description\"\n      data-glue-page-header-description\n    >\n      "
      + container.escapeExpression(((helper = (helper = helpers.description || (depth0 != null ? depth0.description : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"description","hash":{},"data":data}) : helper)))
      + "\n    </div>\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.descriptionHTML : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.program(3, data, 0),"data":data})) != null ? stack1 : "");
  },"useData":true});
  
  },{"hbsfy/runtime":368}],107:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('../../../cards/card-without-link.hbs');
  HandlebarsCompiler.registerPartial('../../../cards/card-without-link.hbs', partial$0);
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var stack1, helper;
  
    return "  <div class=\"glue-page-header__image\">\n    <div class=\"glue-page-header__image-inner\">\n      "
      + ((stack1 = ((helper = (helper = helpers.imageHTML || (depth0 != null ? depth0.imageHTML : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"imageHTML","hash":{},"data":data}) : helper))) != null ? stack1 : "")
      + "\n    </div>\n  </div>\n";
  },"3":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.image : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"4":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "    <div class=\"glue-page-header__image\">\n      <div class=\"glue-page-header__image-inner\">\n"
      + ((stack1 = container.invokePartial(partials["../../../cards/card-without-link.hbs"],(depth0 != null ? depth0.image : depth0),{"name":"../../../cards/card-without-link.hbs","data":data,"indent":"        ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "      </div>\n    </div>\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.imageHTML : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.program(3, data, 0),"data":data})) != null ? stack1 : "");
  },"usePartial":true,"useData":true});
  
  },{"../../../cards/card-without-link.hbs":89,"hbsfy/runtime":368}],108:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var stack1, helper;
  
    return "  <span class=\"glue-page-header__label\" data-ta-id=\"page-header-label\">"
      + ((stack1 = ((helper = (helper = helpers.labelHTML || (depth0 != null ? depth0.labelHTML : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"labelHTML","hash":{},"data":data}) : helper))) != null ? stack1 : "")
      + "</span>\n";
  },"3":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.label : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"4":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "    <span class=\"glue-page-header__label\" data-ta-id=\"page-header-label\">"
      + container.escapeExpression(((helper = (helper = helpers.label || (depth0 != null ? depth0.label : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"label","hash":{},"data":data}) : helper)))
      + "</span>\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.labelHTML : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.program(3, data, 0),"data":data})) != null ? stack1 : "");
  },"useData":true});
  
  },{"hbsfy/runtime":368}],109:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var stack1, helper;
  
    return "  <div\n    class=\"glue-page-header__meta-info\"\n    data-ta-id=\"page-header-meta-info\"\n  >\n    "
      + ((stack1 = ((helper = (helper = helpers.metaInfoHTML || (depth0 != null ? depth0.metaInfoHTML : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"metaInfoHTML","hash":{},"data":data}) : helper))) != null ? stack1 : "")
      + "\n  </div>\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.metaInfoHTML : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"useData":true});
  
  },{"hbsfy/runtime":368}],110:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('../../../navbar.hbs');
  HandlebarsCompiler.registerPartial('../../../navbar.hbs', partial$0);
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "  <div class=\"glue-page-header__navbar-wrapper\">\n"
      + ((stack1 = container.invokePartial(partials["../../../navbar.hbs"],(depth0 != null ? depth0.navBar : depth0),{"name":"../../../navbar.hbs","data":data,"indent":"    ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "  </div>\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.navBar : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"usePartial":true,"useData":true});
  
  },{"../../../navbar.hbs":115,"hbsfy/runtime":368}],111:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      return "data-contextmenu";
  },"3":function(container,depth0,helpers,partials,data) {
      return "draggable=\"true\"";
  },"5":function(container,depth0,helpers,partials,data) {
      return "    data-glue-page-header-use-large-title\n";
  },"7":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "    style=\"color: "
      + container.escapeExpression(((helper = (helper = helpers.titleColor || (depth0 != null ? depth0.titleColor : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"titleColor","hash":{},"data":data}) : helper)))
      + "\"\n";
  },"9":function(container,depth0,helpers,partials,data) {
      var stack1, helper;
  
    return "    "
      + ((stack1 = ((helper = (helper = helpers.titleHTML || (depth0 != null ? depth0.titleHTML : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"titleHTML","hash":{},"data":data}) : helper))) != null ? stack1 : "")
      + "\n";
  },"11":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "    "
      + container.escapeExpression(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"title","hash":{},"data":data}) : helper)))
      + "\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {});

    return "<h1\n  class=\"glue-page-header__title\"\n  data-uri=\""
      + container.escapeExpression(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"uri","hash":{},"data":data}) : helper)))
      + "\"\n  data-ta-id=\"page-header-title\"\n  "
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.enableContextMenu : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n  "
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.enableDragging : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n  data-glue-page-header-title\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.useLargeTitle : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.titleColor : depth0),{"name":"if","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + ">\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.titleHTML : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.program(11, data, 0),"data":data})) != null ? stack1 : "")
      + "</h1>";
  },"useData":true});
  
  },{"hbsfy/runtime":368}],112:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('./internal/page-header/background.hbs');
  HandlebarsCompiler.registerPartial('./internal/page-header/background.hbs', partial$0);
  var partial$1 = require('./internal/page-header/image.hbs');
  HandlebarsCompiler.registerPartial('./internal/page-header/image.hbs', partial$1);
  var partial$2 = require('./internal/page-header/label.hbs');
  HandlebarsCompiler.registerPartial('./internal/page-header/label.hbs', partial$2);
  var partial$3 = require('./internal/page-header/title.hbs');
  HandlebarsCompiler.registerPartial('./internal/page-header/title.hbs', partial$3);
  var partial$4 = require('./internal/page-header/description.hbs');
  HandlebarsCompiler.registerPartial('./internal/page-header/description.hbs', partial$4);
  var partial$5 = require('./internal/page-header/meta-info.hbs');
  HandlebarsCompiler.registerPartial('./internal/page-header/meta-info.hbs', partial$5);
  var partial$6 = require('./internal/page-header/buttons.hbs');
  HandlebarsCompiler.registerPartial('./internal/page-header/buttons.hbs', partial$6);
  var partial$7 = require('./internal/page-header/count-label.hbs');
  HandlebarsCompiler.registerPartial('./internal/page-header/count-label.hbs', partial$7);
  var partial$8 = require('./internal/page-header/buttons.hbs');
  HandlebarsCompiler.registerPartial('./internal/page-header/buttons.hbs', partial$8);
  var partial$9 = require('./internal/page-header/image.hbs');
  HandlebarsCompiler.registerPartial('./internal/page-header/image.hbs', partial$9);
  var partial$10 = require('./internal/page-header/title.hbs');
  HandlebarsCompiler.registerPartial('./internal/page-header/title.hbs', partial$10);
  var partial$11 = require('./internal/page-header/buttons.hbs');
  HandlebarsCompiler.registerPartial('./internal/page-header/buttons.hbs', partial$11);
  var partial$12 = require('./internal/page-header/navbar.hbs');
  HandlebarsCompiler.registerPartial('./internal/page-header/navbar.hbs', partial$12);
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      return "      glue-page-header--with-navbar\n";
  },"3":function(container,depth0,helpers,partials,data) {
      return "      glue-page-header--with-count-label\n";
  },"5":function(container,depth0,helpers,partials,data) {
      return "      glue-page-header--with-responsive-height-small\n";
  },"7":function(container,depth0,helpers,partials,data) {
      return "      glue-page-header--with-responsive-height-large\n";
  },"9":function(container,depth0,helpers,partials,data) {
      return "      glue-page-header--"
      + container.escapeExpression((helpers.type || (depth0 && depth0.type) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.uri : depth0),{"name":"type","hash":{},"data":data}))
      + "\n";
  },"11":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "    data-uri=\""
      + container.escapeExpression(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"uri","hash":{},"data":data}) : helper)))
      + "\"\n";
  },"13":function(container,depth0,helpers,partials,data) {
      return "    data-context\n";
  },"15":function(container,depth0,helpers,partials,data) {
      return "    data-glue-page-header-sticky-height=\"160\"\n";
  },"17":function(container,depth0,helpers,partials,data) {
      return "    data-glue-page-header-sticky-height=\"116\"\n";
  },"19":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "    <div class=\"row\">\n      <div class=\""
      + container.escapeExpression(((helper = (helper = helpers.gridClasses || (depth0 != null ? depth0.gridClasses : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"gridClasses","hash":{},"data":data}) : helper)))
      + "\">\n";
  },"21":function(container,depth0,helpers,partials,data) {
      return "          glue-page-header__content--with-custom-image-size\n";
  },"23":function(container,depth0,helpers,partials,data) {
      return "          glue-page-header__content--with-button-placement-left\n";
  },"25":function(container,depth0,helpers,partials,data) {
      return "            glue-page-header__spacer--large\n";
  },"27":function(container,depth0,helpers,partials,data) {
      return "      </div>\n    </div>\n";
  },"29":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "        <div class=\"row\">\n          <div class=\""
      + container.escapeExpression(((helper = (helper = helpers.gridClasses || (depth0 != null ? depth0.gridClasses : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"gridClasses","hash":{},"data":data}) : helper)))
      + "\">\n";
  },"31":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = container.invokePartial(partials["./internal/page-header/image.hbs"],depth0,{"name":"./internal/page-header/image.hbs","data":data,"indent":"            ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
  },"33":function(container,depth0,helpers,partials,data) {
      return "          </div>\n        </div>\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {});
  
    return "<header\n  class=\"\n    glue-page-header\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.navBar : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.countLabel : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.useResponsiveHeightSmall : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.useResponsiveHeightLarge : depth0),{"name":"if","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.uri : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n    "
      + container.escapeExpression(((helper = (helper = helpers.modifiers || (depth0 != null ? depth0.modifiers : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"modifiers","hash":{},"data":data}) : helper)))
      + "\n  \"\n  data-glue-page-header\n  data-ta-id=\"page-header\"\n\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.uri : depth0),{"name":"if","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n"
      + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.buttons : depth0)) != null ? stack1.playButton : stack1),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.navBar : depth0),{"name":"if","hash":{},"fn":container.program(15, data, 0),"inverse":container.program(17, data, 0),"data":data})) != null ? stack1 : "")
      + ">\n"
      + ((stack1 = container.invokePartial(partials["./internal/page-header/background.hbs"],depth0,{"name":"./internal/page-header/background.hbs","data":data,"indent":"  ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "\n  <div\n    class=\"\n      glue-page-header__content-wrapper\n      container\n    \"\n    data-glue-page-header-content\n  >\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.gridClasses : depth0),{"name":"if","hash":{},"fn":container.program(19, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n    <div\n      class=\"\n        glue-page-header__content\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.useCustomImageSize : depth0),{"name":"if","hash":{},"fn":container.program(21, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.buttons : depth0)) != null ? stack1.buttonPlacementLeft : stack1),{"name":"if","hash":{},"fn":container.program(23, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "      \"\n    >\n      <div class=\"glue-page-header__content-inner\" data-ta-id=\"page-header-inner-content\">\n"
      + ((stack1 = container.invokePartial(partials["./internal/page-header/image.hbs"],depth0,{"name":"./internal/page-header/image.hbs","data":data,"indent":"        ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "\n        <div class=\"glue-page-header__data\">\n"
      + ((stack1 = container.invokePartial(partials["./internal/page-header/label.hbs"],depth0,{"name":"./internal/page-header/label.hbs","data":data,"indent":"          ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + ((stack1 = container.invokePartial(partials["./internal/page-header/title.hbs"],depth0,{"name":"./internal/page-header/title.hbs","data":data,"indent":"          ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + ((stack1 = container.invokePartial(partials["./internal/page-header/description.hbs"],depth0,{"name":"./internal/page-header/description.hbs","data":data,"indent":"          ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + ((stack1 = container.invokePartial(partials["./internal/page-header/meta-info.hbs"],depth0,{"name":"./internal/page-header/meta-info.hbs","data":data,"indent":"          ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + ((stack1 = container.invokePartial(partials["./internal/page-header/buttons.hbs"],depth0,{"name":"./internal/page-header/buttons.hbs","data":data,"indent":"          ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + ((stack1 = container.invokePartial(partials["./internal/page-header/count-label.hbs"],depth0,{"name":"./internal/page-header/count-label.hbs","data":data,"indent":"          ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "        </div>\n\n        <div class=\"\n          glue-page-header__spacer\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.useLargeSpacer : depth0),{"name":"if","hash":{},"fn":container.program(25, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "        \">\n        </div>\n      </div>\n\n"
      + ((stack1 = container.invokePartial(partials["./internal/page-header/buttons.hbs"],depth0,{"name":"./internal/page-header/buttons.hbs","data":data,"indent":"      ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "    </div>\n\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.gridClasses : depth0),{"name":"if","hash":{},"fn":container.program(27, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "  </div>\n\n  <div\n    class=\"glue-page-header__sticky-wrapper\"\n    data-sticky-fixed-wrapper\n    aria-hidden=\"true\"\n  >\n    <div class=\"glue-page-header__sticky\" data-sticky>\n      <div class=\"container\">\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.gridClasses : depth0),{"name":"if","hash":{},"fn":container.program(29, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n        <div class=\"glue-page-header__sticky-inner\">\n"
      + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.hideImageInStickyHeader : depth0),{"name":"unless","hash":{},"fn":container.program(31, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + ((stack1 = container.invokePartial(partials["./internal/page-header/title.hbs"],depth0,{"name":"./internal/page-header/title.hbs","data":data,"indent":"          ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + ((stack1 = container.invokePartial(partials["./internal/page-header/buttons.hbs"],depth0,{"name":"./internal/page-header/buttons.hbs","data":data,"indent":"          ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "        </div>\n\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.gridClasses : depth0),{"name":"if","hash":{},"fn":container.program(33, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "      </div>\n    </div>\n  </div>\n\n"
      + ((stack1 = container.invokePartial(partials["./internal/page-header/navbar.hbs"],depth0,{"name":"./internal/page-header/navbar.hbs","data":data,"indent":"  ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "</header>\n";
  },"usePartial":true,"useData":true});
  
  },{"./internal/page-header/background.hbs":103,"./internal/page-header/buttons.hbs":104,"./internal/page-header/count-label.hbs":105,"./internal/page-header/description.hbs":106,"./internal/page-header/image.hbs":107,"./internal/page-header/label.hbs":108,"./internal/page-header/meta-info.hbs":109,"./internal/page-header/navbar.hbs":110,"./internal/page-header/title.hbs":111,"hbsfy/runtime":368}],113:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('../../../libs/glue/templates/cards/card-without-link.hbs');
  HandlebarsCompiler.registerPartial('../../../libs/glue/templates/cards/card-without-link.hbs', partial$0);
  var partial$1 = require('./buttons/add-interior.hbs');
  HandlebarsCompiler.registerPartial('./buttons/add-interior.hbs', partial$1);
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var stack1, helper, options, buffer = "";
  
    stack1 = ((helper = (helper = helpers.hideRemove || (depth0 != null ? depth0.hideRemove : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"hideRemove","hash":{},"fn":container.noop,"inverse":container.program(2, data, 0),"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
    if (!helpers.hideRemove) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
    if (stack1 != null) { buffer += stack1; }
    return buffer + "      <button class=\"button button-with-stroke button-add\" data-button=\"add\" >"
      + ((stack1 = container.invokePartial(partials["./buttons/add-interior.hbs"],depth0,{"name":"./buttons/add-interior.hbs","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "</button>\n";
  },"2":function(container,depth0,helpers,partials,data) {
      return "      <button class=\"button button-icon-only remove\">"
      + container.escapeExpression((helpers.loc || (depth0 && depth0.loc) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"Remove",{"name":"loc","hash":{},"data":data}))
      + "</button>\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression, buffer = 
    "<table class=\"list-group-item list-group-item-social "
      + alias4(((helper = (helper = helpers.modifiers || (depth0 != null ? depth0.modifiers : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"modifiers","hash":{},"data":data}) : helper)))
      + "\"\n  data-uri=\""
      + alias4(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"uri","hash":{},"data":data}) : helper)))
      + "\"\n  data-log-context=\"list-group-item-social\"\n  data-log-data='{\"target_uri\": \""
      + alias4(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"uri","hash":{},"data":data}) : helper)))
      + "\"}'><tr>\n  <td>\n"
      + ((stack1 = container.invokePartial(partials["../../../libs/glue/templates/cards/card-without-link.hbs"],depth0,{"name":"../../../libs/glue/templates/cards/card-without-link.hbs","data":data,"indent":"    ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "  </td>\n\n  <td><div><div><!-- Extra divs for elipsis issues -->\n    <span class=\"one-line\">\n      <a href=\""
      + alias4(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"uri","hash":{},"data":data}) : helper)))
      + "\" data-uri=\""
      + alias4(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"uri","hash":{},"data":data}) : helper)))
      + "\" title=\""
      + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
      + "\" class=\"title\">"
      + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
      + "</a>\n    </span>\n    <span class=\"one-line\">"
      + ((stack1 = ((helper = (helper = helpers.line2 || (depth0 != null ? depth0.line2 : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"line2","hash":{},"data":data}) : helper))) != null ? stack1 : "")
      + "</span>\n    <span class=\"one-line text-meta pull-bottom\">"
      + ((stack1 = ((helper = (helper = helpers.line1 || (depth0 != null ? depth0.line1 : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"line1","hash":{},"data":data}) : helper))) != null ? stack1 : "")
      + "</span>\n\n  </div></div></td>\n  <td>\n";
    stack1 = ((helper = (helper = helpers.currentUser || (depth0 != null ? depth0.currentUser : depth0)) != null ? helper : alias2),(options={"name":"currentUser","hash":{},"fn":container.noop,"inverse":container.program(1, data, 0),"data":data}),(typeof helper === alias3 ? helper.call(alias1,options) : helper));
    if (!helpers.currentUser) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
    if (stack1 != null) { buffer += stack1; }
    return buffer + "  </td>\n</tr></table>\n";
  },"usePartial":true,"useData":true});
  
  },{"../../../libs/glue/templates/cards/card-without-link.hbs":89,"./buttons/add-interior.hbs":87,"hbsfy/runtime":368}],114:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return " data-navbar-item-matcher=\""
      + container.escapeExpression(((helper = (helper = helpers.matcher || (depth0 != null ? depth0.matcher : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"matcher","hash":{},"data":data}) : helper)))
      + "\"";
  },"3":function(container,depth0,helpers,partials,data) {
      var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
  
    return "  <a href=\""
      + alias4(((helper = (helper = helpers.url || (depth0 != null ? depth0.url : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"url","hash":{},"data":data}) : helper)))
      + "\" draggable=\"false\" data-interaction-target=\"link\">"
      + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
      + "</a>\n  ";
  },"5":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return " "
      + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.id : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.program(8, data, 0),"data":data})) != null ? stack1 : "")
      + " ";
  },"6":function(container,depth0,helpers,partials,data) {
      var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
  
    return "\n  <a draggable=\"false\" data-interaction-target=\"link\">"
      + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
      + "</a>\n";
  },"8":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "  <p class=\"navbar-text\">"
      + container.escapeExpression(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"name","hash":{},"data":data}) : helper)))
      + "</p>\n  ";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
  
    return "<li class=\""
      + alias4(((helper = (helper = helpers.className || (depth0 != null ? depth0.className : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"className","hash":{},"data":data}) : helper)))
      + "\" data-navbar-item-id=\""
      + alias4(((helper = (helper = helpers.id || (depth0 != null ? depth0.id : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"id","hash":{},"data":data}) : helper)))
      + "\""
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.matcher : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + ">\n"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.url : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.program(5, data, 0),"data":data})) != null ? stack1 : "")
      + "\n</li>\n";
  },"useData":true});
  
  },{"hbsfy/runtime":368}],115:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('./navbar-item.hbs');
  HandlebarsCompiler.registerPartial('./navbar-item.hbs', partial$0);
  var partial$1 = require('./facepile.hbs');
  HandlebarsCompiler.registerPartial('./facepile.hbs', partial$1);
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      return "no-overflow-menu";
  },"3":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = container.invokePartial(partials["./navbar-item.hbs"],depth0,{"name":"./navbar-item.hbs","data":data,"indent":"          ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
  },"5":function(container,depth0,helpers,partials,data) {
      return "          <li class=\"overflow-menu-button\" data-navbar-item-id=\"navbar-overflow-menu\">\n            <a data-interaction-target=\"menu-overflow-button\" data-interaction-intent=\"show-overflowed-menu\">"
      + container.escapeExpression((helpers.loc || (depth0 && depth0.loc) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),"More",{"name":"loc","hash":{},"data":data}))
      + "</a>\n          </li>\n";
  },"7":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.facePile : depth0)) != null ? stack1.users : stack1),{"name":"if","hash":{},"fn":container.program(8, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
  },"8":function(container,depth0,helpers,partials,data) {
      var stack1;
  
    return "            <div class=\"navbar-right facepile-list-container\">\n"
      + ((stack1 = container.invokePartial(partials["./facepile.hbs"],(depth0 != null ? depth0.facePile : depth0),{"name":"./facepile.hbs","data":data,"indent":"              ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "            </div>\n";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression, buffer = 
    "<div class=\"navbar-fixed-wrapper\" data-sticky-fixed-wrapper>\n  <div class=\"navbar\" data-sticky=\"true\">\n    <div class=\"container\">\n      <ul class=\"nav navbar-nav "
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.noOverflowMenu : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\"\n        data-navbar=\"navbar\"\n        data-navbar-history-position=\""
      + alias4(((helper = (helper = helpers.historyPosition || (depth0 != null ? depth0.historyPosition : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"historyPosition","hash":{},"data":data}) : helper)))
      + "\"\n        data-navbar-active-id=\""
      + alias4(((helper = (helper = helpers.activeTab || (depth0 != null ? depth0.activeTab : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"activeTab","hash":{},"data":data}) : helper)))
      + "\"\n        data-log-context=\"navbar\"\n        data-interaction-context=\"navbar\"\n        data-interaction-list\n        >\n";
    stack1 = ((helper = (helper = helpers["nav-items"] || (depth0 != null ? depth0["nav-items"] : depth0)) != null ? helper : alias2),(options={"name":"nav-items","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data}),(typeof helper === alias3 ? helper.call(alias1,options) : helper));
    if (!helpers["nav-items"]) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
    if (stack1 != null) { buffer += stack1; }
    return buffer + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.noOverflowMenu : depth0),{"name":"unless","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.facePile : depth0),{"name":"if","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "      </ul>\n    </div>\n  </div>\n</div>\n";
  },"usePartial":true,"useData":true});
  
  },{"./facepile.hbs":99,"./navbar-item.hbs":114,"hbsfy/runtime":368}],116:[function(require,module,exports){
  'use strict';
  
  const base = 'spotify:internal:filterlist';
  const regExp = /^spotify:internal:filterlist:([^:]*):(.*)$/;
  
  /**
   * Create a URI representing a filtered variant of a list.
   *
   * @param {string} originUri The original list URI.
   * @param {string} query The query string (compatible with Core). Filter
   *     implementations might not look at all values.
   *
   * @return {string} URI of the filtered variant of the list. The URI will have
   *     the query string URL encoded.
   */
  exports.create = function (originUri, query) {
    const newOriginUri = originUri.replace(/^spotify:/, '');
  
    const newQuery = encodeURIComponent(query);
  
    return [base, newQuery, newOriginUri].join(':');
  };
  
  /**
   * Get the query string from a filter URI.
   *
   * @param {string} filterUri The URI of the filtered list.
   *
   * @return {string} The query string, URL decoded. If URI is
   *     invalid, this returns an empty string.
   */
  exports.getQuery = function (filterUri) {
    const matches = filterUri.match(regExp);
    if (!matches) return '';
  
    return decodeURIComponent(matches[1]);
  };
  
  /**
   * Get the URI of the original list.
   *
   * @param {string} filterUri The URI of the filtered list.
   *
   * @return {string} The URI of the original list. If the filter URI is invalid,
   *     this returns an empty string.
   */
  exports.getOriginUri = function (filterUri) {
    const matches = filterUri.match(regExp);
    if (!matches) return '';
  
    return `spotify:${matches[2]}`;
  };
  
  /**
   * Parse and return all parts of the filter URI.
   *
   * @param {string} filterUri The URI of the filtered list.
   *
   * @return {Object?} An object with properties `direction`, `query` and `originUri`,
   *     or null if the URI is not valid.
   */
  exports.parse = function (filterUri) {
    const matches = filterUri.match(regExp);
    if (!matches) return null;
  
    return {
      query: decodeURIComponent(matches[1]),
      originUri: `spotify:${matches[2]}`
    };
  };
  
  /**
   * Test if the provided URI is a valid filter URI.
   *
   * @param {string} filterUri The URI of the filtered list.
   *
   * @return {boolean} True if it's valid, false otherwise.
   */
  exports.isValid = function (filterUri) {
    return regExp.test(filterUri);
  };
  
  /**
   * The regular expression that matches filter URIs.
   */
  exports.regExp = regExp;
  
  },{}],117:[function(require,module,exports){
  'use strict';
  
  const Range = require('../range2');
  
  /**
   * Get the needed operations for inserting items at a specific index.
   *
   * @param {Array} items Array of items to insert.
   * @param {number} index Index to insert the items at, relative to the list
   *     before the operations.
   *
   * @return {Array.<Object>} Array of operation objects.
   */
  function getInsertOperations(items, index) {
    return [{ type: 'insert', index: index, values: items }];
  }
  
  /**
   * Get the needed operations for removing items from the specified indices.
   *
   * @param {Array.<number>} indices Array of indices relative to the list before
   *     the operations.
   *
   * @return {Array.<Object>} Array of operation objects.
   */
  function getRemoveOperations(indices) {
    const ranges = Range.fromIndices(indices);
  
    let removed = 0;
    const operations = [];
  
    for (let r = 0, range; ranges[r]; r++) {
      range = ranges[r];
      operations.push({
        type: 'remove',
        index: range.start - removed,
        length: range.length
      });
      removed += range.length;
    }
  
    return operations;
  }
  
  /**
   * Get the needed operations for moving items from a set of indices to a
   * target index.
   *
   * @param {Array.<number>} fromIndices Array of indices relative to the list
   *     before the operations.
   * @param {number} toIndex Index to move the items to, relative to the list
   *     before the operations.
   *
   * @return {Array.<Object>} Array of operation objects.
   */
  function getMoveOperations(fromIndices, toIndex) {
    // We're going through one range at a time, and when a range is moved down,
    // we need to decrease the start of the next range to account for the
    // range we moved down (since each operation is based on the result of
    // the previous operation).
    let movedAbove = 0;
  
    // The maximum index starts at the index we want to move to.
    // When operations are created to move ranges, this maximum index
    // is increased to always point to the bottom index of the moved
    // items. If five items are moved from far down up to the 'to' index,
    // the new maxIndex will be toIndex + 5.
    let maxIndex = toIndex;
  
    const ranges = Range.fromIndices(fromIndices);
    let r;
    let range;
    let toRange;
  
    // Find any range that intersects with the 'to' index. Referred below as
    // the 'to' range. This range can't be split, so we need to move items to
    // positions above or below this range if it exists.
    for (r = 0; ranges[r]; r++) {
      range = ranges[r];
      if (range.start <= toIndex && range.end >= toIndex) {
        toRange = range;
        break;
      }
    }
  
    const operations = [];
  
    for (r = 0; ranges[r]; r++) {
      range = ranges[r];
      const length = range.length;
      let to;
      let from = range.start - movedAbove;
  
      // If the 'to' index is inside a range
      if (toRange) {
        // Range is above the 'to' range, so move it to right above that range.
        if (range.end < toRange.start) {
          from = range.start - movedAbove;
          to = toRange.start - length;
          movedAbove += length;
  
          // Range is the 'to' range, which means it will not move anywhere.
        } else if (range === toRange) {
          maxIndex = Math.max(maxIndex, range.end);
          continue;
  
          // Range is below the 'to' range, so move it to the maximum index
          // we've reached so far.
        } else {
          from = range.start;
          to = maxIndex;
          maxIndex = Math.max(maxIndex, to + length);
        }
        // If the 'to' index is not inside a range
      } else {
        // Range is above the 'to' index, so move it to right above the maximum index.
        if (range.end < toIndex) {
          from = range.start - movedAbove;
          to = maxIndex - length;
          movedAbove += length;
  
          // Range is below the 'to' index, so move it to the maximum index we've
          // reached so far.
        } else {
          from = range.start;
          to = maxIndex;
          maxIndex = Math.max(maxIndex, to + length);
        }
      }
  
      operations.push({ type: 'move', from: from, length: length, to: to });
    }
  
    return operations;
  }
  
  exports.getInsertOperations = getInsertOperations;
  exports.getRemoveOperations = getRemoveOperations;
  exports.getMoveOperations = getMoveOperations;
  
  },{"../range2":204}],118:[function(require,module,exports){
  (function (global){
  'use strict';
  
  const live = require('../../live');
  const cosmos = require('../util/cosmos');
  const Collection = require('./collection');
  const liburi = require('spotify-liburi');
  
  const endpoints = {
    isInCollection: 'sp://core-collection/v1/contains',
    updateIsInCollection: 'sp://core-collection/v1/items',
    broadcast: 'sp://messages/v1/collectionstate',
    pubsub: 'hm://collection/collection/@/json'
  };
  
  const onCollectionPublish = function (error, response) {
    if (error) {
      if (global.console) console.error(error);
      return;
    }
  
    live(response.body.items.filter(({ type = '' }) => type.toUpperCase() === 'ALBUM').map(({ identifier, removed }) => ({
      uri: liburi.albumURI(identifier).toURI(),
      added: !removed
    })));
  };
  
  let albumCollection;
  let broadcastSubscription;
  let collectionSubscription;
  
  const regExp = exports.matches = /^spotify:album:[^:]+$/;
  let registered = false;
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    albumCollection = new Collection(endpoints);
  
    live.subscribe(regExp, 'publish', albumCollection.onPublish);
    live.subscribe(regExp, 'wait', albumCollection.onWait);
  
    broadcastSubscription = cosmos.subscribe({ url: endpoints.broadcast }, Collection.onBroadcast);
    collectionSubscription = cosmos.subscribe({ url: endpoints.pubsub }, onCollectionPublish);
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    live.unsubscribe(regExp, 'publish', albumCollection.onPublish);
    live.unsubscribe(regExp, 'wait', albumCollection.onWait);
  
    albumCollection = null;
  
    if (broadcastSubscription) {
      broadcastSubscription.cancel();
      broadcastSubscription = null;
    }
  
    if (collectionSubscription) {
      collectionSubscription.cancel();
      collectionSubscription = null;
    }
  };
  
  exports._endpoints = endpoints;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../live":172,"../util/cosmos":167,"./collection":120,"spotify-liburi":553}],119:[function(require,module,exports){
  'use strict';
  
  const profile = require('./profile');
  
  const regExp = exports.matches = /^spotify:artist:/;
  let registered;
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    profile.register(regExp);
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    profile.unregister(regExp);
  };
  
  },{"./profile":124}],120:[function(require,module,exports){
  (function (global){
  'use strict';
  
  // This model will take a URI and create a live model for that URI.
  //
  //
  // It will listen for publish events on the live model. The publish
  // events are only triggered when someone actually wants to update
  // something on the backend.
  // When a publish event is detected, it will:
  //
  // * If the changed property was "added", it will contact
  //   the backend to update the stored collection state. If this
  //   fails, it will set it to the opposite of what was pusblished.
  //
  // * On success, broadcast the changes to the rest of the client via
  //   cosmos, as "isInCollection" / "isFollowing" and as "added".
  //
  // This model will also listen for update events on the live model.
  // The update event will be triggered when someone changes the data
  // on the live model, without an intention to update the backend.
  // When an update event is detected, it will:
  //
  // This model will also listen for broadcast events from the rest
  // of the client. When a broadcast event is received, this happens:
  //
  // * If the URI for the broadcast message does not match this model,
  //   do nothing.
  //
  // * It will then update the live model with the data it received
  //   from the broadcast event. This will trigger the update listener
  //   described above, but it won't run the code because all properties
  //   are there.
  
  const prime = require('../../../libs/prime');
  const defer = require('../../../libs/prime/defer');
  
  const live = require('../../live');
  const contains = require('mout/array/contains');
  
  const cosmos = require('../util/cosmos');
  
  const INSUFFICIENT_STORAGE = 507;
  
  // COLLECTION
  
  const Collection = prime({
    constructor: function (endpoints) {
      this.endpoints = endpoints;
      this.onPublish = this.onPublish.bind(this);
      this.waitQueue = [];
      this.onWait = this.onWait.bind(this);
      this.fetch = this.fetch.bind(this);
    },
  
    onPublish: function (model, data) {
      // If the property is "added", update the backend with the new value
      // If the update fails, revert the state
      // If the update does not fail, broadcast the new state, as "isInCollection" and "added"
      if ('added' in data) {
        const endpoints = this.endpoints;
        const state = !!data.added;
  
        // optimistic
        model.update({ added: state });
  
        const method = state ? 'post' : 'delete';
        this._publish(method, [model], function (error, publishData) {
          if (error) {
            // that's what being optimistic gets you
            model.update({ added: !state });
            if (error.response && error.response.getStatusCode() === INSUFFICIENT_STORAGE) {
              cosmos.post({
                url: 'sp://messages/v1/container/user-message',
                body: { id: 'collection-limit-exceeded' }
              });
            }
          } else {
            cosmos.post({
              url: endpoints.broadcast,
              body: { uri: model.uri, isInCollection: state, added: state }
            });
            if (publishData && publishData.items) {
              publishData.items.forEach(function (item) {
                // Patch items to contain added
                item.added = item.isInCollection;
                cosmos.post({ url: endpoints.broadcast, body: item });
              });
            }
          }
        });
      }
    },
  
    onWait: function (model, properties) {
      if (contains(properties, 'added')) {
        this.fetch(model);
      }
    },
  
    fetch: function (model) {
      const queue = this.waitQueue;
  
      queue.push(model);
  
      // The first thing that gets added to the queue should defer a batch fetch
      // for the next tick. On the next tick, it will batch fetch status for all
      // models added to the queue.
      if (queue.length === 1) {
        defer.immediate(this._fetchBatch, this);
      }
    },
  
    _fetchBatch: function () {
      const queue = this.waitQueue;
      if (queue.length === 0) return;
  
      // Create a new queue for requests coming after this
      this.waitQueue = [];
  
      this._contains(queue, function (error, result) {
        // Collection returns 404 for a user without a collection. This is sent as an error,
        // but we will just interpret that as `isInCollection` is false. All other errors
        // we just throw.
        if (error && (!error.response || error.response && error.response.getStatusCode() !== 404)) {
          if (global.console) console.error(error);
          return;
        }
  
        queue.forEach(function (model, i) {
          let added = false;
          if (!error) added = result[i];
          model.update({ added: added });
        });
      });
    },
  
    _contains: function (models, callback) {
      const url = this.endpoints.isInCollection;
      const body = this._getRequestBody(models);
  
      cosmos.post({ url: url, body: body }, function (error, result) {
        callback(error, result && result.body && result.body.found);
      });
    },
  
    _publish: function (method, models, callback) {
      const url = this.endpoints.updateIsInCollection;
      const body = this._getRequestBody(models);
  
      cosmos[method]({ url: url, body: body }, function (error, result) {
        callback(error, result && result.body);
      });
    },
  
    _getRequestBody: function (models) {
      return {
        items: models.map(function (model) {
          return model.uri;
        }),
        source: global.__spotify && global.__spotify.app_uri || null
      };
    }
  });
  
  // ----------------------
  
  Collection.onBroadcast = function (error, response) {
    if (error) {
      if (global.console) console.error(error);
      return;
    }
  
    let value;
    if ('added' in response.body) value = response.body.added;else if ('isInCollection' in response.body) {
      // Deal with older versions of this package that send isInCollection / isFollowing
      value = response.body.isInCollection;
    }
  
    live(response.body.uri).update({
      added: value
    });
  };
  
  module.exports = Collection;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../../libs/prime":202,"../../../libs/prime/defer":200,"../../live":172,"../util/cosmos":167,"mout/array/contains":434}],121:[function(require,module,exports){
  'use strict';
  
  const album = require('./album');
  const artist = require('./artist');
  const playlist = require('./playlist');
  const track = require('./track');
  const user = require('./user');
  const local = require('./local');
  const show = require('./show');
  
  exports.register = function () {
    album.register();
    artist.register();
    playlist.register();
    track.register();
    user.register();
    local.register();
    show.register();
  };
  
  exports.unregister = function () {
    album.unregister();
    artist.unregister();
    playlist.unregister();
    track.unregister();
    user.unregister();
    local.unregister();
    show.unregister();
  };
  
  },{"./album":118,"./artist":119,"./local":122,"./playlist":123,"./show":125,"./track":126,"./user":127}],122:[function(require,module,exports){
  'use strict';
  
  const live = require('../../live');
  const contains = require('mout/array/contains');
  
  const onWait = function (model, properties) {
    if (contains(properties, 'added')) {
      // Neither tracks nor albums or artists can ever be added to YM.
      model.update({ added: false });
    }
  };
  
  // Matches local track, albums and artists.
  const regExp = exports.matches = /^spotify:local:[^:]*:[^:]*:[^:]*:\d*$|^spotify:local:[^:]*:[^:]*$|^spotify:local:[^:]*$/;
  let registered = false;
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    live.subscribe(regExp, 'wait', onWait);
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    live.unsubscribe(regExp, 'wait', onWait);
  };
  
  },{"../../live":172,"mout/array/contains":434}],123:[function(require,module,exports){
  (function (global){
  'use strict';
  
  // The 'playlist' function will be called when a new live object is
  // created for the pattern in playlist.matches. We use this function
  // to automatically add data to the live model when requested, and to
  // push any needed updates to the backend.
  //
  //
  // It will listen for publish events on the live model. The publish
  // events are only triggered when someone actually wants to update
  // something on the backend.
  // When a publish event is detected, it will:
  //
  // * Broadcast the changes to the rest of the client via cosmos.
  //
  // * If the changed property was `added`, it will contact
  //   the backend to update the stored collection state for the
  //   currently logged in user. If this fails, it will revert the
  //   state for the live model and broadcast the reverted state.
  //
  // This function will also listen for update events on the live model.
  // The update event will be triggered when someone changes the data
  // on the live model, without an intention to update the backend.
  // When an update event is detected, it will:
  //
  // This function will also listen for broadcast events from the rest
  // of the client. When a broadcast event is received, this happens:
  //
  // * If the URI for the broadcast message does not match this model,
  //   do nothing.
  //
  // * It will then update the live model with the data it received
  //   from the broadcast event. This will trigger the update listener
  //   described above, but it won't run the code because all properties
  //   are there.
  
  var _playlistUtils = require('../../../libs/playlist-utils');
  
  const contains = require('mout/array/contains');
  
  const live = require('../../live');
  
  const cosmos = require('../util/cosmos');
  
  
  const endpoints = {
    broadcast: 'sp://messages/v1/followstate'
  };
  
  function onPublish(model, data) {
    // If the property is "added", update the backend with the new
    // value. If the update fails somewhere along the way, revert the state
    // and broadcast the reverted state to the rest of the client.
    if ('added' in data) {
      model.update({ added: data.added });
  
      if (model.get('followersCount') !== null) {
        model.update({
          followersCount: model.get('followersCount') + (data.added ? 1 : -1)
        });
      }
  
      const fn = data.added ? _playlistUtils.followPlaylist : _playlistUtils.removePlaylist;
      fn(model.uri, error => {
        if (error) {
          // Revert optimistic change
          model.update({ added: !data.added });
  
          if (model.get('followersCount') !== null) {
            model.update({
              followersCount: model.get('followersCount') + (!data.added ? 1 : -1)
            });
          }
        }
      });
    }
  }
  
  function onBroadcast(error, response) {
    if (error) {
      if (global.console) console.error(error);
      return;
    }
    let value;
    if ('added' in response.body) {
      value = response.body.added;
    } else if ('isFollowing' in response.body) {
      value = response.body.isFollowing;
    }
  
    if (value !== undefined) {
      live(response.body.uri).update({
        added: value
      });
    }
  }
  
  function fetchAdded(model) {
    cosmos.get({
      url: `sp://core-playlist/v1/playlist/${encodeURIComponent(model.uri)}/metadata`,
      body: {
        policy: {
          followed: true
        }
      }
    }, function (error, response) {
      if (error) {
        if (global.console) console.error(error);
        return;
      }
      model.update({
        added: response.body.metadata.followed
      });
    });
  }
  
  function fetchFollowersCount(model) {
    cosmos.get({
      url: `sp://core-playlist/v1/playlist/${encodeURIComponent(model.uri)}/metadata`,
      body: {
        policy: {
          followers: true
        }
      }
    }, function (error, response) {
      if (error) {
        if (global.console) console.error(error);
        return;
      }
      model.update({
        followersCount: response.body.metadata.followers
      });
    });
  }
  
  const onWait = function (model, properties) {
    const needsAdded = contains(properties, 'added');
    const needsFollowersCount = contains(properties, 'followersCount');
    if (needsAdded) {
      const subscribed = model.get('subscribed');
      if (subscribed !== undefined) {
        model.update({ added: subscribed });
      } else {
        fetchAdded(model);
      }
    }
    if (needsFollowersCount) {
      fetchFollowersCount(model);
    }
  };
  
  const regExp = exports.matches = /^spotify:(user:[^:]+:)?playlist:[^:]+$/;
  
  let broadcastSubscription;
  let registered;
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    live.subscribe(regExp, 'publish', onPublish);
    live.subscribe(regExp, 'wait', onWait);
    broadcastSubscription = cosmos.subscribe({ url: endpoints.broadcast }, onBroadcast);
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    live.unsubscribe(regExp, 'publish', onPublish);
    live.unsubscribe(regExp, 'wait', onWait);
  
    broadcastSubscription.cancel();
    broadcastSubscription = null;
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../../libs/playlist-utils":195,"../../live":172,"../util/cosmos":167,"mout/array/contains":434}],124:[function(require,module,exports){
  (function (global){
  'use strict';
  
  // This model will, when registered to live, listen for publish
  // events on the live model. The publish events are only triggered
  // when someone actually wants to update something on the backend.
  // When a publish event is detected, it will:
  //
  // * Broadcast the changes to the rest of the client via cosmos.
  //
  // * If the changed property was isFollowing, it will contact
  //   the backend to update the stored following state for the
  //   currently logged in user. If this fails, it will revert the
  //   state for the live model and broadcast the reverted state.
  //
  // This function will also listen for update events on the live model.
  // The update event will be triggered when someone changes the data
  // on the live model, without an intention to update the backend.
  // When an update event is detected, it will:
  //
  // * If the changed property was isFollowing, it will automatically
  //   update followersCount on the live model.
  //
  // This function will also listen for broadcast events from the rest
  // of the client. When a broadcast event is received, this happens:
  //
  // * If the URI for the broadcast message does not match this model,
  //   do nothing.
  //
  // * It will then update the live model with the data it received
  //   from the broadcast event. This will trigger the update listener
  //   described above, but it won't run the code because all properties
  //   are there.
  //
  
  const intersection = require('mout/array/intersection');
  const live = require('../../live');
  const cosmos = require('../util/cosmos');
  let requestsInProgress = {};
  
  const endpoints = {
    isFollowing: 'hm://socialgraph/v2/is_following?format=json',
    followCounts: 'hm://socialgraph/v2/counts?format=json',
    updateIsFollowing: 'hm://socialgraph/v2/following?format=json',
    broadcast: 'sp://messages/v1/followstate'
  };
  
  function onUpdate(model, data) {
    // Only update followers count if that data was not present in the changed data
    if ('added' in data && !('followersCount' in data)) {
      const followersCount = model.get('followersCount');
      if (followersCount !== undefined) {
        const newFollowersCount = followersCount + (data.added ? 1 : -1);
        model.update({ followersCount: newFollowersCount });
      }
    }
  }
  
  function onPublish(model, data) {
    // If the property is added, update the backend with the new value.
    // If the update fails, revert the model state.
    // If the update is successful, broadcast the state to the client.
    if ('added' in data) {
      // if there is a request in course, do not launch a new request
      // as this can mess up with the Backend
      if (requestsInProgress[model.uri]) {
        return;
      }
      requestsInProgress[model.uri] = true;
  
      // we're optimistic this will work
      model.update({ added: data.added });
  
      updateBackend(model, !!data.added, function (error) {
        // request finished, we can accept more requests now
        delete requestsInProgress[model.uri];
        if (error) {
          // such is life.
          model.update({ added: !data.added });
        } else {
          model.update({ added: data.added });
          broadcast(model);
        }
      });
    }
  }
  
  function onBroadcast(error, response) {
    if (error) {
      if (global.console) console.error(error);
      return;
    }
  
    const body = response.body;
    const data = {};
  
    if ('added' in body) data.added = body.added;else if ('isFollowing' in body) {
      // Also accept broadcast from older versions that only send "isFollowing"
      data.added = body.isFollowing;
    }
  
    if ('followersCount' in body) data.followersCount = body.followersCount;
    if ('followingCount' in body) data.followingCount = body.followingCount;
  
    live(body.uri).update(data);
  }
  
  const followProperties = ['added', 'followersCount', 'followingCount'];
  
  function onWait(model, properties) {
    if (intersection(properties, followProperties).length) {
      getRequestData(model, function (error, data) {
        if (error) {
          if (global.console) console.error(error);
          return;
        }
        updateIsFollowing(model, data);
        updateCounts(model, data);
      });
    }
  }
  
  function updateIsFollowing(model, requestData, callback) {
    const options = {
      url: endpoints.isFollowing,
      body: requestData
    };
    cosmos.get(options, function (error, response) {
      if (error) {
        if (global.console) console.error(error);
        if (callback) {
          callback(error);
        }
      } else {
        const value = response.body[0].is_following;
        model.update({ added: value });
  
        if (callback) callback();
      }
    });
  }
  
  function updateCounts(model, requestData, callback) {
    const options = {
      url: endpoints.followCounts,
      body: requestData
    };
    cosmos.get(options, function (error, response) {
      if (error) {
        if (global.console) console.error(error);
        if (callback) {
          callback(error);
        }
      } else {
        const body = response.body[0];
  
        model.update({
          followersCount: body.followers_count,
          followingCount: body.following_count
        });
  
        if (callback) callback();
      }
    });
  }
  
  function getRequestData(model, callback) {
    // Assumes that the client live object either already has the data about the current user,
    // or that there is code included for getting this data on request.
    live('spotify:client').query('currentUser(uri)', function (error, data) {
      if (error) {
        callback(error);
      } else {
        callback(null, {
          // Social graph v2 expects decoded uris (i.e. spotify:user:someone!
          // rather than spotify:user:someone%21). `currentUser(uri)` returns a
          // uri-encoded user name.
          source_uri: decodeURIComponent(data.currentUser.uri),
          target_uris: [decodeURIComponent(model.uri)]
        });
      }
    });
  }
  
  function updateBackend(model, isFollowing, callback) {
    getRequestData(model, function (error, data) {
      if (error) {
        callback(error);
      } else {
        const requestMethod = isFollowing ? 'post' : 'delete';
        cosmos[requestMethod]({ url: endpoints.updateIsFollowing, body: data }, function (cosmosError) {
          if (cosmosError) {
            callback(cosmosError);
          } else {
            callback(null);
          }
        });
      }
    });
  }
  
  function broadcast(model) {
    const added = model.get('added');
    cosmos.post({
      url: endpoints.broadcast,
      body: {
        uri: model.uri,
        isFollowing: added,
        added: added,
        followersCount: model.get('followersCount'),
        followingCount: model.get('followingCount')
      }
    });
  }
  
  let broadcastSubscription;
  
  exports.register = function (regExp) {
    live.subscribe(regExp, 'wait', onWait);
    live.subscribe(regExp, 'update', onUpdate);
    live.subscribe(regExp, 'publish', onPublish);
  
    // reset the requestsInProgress variable
    requestsInProgress = {};
  
    if (!broadcastSubscription) {
      broadcastSubscription = cosmos.subscribe({ url: endpoints.broadcast }, onBroadcast);
    }
  };
  
  exports.unregister = function (regExp) {
    live.unsubscribe(regExp, 'wait', onWait);
    live.unsubscribe(regExp, 'update', onUpdate);
    live.unsubscribe(regExp, 'publish', onPublish);
  
    if (broadcastSubscription) {
      broadcastSubscription.cancel();
      broadcastSubscription = null;
    }
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../live":172,"../util/cosmos":167,"mout/array/intersection":441}],125:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.register = register;
  exports.unregister = unregister;
  
  var _live = require('../../live');
  
  var _live2 = _interopRequireDefault(_live);
  
  var _collection = require('./collection');
  
  var _collection2 = _interopRequireDefault(_collection);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  const regExp = /^spotify:show:[^:]+$/;
  let showCollection;
  let registered = false;
  
  function register() {
    if (registered) return;
    registered = true;
  
    showCollection = new _collection2.default({
      isInCollection: 'sp://core-collection/v1/contains',
      updateIsInCollection: 'sp://core-collection/v1/items',
      broadcast: 'sp://messages/v1/collectionstate',
      pubsub: 'hm://collection/collection/@/json'
    });
  
    _live2.default.subscribe(regExp, 'publish', showCollection.onPublish);
    _live2.default.subscribe(regExp, 'wait', showCollection.onWait);
  }
  
  function unregister() {
    if (!registered) return;
    registered = false;
  
    _live2.default.unsubscribe(regExp, 'publish', showCollection.onPublish);
    _live2.default.unsubscribe(regExp, 'wait', showCollection.onWait);
  }
  
  },{"../../live":172,"./collection":120}],126:[function(require,module,exports){
  (function (global){
  'use strict';
  
  const live = require('../../live');
  const cosmos = require('../util/cosmos');
  const Collection = require('./collection');
  const liburi = require('spotify-liburi');
  
  const endpoints = {
    isInCollection: 'sp://core-collection/v1/contains',
    updateIsInCollection: 'sp://core-collection/v1/items',
    broadcast: 'sp://messages/v1/collectionstate',
    pubsub: 'hm://collection/collection/@/json'
  };
  
  const onCollectionPublish = function (error, response) {
    if (error) {
      if (global.console) console.error(error);
      return;
    }
  
    live(response.body.items.filter(({ type = '' }) => type.toUpperCase() === 'TRACK').map(({ identifier, removed }) => ({
      uri: liburi.trackURI(identifier).toURI(),
      added: !removed
    })));
  };
  
  let trackCollection;
  let broadcastSubscription;
  let collectionSubscription;
  
  const regExp = exports.matches = /^spotify:track:[^:]+$/;
  let registered = false;
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    trackCollection = new Collection(endpoints);
  
    live.subscribe(regExp, 'publish', trackCollection.onPublish);
    live.subscribe(regExp, 'wait', trackCollection.onWait);
  
    broadcastSubscription = cosmos.subscribe({ url: endpoints.broadcast }, Collection.onBroadcast);
    collectionSubscription = cosmos.subscribe({ url: endpoints.pubsub }, onCollectionPublish);
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    live.unsubscribe(regExp, 'publish', trackCollection.onPublish);
    live.unsubscribe(regExp, 'wait', trackCollection.onWait);
  
    trackCollection = null;
  
    if (broadcastSubscription) {
      broadcastSubscription.cancel();
      broadcastSubscription = null;
    }
  
    if (collectionSubscription) {
      collectionSubscription.cancel();
      collectionSubscription = null;
    }
  };
  
  exports._endpoints = endpoints;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../live":172,"../util/cosmos":167,"./collection":120,"spotify-liburi":553}],127:[function(require,module,exports){
  'use strict';
  
  const profile = require('./profile');
  
  const regExp = exports.matches = /^spotify:user:[^:]+$/;
  let registered;
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    profile.register(regExp);
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    profile.unregister(regExp);
  };
  
  },{"./profile":124}],128:[function(require,module,exports){
  (function (global){
  /**
   * @module live-models/album
   */
  'use strict';
  
  const live = require('../live');
  
  const liburi = require('spotify-liburi');
  
  const intersection = require('mout/array/intersection');
  const contains = require('mout/array/contains');
  const mixIn = require('mout/object/mixIn');
  
  const bridge = require('./util/bridge').request;
  
  function isLocalAlbum(uri) {
    return liburi.from(uri).type === liburi.Type.LOCAL_ALBUM;
  }
  
  function isLocalArtist(uri) {
    return liburi.from(uri).type === liburi.Type.LOCAL_ARTIST;
  }
  
  function updateMetadata(model) {
    if (isLocalAlbum(model.uri)) {
      const uriObject = liburi.from(model.uri);
      if (uriObject) {
        const data = {
          local: true,
          name: uriObject.album,
          artists: [{
            uri: 'spotify:local:',
            name: '',
            local: true
          }]
        };
        if (uriObject.artist) {
          data.artists[0].uri = liburi.localArtistURI(uriObject.artist).toURI();
          data.artists[0].name = uriObject.artist;
          data.artists[0].local = true;
        }
        model.update(data);
      }
    } else {
      bridge('album_metadata', [model.uri], function (error, payload) {
        if (error) {
          if (global.console) console.error(error);
          return;
        }
        model.update(payload);
      });
    }
  }
  
  function updateLocal(model) {
    model.update({
      local: isLocalAlbum(model.uri)
    });
  }
  
  let idCounter = 1000;
  
  function getRowUri(trackUri) {
    return trackUri.replace('spotify:', `spotify:row:${(++idCounter).toString(36)}:`);
  }
  
  function updateRows(model) {
    const descriptor = { type: 'list', uri: model.uri };
  
    bridge('album_tracks_snapshot', [descriptor, 0, 0, false], function (error, payload) {
      if (error) {
        if (global.console) console.error(error);
        return;
      }
  
      bridge('album_tracks_snapshot', [descriptor, 0, payload.length, false], function (bridgeError, bridgePayload) {
        if (bridgeError) {
          if (global.console) console.error(bridgeError);
        } else {
          const discs = [];
          const uriId = liburi.from(model.uri).id;
  
          const rows = bridgePayload.metadata && bridgePayload.metadata.map(function (trackdata, i) {
            const track = mixIn({ uri: bridgePayload.array[i] }, trackdata);
            const discNum = track.disc ? track.disc - 1 : 0;
            track.unavailable = !track.playable;
            setLocalProperty(track);
            if (!discs[discNum]) {
              discs[discNum] = {
                rows: [],
                uri: uriId ? liburi.albumURI(uriId, discNum + 1).toURI() : ''
              };
            }
            const uri = getRowUri(track.uri);
            const trackObject = {
              track: track,
              uri: uri
            };
            discs[discNum].rows.push(trackObject);
            return trackObject;
          });
  
          model.update({ rows: rows, discs: discs });
        }
      });
    });
  }
  
  function setLocalProperty(track) {
    if (track.album && track.album.uri) {
      track.album.local = isLocalAlbum(track.album.uri);
    }
    if (track.artists) {
      for (let i = 0, l = track.artists.length; i < l; i++) {
        const artist = track.artists[i];
        if (artist.uri) {
          artist.local = isLocalArtist(artist.uri);
        }
      }
    }
  }
  
  function onWait(model, properties) {
    const hasLocal = contains(properties, 'local');
    const hasRows = contains(properties, 'rows');
    const hasDiscs = contains(properties, 'discs');
  
    const metaFields = ['artists', 'image', 'images', 'name', 'playable', 'type', 'availability', 'date', 'label', 'copyrights'];
  
    const hasMetadata = !!intersection(properties, metaFields).length;
  
    if (hasMetadata) updateMetadata(model);
    if (hasRows || hasDiscs) updateRows(model);
    if (hasLocal) updateLocal(model);
  }
  
  const regExp = exports.matches = /^spotify:album:[0-9a-zA-Z]+$|^spotify:local:[^:]*:[^:]*$/;
  let registered;
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    live.subscribe(regExp, 'wait', onWait);
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    live.unsubscribe(regExp, 'wait', onWait);
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../live":172,"./util/bridge":165,"mout/array/contains":434,"mout/array/intersection":441,"mout/object/mixIn":471,"spotify-liburi":553}],129:[function(require,module,exports){
  (function (global){
  /**
   * @module live-models/application
   */
  'use strict';
  
  var _getLocationHref = require('./util/get-location-href');
  
  var _getLocationHref2 = _interopRequireDefault(_getLocationHref);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  const live = require('../live');
  const AppState = require('../navigation/app-state');
  
  
  function onMessage(message) {
    if (message.data && message.data.name) {
      if (message.data.name === 'set_active') {
        live('spotify:application').update({
          active: message.data.active
        });
      } else if (message.data.name === 'set_arguments') {
        // Arguments come as an array of un-encoded values, but we need to
        // concatenate them to a single colon-separated string for the
        // current design of the API. This is incorrect, as arguments should
        // be encoded if they are joined in a string. We should fix this, but
        // not without a breaking change.
        // https://jira.spotify.net/browse/KM-2353
        live('spotify:application').update({
          arguments: message.data.arguments.join(':')
        });
      } else if (message.data.name === 'set_state') {
        live('spotify:application').update({
          state: AppState.unserialize(message.data.state)
        });
      }
    }
  }
  
  function onRegister(model) {
    model.update({
      version: global.__spotify && global.__spotify.app_version || '0.0.0'
    });
  
    // Get the initial application state from the URL.
    const url = new URL((0, _getLocationHref2.default)());
    if (url.hostname.length > 0) {
      const pattern = /^([a-zA-Z-]+)\.app\.spotify\.com$/;
      const matches = url.hostname.match(pattern);
      if (matches.length === 2) {
        model.update({
          appURI: `spotify:app:${matches[1]}`,
          arguments: url.searchParams.get('param') || '',
          active: true
        });
      }
    }
  }
  
  exports.matches = /^spotify:application$/;
  let registered;
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    global.addEventListener('message', onMessage);
    onRegister(live('spotify:application'));
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    global.removeEventListener('message', onMessage);
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../live":172,"../navigation/app-state":189,"./util/get-location-href":168}],130:[function(require,module,exports){
  (function (global){
  /**
   * @module live-models/artist
   */
  'use strict';
  
  const liburi = require('spotify-liburi');
  const contains = require('mout/array/contains');
  const live = require('../live');
  const bridge = require('./util/bridge').request;
  
  function isLocalArtist(uri) {
    return liburi.from(uri).type === liburi.Type.LOCAL_ARTIST;
  }
  
  function updateMetadata(model) {
    if (isLocalArtist(model.uri)) {
      const uriObject = liburi.from(model.uri);
      if (uriObject) {
        model.update({
          local: true,
          name: uriObject.artist
        });
      }
    } else {
      bridge('artist_metadata', [model.uri], function (error, payload) {
        if (error) {
          if (global.console) console.error(error);
          return;
        }
        if (!payload.image && !model.get('image')) payload.image = '';
        if (!payload.images && !model.get('images')) payload.images = [];
        model.update(payload);
      });
    }
  }
  
  function updateLocal(model) {
    model.update({
      local: isLocalArtist(model.uri)
    });
  }
  
  function onWait(model, properties) {
    const hasLocal = contains(properties, 'local');
    let hasMetadata = false;
    const metaFields = ['image', 'images', 'name'];
    for (let i = 0, l = properties.length; i < l; i++) {
      if (contains(metaFields, properties[i])) {
        hasMetadata = true;
        break;
      }
    }
  
    if (hasMetadata) updateMetadata(model);
    if (hasLocal) updateLocal(model);
  }
  
  const regExp = exports.matches = /^spotify:artist:|^spotify:local:[^:]*$/;
  let registered;
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    live.subscribe(regExp, 'wait', onWait);
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    live.unsubscribe(regExp, 'wait', onWait);
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../live":172,"./util/bridge":165,"mout/array/contains":434,"spotify-liburi":553}],131:[function(require,module,exports){
  (function (global){
  /**
   * @module live-models/client-storage
   */
  'use strict';
  
  const forOwn = require('mout/object/forOwn');
  const combine = require('mout/array/combine');
  const remove = require('mout/array/remove');
  const contains = require('mout/array/contains');
  
  const URI = 'spotify:client-storage';
  const live = require('../live');
  const cosmos = require('./util/cosmos');
  const endpoints = {
    broadcast: 'sp://messages/v1/client-storage'
  };
  
  let waiting = [];
  
  function onInit(model) {
    // Set a flag for showing that the model has been implemented and
    // registered. This is so that we can check this in player without
    // making a breaking change. Would be great with a future live API
    // to see if someone has implemented a specific model.
    model.update({ implemented: true });
  }
  
  function onWait(model, properties) {
    // Ask the top frame to broadcast the properties.
    // Values will be null when they weren't stored.
    waiting = combine(waiting, properties);
    global.top.postMessage({
      type: 'client_storage',
      name: 'broadcast_client_storage',
      data: properties
    }, '*');
  }
  
  function onPublish(model, properties) {
    // Tell the top frame to persist and broadcast
    global.top.postMessage({
      type: 'client_storage',
      name: 'set_client_storage',
      data: properties
    }, '*');
  
    // Optimistic update
    model.update(properties);
  }
  
  function onBroadcast(error, event) {
    if (error) {
      console.error('Broadcast Error', error);
      return;
    }
    const update = {};
    const model = live(URI);
    forOwn(event.body, function (value, key) {
      // Update model if this instance of storage knows that the model waits for it
      const isWaiting = contains(waiting, key);
  
      // Or if the model has a potentially outdated value in the live cache
      const hasKey = model.get(key) !== undefined;
  
      if (isWaiting || hasKey) update[key] = value;
      if (isWaiting) remove(waiting, key);
    });
    model.update(update);
  }
  
  let broadcastSubscription;
  const regExp = exports.matches = new RegExp(`^${URI}$`);
  let registered;
  
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    live.subscribe(regExp, 'init', onInit);
    live.subscribe(regExp, 'wait', onWait);
    live.subscribe(regExp, 'publish', onPublish);
    broadcastSubscription = cosmos.subscribe({ url: endpoints.broadcast }, onBroadcast);
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    live.unsubscribe(regExp, 'init', onInit);
    live.unsubscribe(regExp, 'wait', onWait);
    live.unsubscribe(regExp, 'publish', onPublish);
  
    if (broadcastSubscription) {
      broadcastSubscription.cancel();
      broadcastSubscription = null;
    }
    waiting.length = 0;
  };
  
  exports._endpoints = endpoints;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../live":172,"./util/cosmos":167,"mout/array/combine":433,"mout/array/contains":434,"mout/array/remove":443,"mout/object/forOwn":468}],132:[function(require,module,exports){
  (function (global){
  /**
   * @module live-models/client
   */
  'use strict';
  
  const live = require('../live');
  const bridge = require('./util/bridge').request;
  const liburi = require('spotify-liburi');
  
  const BACKOFF = 100;
  
  function updateCurrentUser(model) {
    const username = global.__spotify && global.__spotify.username;
    const userUri = liburi.profileURI(username).toURI();
    bridge('user_metadata', [userUri], function (error, payload) {
      if (error) {
        if (username) {
          model.update({
            currentUser: {
              uri: userUri,
              username: username
            }
          });
        }
  
        if (global.console) console.error(error);
        return;
      }
  
      model.update({
        currentUser: {
          uri: userUri,
          name: payload.name,
          username: payload.username
        }
      });
    });
  }
  
  function updateSessionData(model) {
    bridge('session_query', [], function (error, payload) {
      if (error) {
        if (global.console) console.error(error);
        return;
      }
  
      // Not all the clients expose employee property so it needs to be set if it does not exist
      if (payload.employee === undefined) {
        if (global.__spotify && global.__spotify.product_state && global.__spotify.product_state.employee) {
          // For Zelda
          payload.employee = global.__spotify.product_state.employee === '1' ? true : false;
        } else {
          // Clients older than 0.9.16 do not expose employee flag.
          payload.employee = false;
        }
      }
  
      model.update({ session: payload });
  
      // Initiate session subscription since data has been requested once.
      bridgeWaitSession(model);
    });
  }
  
  let registered;
  
  function bridgeWaitSession(model) {
    bridge('session_event_wait', [], function (error, event) {
      if (!registered) return;
      if (error) {
        setTimeout(function () {
          bridgeWaitSession(model);
        }, BACKOFF);
        if (global.console) console.error(error);
        return;
      }
  
      if (event.type === 'change') {
        model.get('session').update(event.data);
      }
  
      bridgeWaitSession(model);
    });
  }
  
  function showContextMenu(model, data) {
    if (global === window && window.top && window.top.postMessage) {
      window.top.postMessage({
        type: 'client_show_context_ui',
        data: data
      }, '*');
    }
  }
  
  function onWait(model, properties) {
    if (properties.indexOf('currentUser') > -1) {
      updateCurrentUser(model);
    }
  
    if (properties.indexOf('session') > -1) {
      updateSessionData(model);
    }
  }
  
  function onInit(model) {
    // Get container_features and put it into it's own place on the client model.
    const __spotify = global.__spotify || {};
    const containerFeatures = live(__spotify.container_features || {});
    containerFeatures.on('wait', function (properties) {
      const update = {};
      for (let i = 0; i < properties.length; i++) {
        const property = properties[i];
        update[property] = false;
      }
      containerFeatures.update(update);
    });
  
    model.update({
      containerFeatures: containerFeatures
    });
  
    // Grab the username from __spotify where available
    if (global.__spotify && global.__spotify.username) {
      const username = global.__spotify.username;
      model.update({
        currentUser: {
          uri: liburi.profileURI(username).toURI(),
          username: username
        }
      });
    }
  
    // Async fetch from bridge to complete currentUser to a user model
    // while we don't have all the info available elsewhere.
    updateCurrentUser(model);
  }
  
  const regExp = exports.matches = /^spotify:client$/;
  
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    live.subscribe(regExp, 'wait', onWait);
    live.subscribe(regExp, 'init', onInit);
    live.subscribe(regExp, 'show-context-menu', showContextMenu);
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    live.unsubscribe(regExp, 'wait', onWait);
    live.unsubscribe(regExp, 'init', onInit);
    live.unsubscribe(regExp, 'show-context-menu', showContextMenu);
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../live":172,"./util/bridge":165,"spotify-liburi":553}],133:[function(require,module,exports){
  'use strict';
  
  const live = require('../live');
  const cosmos = require('./util/cosmos');
  
  function onWait(model) {
    cosmos.get({
      url: 'sp://core-show/unstable/decorate',
      body: { items: [model.uri] }
    }, function (error, data) {
      if (error) throw error;
  
      const items = data.body.items[model.uri];
      items.show.uri = items.show.link;
      model.update(items);
    });
  }
  
  const regExp = exports.matches = /^spotify:episode:[^:]+$/;
  let registered;
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    live.subscribe(regExp, 'wait', onWait);
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    live.unsubscribe(regExp, 'wait', onWait);
  };
  
  },{"../live":172,"./util/cosmos":167}],134:[function(require,module,exports){
  'use strict';
  
  var _live = require('../live');
  
  var _live2 = _interopRequireDefault(_live);
  
  var _bridge = require('../bridge');
  
  var _playlistUtils = require('../playlist-utils');
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  const CORE_FEEDBACK_ENDPOINT = 'sp://feedback/v1/feedback';
  let feedbackSubscriptionCancel;
  
  function onFeedbackSuccess(err, data) {
    if (err || !data) return;
  
    const feedbackUri = _playlistUtils.formatListUtils.makeFeedbackUri(data.context_uri, data.uri);
  
    let thumb = null;
    switch (data.type) {
      case 'like':
        thumb = 'up';
        break;
      case 'dislike':
        thumb = 'down';
        break;
      case 'delete':
      default:
        break;
    }
    (0, _live2.default)(feedbackUri).update({
      thumb
    });
  }
  
  exports.register = function () {
    feedbackSubscriptionCancel = (0, _bridge.cosmosJSON)({ method: 'SUB', uri: CORE_FEEDBACK_ENDPOINT }, onFeedbackSuccess);
  };
  
  exports.unregister = function () {
    feedbackSubscriptionCancel();
    feedbackSubscriptionCancel = null;
  };
  
  },{"../bridge":53,"../live":172,"../playlist-utils":195}],135:[function(require,module,exports){
  'use strict';
  
  const album = require('./album');
  const application = require('./application');
  const artist = require('./artist');
  const client = require('./client');
  const clientStorage = require('./client-storage');
  const sessionStorage = require('./session-storage');
  const player = require('./player');
  const playlist = require('./playlist');
  const sortlist = require('./sortlist');
  const track = require('./track');
  const user = require('./user');
  const add = require('./add');
  const localFiles = require('./local-files');
  const listVariant = require('./list-variant');
  const episode = require('./episode');
  const show = require('./show');
  const feedback = require('./feedback');
  
  exports.register = function () {
    album.register();
    application.register();
    artist.register();
    client.register();
    clientStorage.register();
    sessionStorage.register();
    player.register();
    playlist.register();
    sortlist.register();
    track.register();
    user.register();
    add.register();
    localFiles.register();
    listVariant.register();
    episode.register();
    show.register();
    feedback.register();
  };
  
  exports.unregister = function () {
    album.unregister();
    application.unregister();
    artist.unregister();
    client.unregister();
    clientStorage.unregister();
    sessionStorage.unregister();
    player.unregister();
    playlist.unregister();
    sortlist.unregister();
    track.unregister();
    user.unregister();
    add.unregister();
    localFiles.unregister();
    listVariant.unregister();
    episode.unregister();
    show.unregister();
    feedback.unregister();
  };
  
  },{"./add":121,"./album":128,"./application":129,"./artist":130,"./client":132,"./client-storage":131,"./episode":133,"./feedback":134,"./list-variant":136,"./local-files":137,"./player":142,"./playlist":146,"./session-storage":147,"./show":148,"./sortlist":156,"./track":163,"./user":164}],136:[function(require,module,exports){
  /**
   * @module live-models/list-variant
   */
  'use strict';
  
  var _contains = require('mout/array/contains');
  
  var _contains2 = _interopRequireDefault(_contains);
  
  var _unplayableTracksPlaylistSetting = require('./util/unplayable-tracks-playlist-setting.js');
  
  var _unplayableTracksPlaylistSetting2 = _interopRequireDefault(_unplayableTracksPlaylistSetting);
  
  var _legacySortUris = require('./sorting/legacy-sort-uris');
  
  var _legacySortUris2 = _interopRequireDefault(_legacySortUris);
  
  var _live = require('../live');
  
  var _live2 = _interopRequireDefault(_live);
  
  var _liveSortUri = require('../live-sort-uri');
  
  var _liveSortUri2 = _interopRequireDefault(_liveSortUri);
  
  var _liveFilterUri = require('../live-filter-uri');
  
  var _liveFilterUri2 = _interopRequireDefault(_liveFilterUri);
  
  var _playlistUtils = require('../playlist-utils');
  
  var _spotifyLiburi = require('spotify-liburi');
  
  var _spotifyLiburi2 = _interopRequireDefault(_spotifyLiburi);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  const SESSION_STORAGE = 'spotify:session-storage';
  const CLIENT_STORAGE = 'spotify:client-storage';
  const SORT_VARIANT_KEY = 'list-variant-';
  const STRING_FILTER_KEY = 'list-filter-string-';
  
  function onWait(model, properties) {
    if (!(0, _contains2.default)(properties, 'listVariant')) {
      return;
    }
  
    const uri = getOriginUri(model.uri);
  
    // listVariant objects are shared amongst all variants.
    let listVariant = (0, _live2.default)(uri).get('listVariant');
    if (!listVariant) {
      listVariant = (0, _live2.default)({
        origin: {
          uri: uri
        }
      });
      listVariant.on('wait', onVariantWait.bind(null, listVariant));
    }
  
    model.update({ listVariant: listVariant });
  
    // If the model is a variant, set that list variant on the origin too
    // so it can be accessed there for the next variant to ask.
    if (uri !== model.uri) {
      (0, _live2.default)(uri).update({ listVariant: listVariant });
    }
  }
  
  function onVariantWait(model, properties) {
    if ((0, _contains2.default)(properties, 'currentUri')) {
      addAndWatchCurrentUri(model);
      // This will fulfill and watch all other properties
      return;
    }
  
    if ((0, _contains2.default)(properties, 'filterString')) {
      addAndWatchFilterString(model);
    }
    if ((0, _contains2.default)(properties, 'sortQuery') || (0, _contains2.default)(properties, 'sortDirection') || (0, _contains2.default)(properties, 'sortUri')) {
      addAndWatchSortQuery(model);
    }
    if ((0, _contains2.default)(properties, 'showUnplayable')) {
      addAndWatchUnplayable(model);
    }
  }
  
  function addAndWatchCurrentUri(model) {
    model.on('update', update);
    update();
  
    function update() {
      const props = 'filterString, sortQuery, sortDirection, sortUri, showUnplayable, origin(uri)';
      model.query(props, function (error, data) {
        if (error) {
          console.error('Error when getting', props, ':', Error);
          return;
        }
  
        let result = data.origin.uri;
        const filters = [];
  
        if (data.filterString) {
          filters.push(`text contains ${encodeURIComponent(data.filterString)}`);
        }
  
        if (!data.showUnplayable) {
          filters.push('playable eq true');
        }
  
        if (filters.length) {
          result = _liveFilterUri2.default.create(result, filters.join(','));
        }
  
        const sortUriParams = data.sortUri && _liveSortUri2.default.parse(data.sortUri);
  
        if (sortUriParams) {
          result = _liveSortUri2.default.create(result, sortUriParams.direction, sortUriParams.query);
        }
  
        model.update({
          currentUri: result
        });
      });
    }
  }
  
  function addAndWatchUnplayable(model) {
    const uri = model.get('origin').uri;
  
    if (!_spotifyLiburi2.default.isPlaylistV1OrV2(uri)) {
      model.update({ showUnplayable: true });
      return;
    }
  
    (0, _live2.default)(uri).get('formatListType', function (error, formatListType) {
      if (error || formatListType !== 'chart') {
        subscribeToUnplayablePref(model);
      } else {
        model.update({ showUnplayable: true });
      }
    });
  }
  
  let showUnplayablePref;
  let updateUnavailablePrefOn = [];
  
  function subscribeToUnplayablePref(model) {
    updateUnavailablePrefOn.push(model);
    if (showUnplayablePref) {
      model.update({ showUnplayable: showUnplayablePref });
      return;
    }
  
    _unplayableTracksPlaylistSetting2.default.subscribe(function (error, showUnplayable) {
      let newShowUnplayable = showUnplayable;
  
      // Cache this globally as we will never cancel this subscribe here.
      // The same setting is valid for all models.
      if (error) {
        console.error(error);
        newShowUnplayable = false; // Should default to hide unplayable.
      }
  
      showUnplayablePref = newShowUnplayable;
      for (let i = 0; i < updateUnavailablePrefOn.length; i++) {
        updateUnavailablePrefOn[i].update({ showUnplayable: newShowUnplayable });
      }
    });
  }
  
  exports.getSortVariantKey = function (uri) {
    return SORT_VARIANT_KEY + uri;
  };
  
  function fetchSortVariantByUri(uri) {
    return new Promise((resolve, reject) => (0, _live2.default)(CLIENT_STORAGE).get(exports.getSortVariantKey(uri), (error, variant) => {
      if (error) reject(error);else resolve(variant);
    }));
  }
  
  async function addAndWatchSortQuery(model) {
    const uri = model.get('origin').uri;
    const clientStorage = (0, _live2.default)(CLIENT_STORAGE);
    const key = exports.getSortVariantKey(uri);
  
    if (clientStorage.get('implemented')) {
      if (_spotifyLiburi2.default.isPlaylistV2(uri)) {
        const variant = await fetchSortVariantByUri(uri);
        if (!variant) {
          // This Playlist V2 URI didn't have any variant saved – let's check if
          // its V1 equivalent has!
          const oldVariant = await fetchSortVariantByUri((await (0, _playlistUtils.fetchPlaylistV1UriFromPlaylist)(uri)));
          if (oldVariant) {
            // Oh, its V1 equivalent had a saved variant – let's port it over to
            // the V2 URI
            clientStorage.publish({
              [key]: oldVariant
            });
          }
        }
      }
  
      updateSortParams(model, uri, key);
      clientStorage.on('update', function (changed) {
        if (changed.hasOwnProperty(key)) {
          updateSortParams(model, uri, key);
        }
      });
    } else {
      // There is no clientStorage. Update the model with defaults.
      const update = getDefaultQueryParams(uri);
      model.update(update);
    }
  }
  
  function updateSortParams(model, uri, key) {
    const clientStorage = (0, _live2.default)(CLIENT_STORAGE);
  
    clientStorage.get(key, function (error, variant) {
      // Respond with the sort variant if it's a valid sort URI
      const sortParams = variant && _liveSortUri2.default.parse(variant);
      if (!error && sortParams) {
        // Check for legacy sort URIs and convert them by publishing
        // the updated version on client-storage
        const convertedFromLegacy = _legacySortUris2.default.legacyQueries[sortParams.query];
  
        if (convertedFromLegacy && convertedFromLegacy !== sortParams.query) {
          // Fix the legacy query on storage
          const fixedUri = _liveSortUri2.default.create(uri, sortParams.direction, convertedFromLegacy);
          const publish = {};
          publish[key] = fixedUri;
          clientStorage.publish(publish);
          // This will trigger another update which will then set the sort params.
          return;
        }
  
        model.update({
          sortQuery: sortParams.query,
          sortDirection: sortParams.direction, // this is 'asc' or 'desc'
          sortUri: variant
        });
      } else if (variant === uri) {
        // This was unsorted.
        model.update({
          sortQuery: null,
          sortDirection: null,
          sortUri: null
        });
      } else {
        // There was nothing valid in store. Update the model with defaults.
        const update = getDefaultQueryParams(uri);
        model.update(update);
      }
    });
  }
  
  function getDefaultQueryParams(uri) {
    let sortQuery = null;
    let sortDirection = null;
    let sortUri = null;
  
    const isLocalFiles = uri === 'spotify:internal:local-files';
    if (isLocalFiles) {
      // Local files should be sorted by trackname by default
      sortQuery = 'track(name)';
      sortDirection = 'asc';
      sortUri = _liveSortUri2.default.create(uri, sortDirection, sortQuery);
    }
  
    return {
      sortQuery: sortQuery,
      sortDirection: sortDirection,
      sortUri: sortUri
    };
  }
  
  function addAndWatchFilterString(model) {
    const uri = model.get('origin').uri; // The variant object itself doesn't have a uri, this is the one for the list it's about
    const sessionStorage = (0, _live2.default)(SESSION_STORAGE);
    const key = STRING_FILTER_KEY + uri;
  
    if (sessionStorage.get('implemented')) {
      updateFilterString(model, uri, key);
      sessionStorage.on('update', function (changed) {
        if (changed.hasOwnProperty(key)) {
          updateFilterString(model, uri, key);
        }
      });
    } else {
      model.update({ filterString: '' });
    }
  }
  
  function updateFilterString(model, uri, key) {
    const sessionStorage = (0, _live2.default)(SESSION_STORAGE);
  
    sessionStorage.get(key, function (error, value) {
      model.update({ filterString: value || '' });
    }, _live2.default.ASAP);
  }
  
  function getOriginUri(uri) {
    let newUri = uri;
    const sorted = _liveSortUri2.default.parse(newUri);
    if (sorted) newUri = sorted.originUri;
  
    const filtered = _liveFilterUri2.default.parse(newUri);
    if (filtered) newUri = filtered.originUri;
  
    return newUri;
  }
  
  let registered;
  
  function updateRegisteredStatus(status) {
    registered = status;
    (0, _live2.default)('spotify:list-variant').update({ implemented: status });
  }
  
  const regExp = exports.matches = /^spotify:/;
  exports.register = function () {
    if (registered) return;
  
    _live2.default.subscribe(regExp, 'wait', onWait);
    updateRegisteredStatus(true);
  };
  
  exports.unregister = function () {
    if (!registered) return;
  
    updateUnavailablePrefOn = [];
    showUnplayablePref = null;
  
    _live2.default.unsubscribe(regExp, 'wait', onWait);
    updateRegisteredStatus(false);
  };
  
  },{"../live":172,"../live-filter-uri":116,"../live-sort-uri":170,"../playlist-utils":195,"./sorting/legacy-sort-uris":150,"./util/unplayable-tracks-playlist-setting.js":169,"mout/array/contains":434,"spotify-liburi":553}],137:[function(require,module,exports){
  (function (global){
  /**
   * @module live-models/local-files
   */
  'use strict';
  
  const contains = require('mout/array/contains');
  
  const live = require('../live');
  const listOperations = require('../live-list-operations');
  const liburi = require('spotify-liburi');
  const Range = require('../range2');
  const cosmos = require('./util/cosmos');
  
  const regExp = /^spotify:internal:local-files$/;
  
  function onWait(model, properties) {
    if (contains(properties, 'allows')) getAllows(model);
    if (contains(properties, 'rows')) getRows(model);
  }
  
  function getAllows(model) {
    model.update({
      allows: {
        insertTracks: false,
        removeTracks: false
      }
    });
  }
  
  function createRows(data) {
    const rows = new Array(data.length);
  
    for (let i = 0, l = data.length; i < l; i++) {
      const track = data[i];
  
      setLocalProperty(track);
  
      // We want timestamp in milliseconds, but the client gives it in seconds.
      const dateAdded = track.dateAdded * 1000;
  
      const row = {
        uri: getRowUri(track.uri),
        track: track,
        dateAdded: dateAdded
      };
  
      rows[i] = row;
  
      delete track.dateAdded;
      delete track.addedBy;
    }
  
    return rows;
  }
  
  function setLocalProperty(track) {
    if (track.album && track.album.uri) {
      const albumType = liburi.from(track.album.uri).type;
      track.album.local = albumType === liburi.Type.LOCAL_ALBUM;
    }
    if (track.artists) {
      for (let i = 0, l = track.artists.length; i < l; i++) {
        const artist = track.artists[i];
        if (artist.uri) {
          const artistType = liburi.from(artist.uri).type;
          artist.local = artistType === liburi.Type.LOCAL_ARTIST;
        }
      }
    }
  }
  
  function getRows(model) {
    cosmos.get({ url: 'sp://local-files/v1/tracks' }, function (error, response) {
      if (error) {
        if (global.console) console.error(error);
        return;
      }
  
      model.update({
        rows: createRows(response.body)
      });
  
      cosmos.subscribe({ url: 'sp://local-files/v1/changes' }, function (subError, subResponse) {
        if (subError) {
          if (global.console) console.error(subError);
        } else {
          onChange(model, subResponse.body);
        }
      });
    });
  }
  
  function onChange(model, event) {
    switch (event.type) {
      case 'removed':
        onTracksRemoved(model, event.data);
        break;
      case 'added':
        onTracksAdded(model, event.data);
        break;
      case 'moved':
        onTracksMoved(model, event.data);
        break;
      case 'changed':
        onMetadataChanged(model, event.data);
        break;
      default:
        console.error('Unexpected event type', event.type);
        break;
    }
  }
  
  function onMetadataChanged(model, data) {
    // The positions we get could in some cases be for multiple ranges.
    //
    // We need to remove each range individually, so that we can re-add
    // the same range with updated data right after removing the range.
    // The reason we can't just update rows with new data is that the
    // metadata might cause the row to get a new URI (since local files
    // contain the metadata in the URI).
  
    const ranges = Range.fromIndices(data.positions);
  
    let startOfRangeInChangeset = 0;
  
    for (let rangeIndex = 0; rangeIndex < ranges.length; rangeIndex++) {
      const range = ranges[rangeIndex];
      const indices = range.toIndices();
      const rangeLength = range.length;
      const tracks = new Array(indices.length);
  
      for (let i = 0; i < rangeLength; i++) {
        tracks[i] = data.tracks[startOfRangeInChangeset + i];
      }
  
      // Remove range and re-add it with new data
      const removeOperations = listOperations.getRemoveOperations(indices);
      model.get('rows').update(removeOperations);
      addTracksToList(model, tracks, indices[0]);
  
      startOfRangeInChangeset += rangeLength;
    }
  }
  
  function onTracksMoved(model, data) {
    const moveOperations = listOperations.getMoveOperations(data.positions, data.position);
    model.get('rows').update(moveOperations);
  }
  
  function onTracksRemoved(model, data) {
    const removeOperations = listOperations.getRemoveOperations(data.positions);
    model.get('rows').update(removeOperations);
  }
  
  function onTracksAdded(model, data) {
    addTracksToList(model, data.tracks, data.position);
  }
  
  function addTracksToList(model, tracks, index) {
    const rows = createRows(tracks);
  
    const insertOperations = listOperations.getInsertOperations(rows, index);
    model.get('rows').update(insertOperations);
  }
  
  let idCounter = 1000;
  function getRowUri(trackUri) {
    return trackUri.replace('spotify:', `spotify:row:${(++idCounter).toString(36)}:`);
  }
  
  let registered = false;
  
  exports.register = function () {
    if (registered) return;
    registered = true;
    live.subscribe(regExp, 'wait', onWait);
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
    live.unsubscribe(regExp, 'wait', onWait);
  };
  
  exports.matches = regExp;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../live":172,"../live-list-operations":117,"../range2":204,"./util/cosmos":167,"mout/array/contains":434,"spotify-liburi":553}],138:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.play = play;
  const cosmos = require('../util/cosmos');
  const liburi = require('spotify-liburi');
  
  function play(contextUri, options, callback) {
    const uriObject = liburi.from(contextUri);
  
    const type = {
      [liburi.Type.COLLECTION_ALBUM]: 'album',
      [liburi.Type.COLLECTION_ARTIST]: 'artist'
    }[uriObject.type];
  
    const id = uriObject.getBase62Id();
  
    const sort = {
      [liburi.Type.COLLECTION_ALBUM]: 'discNumber,trackNumber',
      [liburi.Type.COLLECTION_ARTIST]: 'addTime DESC,discNumber,trackNumber'
    }[uriObject.type];
  
    const resolverUrl = `sp://core-collection/unstable/@/list/tracks/${type}/${id}/play?sort=${sort}`;
    const postObject = { url: resolverUrl };
    if (options) {
      postObject.body = options;
    }
  
    cosmos.post(postObject, callback);
  }
  
  },{"../util/cosmos":167,"spotify-liburi":553}],139:[function(require,module,exports){
  (function (global){
  'use strict';
  
  var _liveWrappedUri = require('../../live-wrapped-uri');
  
  var _liveWrappedUri2 = _interopRequireDefault(_liveWrappedUri);
  
  var _cosmosPlayer = require('./cosmos-player');
  
  var _cosmosPlayer2 = _interopRequireDefault(_cosmosPlayer);
  
  var _playerApi = require('./player-api');
  
  var _playerApi2 = _interopRequireDefault(_playerApi);
  
  var _playlistResolver = require('./playlist-resolver');
  
  var playlistResolver = _interopRequireWildcard(_playlistResolver);
  
  var _collectionResolver = require('./collection-resolver');
  
  var collectionResolver = _interopRequireWildcard(_collectionResolver);
  
  var _data = require('./data');
  
  var _data2 = _interopRequireDefault(_data);
  
  function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  /**
   * Request to play a list of track URIs.
   *
   * @param {Array.<string>} trackUris Array of track URIs.
   * @param {Object} playOptions An object with play options.
   * @param {Function=} callback Callback function.
   */
  /**
   * @module live-models/player/control
   * @private
   */
  function playTrackUris(tracks, playOptions, callback) {
    _playerApi2.default.play(tracks, playOptions, callback);
  }
  
  /**
   * Request to play a context through the cosmos track resolver.
   *
   * @param {string} contextUri Context URI.
   * @param {Object} playOptions An object with play options.
   * @param {Function=} callback Optional callback function.
   */
  function playFromResolver(contextUri, playOptions, callback) {
    _playerApi2.default.playFromResolver(contextUri, playOptions, callback);
  }
  
  /**
   * Play a context directly, only supported on context player
   *
   * @param {Object} context Context object that CP can deal with.
   * @param {Object} playOptions An object with play options.
   * @param {Function=} callback Optional callback function.
   */
  function playContext(context, playOptions, callback) {
    _playerApi2.default.playContext(context, playOptions, callback);
  }
  
  /**
   * Update the player with a new context without changing what is currently
   * playing.
   *
   * @param {Object} context Context object that CP can deal with.
   * @param {Function=} callback Optional callback function.
   */
  function updateContext(context, callback) {
    _playerApi2.default.updateContext(context, callback);
  }
  
  /**
   * Request to play a context through the playlist resolver.
   *
   * @param {string} contextUri Context URI.
   * @param {Object} playOptions An object with play options.
   * @param {Function=} callback Optional callback function.
   */
  function playFromPlaylistResolver(contextUri, playOptions, callback) {
    // For playlist playback we don't need to support ranges, so we'll
    // always just pick the first.
    const skipToIndex = {};
  
    // Todo: This should be cleaned up, we don't need a list of uids because we
    // never need ranges now. We do that stuff using pages.
    if (playOptions.uids) {
      skipToIndex.track_uid = playOptions.uids[0];
    } else if (playOptions.uid) {
      skipToIndex.track_uid = playOptions.uid;
    }
  
    // Todo: This should be cleaned up, we don't need a list of uids because we
    // never need ranges now. We do that stuff using pages.
    if (playOptions.uris) {
      skipToIndex.track_uri = playOptions.uris[0];
    } else if (playOptions.trackUri) {
      skipToIndex.track_uri = playOptions.trackUri;
    }
  
    const preparePlayOptions = {};
    if (Object.keys(skipToIndex).length) {
      preparePlayOptions.skip_to_index = skipToIndex;
    }
  
    const playOrigin = {
      view_uri: _liveWrappedUri2.default.getOriginUri(playOptions.context) || playOptions.context,
  
      // fills in source_start & source_end
      // example values: browse, playlist-owned-by-self-non-collaborative
      feature_identifier: playOptions.source,
  
      // fills in referer
      // example values: spotify:app:browse
      referrer_identifier: playOptions.referrerId,
  
      // fills in referrer version
      feature_version: playOptions.referrerVersion
    };
  
    const options = {
      prepare_play_options: preparePlayOptions,
      play_origin: playOrigin
    };
  
    playlistResolver.play(contextUri, options, callback);
  }
  
  /**
   * Request to play a context through the collection resolver.
   *
   * @param {string} contextUri Context URI.
   * @param {Object} playOptions An object with play options.
   * @param {Function=} callback Optional callback function.
   */
  function playFromCollectionResolver(contextUri, playOptions, callback) {
    const playOrigin = {
      view_uri: _liveWrappedUri2.default.getOriginUri(playOptions.context) || playOptions.context,
  
      // fills in source_start & source_end
      // example values: browse, playlist-owned-by-self-non-collaborative
      feature_identifier: playOptions.source,
  
      // fills in referer
      // example values: spotify:app:browse
      referrer_identifier: playOptions.referrerId,
  
      // fills in referrer version
      feature_version: playOptions.referrerVersion
    };
  
    const preparePlayOptions = {};
    if (playOptions.index !== null) {
      preparePlayOptions.skip_to = {
        track_index: playOptions.index
      };
    }
  
    const options = {
      prepare_play_options: preparePlayOptions,
      play_origin: playOrigin
    };
  
    collectionResolver.play(contextUri, options, callback);
  }
  
  /**
   * Request to play a single track.
   *
   * @param {string} uri The track URI.
   * @param {Object} playOptions An object with play options.
   * @param {Function=} callback Optional callback function.
   */
  function playTrack(uri, playOptions, callback) {
    playOptions.context = uri;
    playOptions.track = uri;
    playOptions.index = 0;
    playTrackUris([{ uri }], playOptions, callback);
  }
  
  /**
   * Request to play tracks found in the list of rows.
   *
   * @param {LiveList} rows A live list of rows with tracks.
   * @param {Object} playOptions An object with play options.
   * @param {Function=} callback Optional callback function.
   */
  function playRows(rows, playOptions, callback) {
    const { index, range } = playOptions;
  
    if (index === null && range) {
      _data2.default.getFirstPlayableRow(rows, range, (error, rowIndex) => {
        if (error) {
          if (global.console) console.error(error);
          if (callback) {
            callback(error);
          }
        } else {
          const tracks = _data2.default.getTracksFromRows(rows);
          playOptions.index = rowIndex;
  
          playTrackUris(tracks, playOptions, callback);
        }
      });
    } else {
      const tracks = _data2.default.getTracksFromRows(rows);
      playTrackUris(tracks, playOptions, callback);
    }
  }
  
  /**
   * Request to play artist context.
   *
   * @param {string} contextUri Context URI.
   * @param {Function=} callback Optional callback function.
   */
  function playFromArtist(uri, playOptions, callback) {
    playFromResolver(uri, playOptions, callback);
  }
  
  /**
   * Request to update the player with tracks from the provided rows list.
   * This will update the player silently without interrupting playback.
   *
   * @param {LiveList} rows A live list of rows with tracks.
   * @param {Object} playOptions An object with play options.
   * @param {Function=} callback Optional callback function.
   */
  function updateWithRows(rows, playOptions, callback) {
    const tracks = _data2.default.getTracksFromRows(rows);
  
    // Context Player matches on UIDs so let's not provide index
    delete playOptions.index;
    _playerApi2.default.update(tracks, playOptions, callback);
  }
  
  /**
   * Request to pause the playback.
   *
   * @param {Function=} callback Optional callback function.
   */
  function pause(callback) {
    _playerApi2.default.pause(callback);
  }
  
  /**
   * Request to resume the playback.
   *
   * @param {Function=} callback Optional callback function.
   */
  function resume(callback) {
    _playerApi2.default.resume(callback);
  }
  
  /**
   * Request to skip to the previous track.
   *
   * @param {Function=} callback Optional callback function.
   */
  function skipPrev(callback) {
    _cosmosPlayer2.default.skipPrev(callback);
  }
  
  /**
   * Request to skip to the next track.
   *
   * @param {Function=} callback Optional callback function.
   */
  function skipNext(callback) {
    _cosmosPlayer2.default.skipNext(callback);
  }
  
  exports.playTrack = playTrack;
  exports.playRows = playRows;
  exports.playFromResolver = playFromResolver;
  exports.playContext = playContext;
  exports.playFromArtist = playFromArtist;
  exports.updateContext = updateContext;
  exports.updateWithRows = updateWithRows;
  exports.pause = pause;
  exports.resume = resume;
  exports.skipPrev = skipPrev;
  exports.skipNext = skipNext;
  exports.playFromPlaylistResolver = playFromPlaylistResolver;
  exports.playFromCollectionResolver = playFromCollectionResolver;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../live-wrapped-uri":171,"./collection-resolver":138,"./cosmos-player":140,"./data":141,"./player-api":143,"./playlist-resolver":144}],140:[function(require,module,exports){
  /**
   * @module live-models/player/cosmos-player
   * @private
   */
  'use strict';
  
  const cosmos = require('../util/cosmos');
  
  const PLAYER_URI = 'sp://player/v2/main';
  
  const ACTIONS = {
    PLAY: 'play',
    PAUSE: 'pause',
    RESUME: 'resume',
    UPDATE: 'update',
    SKIP_PREV: 'skip_prev',
    SKIP_NEXT: 'skip_next'
  };
  
  /**
   * Request to pause the playback.
   *
   * @param {Function=} callback Optional callback function.
   */
  function pause(callback) {
    cosmos.post({ url: PLAYER_URI, body: { action: ACTIONS.PAUSE } }, callback);
  }
  
  /**
   * Request to resume the playback.
   *
   * @param {Function=} callback Optional callback function.
   */
  function resume(callback) {
    cosmos.post({ url: PLAYER_URI, body: { action: ACTIONS.RESUME } }, callback);
  }
  
  /**
   * Request to skip playback back.
   *
   * @param {Function=} callback Optional callback function.
   */
  function skipPrev(callback) {
    cosmos.post({ url: PLAYER_URI, body: { action: ACTIONS.SKIP_PREV } }, callback);
  }
  
  /**
   * Request to skip playback forward.
   *
   * @param {Function=} callback Optional callback function.
   */
  function skipNext(callback) {
    cosmos.post({ url: PLAYER_URI, body: { action: ACTIONS.SKIP_NEXT } }, callback);
  }
  
  /**
   * Request to get the current state of the cosmos player.
   *
   * @param {Function} callback Callback function.
   */
  function getState(callback) {
    cosmos.get({ url: PLAYER_URI }, function (error, response) {
      if (error) {
        callback(error);
      } else {
        callback(null, response.body);
      }
    });
  }
  
  /**
   * Request to get the current state of the cosmos player whenever the player
   * changes.
   *
   * @param {Function} callback Callback function.
   *
   * @return {Object} An object with a method to unsubscribe.
   */
  function subscribe(callback) {
    let subscription = cosmos.subscribe({ url: PLAYER_URI }, function (error, response) {
      if (error) {
        callback(error);
      } else {
        callback(null, response.body);
      }
    });
  
    return {
      unsubscribe: function () {
        if (!subscription) return;
        subscription.cancel();
        subscription = null;
      }
    };
  }
  
  exports.pause = pause;
  exports.resume = resume;
  exports.skipPrev = skipPrev;
  exports.skipNext = skipNext;
  exports.getState = getState;
  exports.subscribe = subscribe;
  
  },{"../util/cosmos":167}],141:[function(require,module,exports){
  (function (global){
  /**
   * @module live-models/player/data
   * @private
   */
  'use strict';
  
  const live = require('../../live');
  const Range = require('../../range2');
  
  const playerApi = require('./player-api');
  
  /**
   * Default values for play options.
   */
  const PLAY_OPTIONS_DEFAULTS = {
    page: null,
    uid: null,
    trackUri: null,
    index: null,
    source: 'unknown',
    reason: 'unknown'
  };
  
  /**
   * Get the first playable row from range in a list of rows.
   *
   * @param {LiveList} rows A live list of rows.
   * @param {Range} range A range object.
   * @param {Function} callback A callback where the second argument is the
   *     index of the first playable row. If no playable track is found, the
   *     index will be the first index in the range.
   */
  function getFirstPlayableRow(rows, range, callback) {
    let index = range.start;
  
    isRowPlayable(rows.get(index), function playableCallback(error, playable) {
      if (playable) {
        callback(null, index);
      } else {
        if (index === range.end - 1) {
          callback(null, range.start);
        } else {
          isRowPlayable(rows.get(++index), playableCallback);
        }
      }
    });
  }
  
  /**
   * Check if the row is playable.
   *
   * @param {LiveObject?} row A row live object. If this is falsy, the callback
   *     is returned with false immediately.
   * @param {Function} callback A callback where the second argument is the
   *     boolean flag for playable. The callback may run synchronously or
   *     asynchronously.
   */
  function isRowPlayable(row, callback) {
    if (!row) {
      callback(null, false);
    } else {
      row.query('track(playable)', function (error, data) {
        if (error) {
          callback(error);
        } else {
          callback(null, data.track.playable);
        }
      }, live.ASAP);
    }
  }
  
  /**
   * Get options for play actions based on a data object and other state
   * like referrer set on the player or application object.
   *
   * @param {Object} data A data object.
   * @param {Function} callback A callback, where the second argument is a data
   *     object with the options.
   */
  function getPlayOptions(data, callback) {
    getReferrer(data, function (error, viewUri, referrerId, featureId) {
      if (error) {
        callback(error);
      } else {
        Object.keys(PLAY_OPTIONS_DEFAULTS).forEach(function (key) {
          if (data[key] === undefined) {
            data[key] = PLAY_OPTIONS_DEFAULTS[key];
          }
        });
  
        if (data.range) {
          data.range = new Range(data.range[0], data.range[1]);
        }
  
        data.viewUri = viewUri;
        data.referrerId = referrerId;
        data.referrerVersion = getReferrerVersion();
        data.featureId = featureId;
  
        callback(null, data);
      }
    });
  }
  
  /**
   * Get a list of track objects from a live list of rows.
   *
   * @param {LiveList} rows A live list of rows.
   *
   * @return {Array.<Object>} Array of track objects. If a row is not set in the
   *     list, or if the row doesn't have a track object with a URI, an
   *     null value will be put in that position in the output array.
   */
  function getTracksFromRows(rows) {
    return rows.map((row, index) => {
      const trackLiveObject = row && row.get('track');
      if (trackLiveObject) {
        const trackData = trackLiveObject.serialize(1);
  
        const metadata = {};
  
        if (trackData.manifestId) {
          metadata['media.manifest_id'] = trackData.manifestId;
        }
  
        if (trackData.mediaTypeEnum) {
          metadata['media.type'] = trackData.mediaTypeEnum === 2 ? 'video' : 'audio';
        }
  
        return {
          uri: trackData.uri,
          uid: rows.keys[index],
          metadata: metadata
        };
      }
  
      return null;
    });
  }
  
  /**
   * Get the latest player state, in the format of the player live model.
   *
   * @param {Function} callback Callback function.
   */
  function getPlayerState(callback) {
    playerApi.getState(callback);
  }
  
  /**
   * Subscribe to the cosmos player to get the latest player state,
   * in the format of the player live model.
   *
   * @param {Function} callback Callback function.
   * @return {Object} An object with a method to unsubscribe.
   */
  function subscribeForPlayerState(callback) {
    return playerApi.subscribe(callback);
  }
  
  /**
   * Get the referrer for the player. This is usually the path back to the app in
   * the state where the playback started. It can be set by the user in the `referrer`
   * property of the player live object. If nothing is set there, it will generate
   * the referrer by combining the app URI with the app arguments.
   *
   * @param {Object} data An object with optional data for referrer.
   * @param {Function} callback A callback where the second argument is the referrer.
   */
  function getReferrer(data, callback) {
    const referrer = live('spotify:player').get('referrer');
    const referrerBaseUri = live('spotify:player').get('referrerBaseUri');
    if (referrer) {
      if (!referrerBaseUri) {
        console.warn(`referrer field is set on the player model (${referrer}), but referrerBaseUri isn't`);
      }
      callback(null, referrer, referrerBaseUri || '', '');
    } else {
      if (!data.viewUri || !data.referrerId) {
        live('spotify:application').get('appURI', 'arguments', function (error, appUri, args) {
          if (error) {
            callback(error);
          } else {
            const viewUri = data.viewUri || (args ? `${appUri}:${args}` : appUri);
            const referrerId = appUri;
            const featureId = '';
            callback(null, viewUri, referrerId, featureId);
          }
        });
      } else {
        callback(null, data.viewUri, data.referrerId, data.featureId);
      }
    }
  }
  
  /**
   * Get the referrer version. This is usually the version of the app. It can be set
   * by the user in the `version` property of the application live object. If nothing
   * is set there, it will use the default version '0.0.0'.
   *
   * @return {string} The version string.
   */
  function getReferrerVersion() {
    return global.__spotify.app_version;
  }
  
  exports.getReferrer = getReferrer;
  exports.getReferrerVersion = getReferrerVersion;
  exports.getFirstPlayableRow = getFirstPlayableRow;
  exports.isRowPlayable = isRowPlayable;
  exports.getPlayOptions = getPlayOptions;
  exports.getTracksFromRows = getTracksFromRows;
  exports.getPlayerState = getPlayerState;
  exports.subscribeForPlayerState = subscribeForPlayerState;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../live":172,"../../range2":204,"./player-api":143}],142:[function(require,module,exports){
  (function (global){
  /**
   * @module live-models/player
   */
  'use strict';
  
  var _messageProxy = require('../../../libs/message-proxy');
  
  const live = require('../../live');
  const URI = require('spotify-liburi');
  const CancellationTokenSource = require('../../../libs/cancellation-token-source').default;
  
  const playerApi = require('./player-api');
  const playbackControl = require('./control');
  const dataUtils = require('./data');
  const updatesUtils = require('./updates');
  const sortUriUtils = require('../../live-sort-uri');
  const filterUriUtils = require('../../live-filter-uri');
  const createStationPlayContext = require('../station/create-play-context');
  const createStationPlayOptions = require('../station/create-play-options');
  const isStationUri = require('../station').isStationUri;
  
  const TYPE_TRACK = 'track';
  const TYPE_ROWS = 'rows';
  const TYPE_CP_RESOLVER_PLAYLIST = 'context-player-resolver-playlist';
  const TYPE_CP_RESOLVER_COLLECTION = 'context-player-resolver-collection';
  const TYPE_CP_CONTEXT = 'context-player-context';
  const TYPE_CONTEXT_OBJECT = 'context-object';
  const TYPE_STATION_CONTEXT = 'context-station-context';
  const TYPE_CP_ARTIST_CONTEXT = 'context-player-artist';
  const TYPE_CP_ALBUM_CONTEXT = 'context-player-album';
  const TYPE_CONTEXT_EPISODE = 'context-episode';
  const TYPE_CONTEXT_SHOW = 'context-show';
  
  const cancellationTokenSource = new CancellationTokenSource();
  
  // This data store will keep context objects keyed by context URI. The values
  // are context objects in the format of spotify-player Context:
  // https://ghe.spotify.net/spotify-sdk/spotify-player/blob/master/lib/v2/types.js#L164
  const contextDataStore = {};
  
  /**
   * Handler for when properties are requested from the model.
   *
   * @param {LiveObject} model The player live object.
   */
  function onWait(model) {
    updateState(model);
  }
  
  /**
   * Handler for when the model needs to be updated by polling the player.
   *
   * @param {LiveObject} model The player live object.
   */
  function onSync(model) {
    updateState(model);
  }
  
  /**
   * Handler for when the real player is updated.
   *
   * @param {Error?} error An error object, or null.
   * @param {Object} data Data object in the format of this live model.
   */
  function onPlayerStateUpdate(error, data) {
    if (error) {
      if (global.console) console.error(error);
      return;
    }
    const model = live('spotify:player');
    if (model.get('pending')) return;
    model.update(data);
  }
  
  /**
   * Handler for when the model receives the 'update' event.
   *
   * @param {LiveObject} model The player live object.
   */
  function onUpdate(model) {
    const index = model.get('index') && model.get('index').get('track');
    if (index !== null) {
      updatesUtils.setPlayingIndex(index);
    }
  
    updatesUtils.clean(model);
  }
  
  /**
   * Handler for when the model receives the 'play' event.
   *
   * @param {LiveObject} model The player live object.
   * @param {Object} event The event object with data about what to play.
   *     Can contain the following properties:
   *       `context` (string, context URI)
   *       `index` (number, index within context to play)
   *       `uid` (string, uid within context to play)
   *       `page` (number, index for the context player page)
   *       `range` (array of two values, start and end index)
   *       `uids` (array of uids representing an index or a range)
   *       `uris` (array of uris connected to the uids)
   *       `source` (string)
   *       `reason` (string)
   */
  function onPlay(model, event) {
    const context = event.context;
    if (!context) return;
  
    // The `index` property might be `null`, which should mean that no specific
    // index should be played.
    if (typeof event.index !== 'number') {
      delete event.index;
    }
  
    // Cancel all previous play/update context requests
    cancellationTokenSource.cancel();
    const cancellationToken = cancellationTokenSource.token();
  
    // Update the model optimistically
    model.update(playerApi.addContext({
      isPlaying: true,
      isPaused: false,
      index: event.index === undefined ? null : { page: 0, track: event.index },
      track: event.trackUri === undefined ? null : { uri: event.trackUri },
      contextUpdatedCrossFrame: false
    }, context));
  
    // Get options for the play action based on event data and other state
    // Todo: getPlayOptions creates options that fit the cosmos/v1 implementation.
    // We could probably do this a bit later from cosmos-player.js
    dataUtils.getPlayOptions(event, function (error, options) {
      if (error || !options.context) {
        updateState(model); // Revert state
      } else {
        // Canonical contexts can have sorted and/or filtered state that needs
        // to be respected client-wide. We therefore query for the correct variant
        // to be played.
        // For spotify:internal uris on the other hand, we don't look for variants.
        // This would also be the case for contexts that already represent variants.
        // Please see docs for details.
        if (/^spotify:internal:/.test(options.context)) {
          playWithOptions(model, cancellationToken, options);
        } else {
          if (live('spotify:list-variant').get('implemented')) {
            live(options.context).query('listVariant(currentUri, sortUri)', function (queryError, data) {
              if (queryError) {
                updateState(model); // Revert state
              } else {
                // Abort if a new play request has happened since this request started
                if (!cancellationToken.isCanceled()) {
                  options.context = data.listVariant.currentUri;
                  playWithOptions(model, cancellationToken, options);
                }
              }
            });
          } else {
            playWithOptions(model, cancellationToken, options);
          }
        }
      }
    });
  }
  
  /**
   * Handler for when the model receives the 'pause' event.
   *
   * @param {LiveObject} model The player live object.
   */
  function onPause(model) {
    if (model.get('isPlaying') === false || model.get('isPaused') === true) {
      return;
    }
  
    model.update({ isPaused: true });
  
    playbackControl.pause(function (error) {
      if (error) {
        model.update({ isPaused: false });
      }
    });
  }
  
  /**
   * Handler for when the model receives the 'resume' event.
   *
   * @param {LiveObject} model The player live object.
   */
  function onResume(model) {
    if (model.get('isPlaying') === true && model.get('isPaused') === false) {
      return;
    }
  
    model.update({ isPaused: false });
  
    playbackControl.resume(function (error) {
      if (error) {
        model.update({ isPaused: true });
      }
    });
  }
  
  /**
   * Handler for when the model receives the 'skip-previous' event.
   *
   * @param {LiveObject} model The player live object.
   */
  function onSkipPrev() {
    // Skip prev often starts playback, but not when you're on the first track.
    // Therefor no optimistic update of 'playing' here.
    playbackControl.skipPrev();
  }
  
  /**
   * Handler for when the model receives the 'skip-next' event.
   *
   * @param {LiveObject} model The player live object.
   */
  function onSkipNext() {
    // Skip next often starts playback, but not when you're on the last track.
    // Therefor no optimistic update of 'playing' here.
    playbackControl.skipNext();
  }
  
  /**
   * Handler for when the model receives the 'update-context' event.
   *
   * @param {LiveObject} model The player live object.
   * @param {Object} event An event object with a `context` property (string).
   */
  function onUpdateContext(model, event) {
    const contextUri = event.context;
    if (!contextUri) return;
  
    // Cancel all previous play/update context requests
    cancellationTokenSource.cancel();
    const cancellationToken = cancellationTokenSource.token();
  
    const currentIndex = model.get('index') && model.get('index').get('track');
    const currentContext = model.get('variant');
  
    // Update player model optimistically with context and no index.
    // We can't know the index yet, so we must set it to nothing to
    // not get wrong highlights.
    model.update(playerApi.addContext({
      index: null,
      pending: true,
      contextUpdatedCrossFrame: false
    }, contextUri));
  
    // We need the rows list to be able to get the index and play the list.
    live(contextUri).get('rows', function (error, rowsList) {
      if (error) {
        model.update({ pending: false });
        if (global.console) console.error(error);
        return;
      }
  
      const options = { context: contextUri };
      dataUtils.getPlayOptions(options, function (playOptionsError, playOptions) {
        // Abort if a new play request has happened since this request started
        if (cancellationToken.isCanceled()) {
          return;
        }
  
        // Store which row is currently playing, so that we can get the index
        // of the playing row in another list.
        updatesUtils.setPlayingIndex(currentIndex, currentContext.get('rows'));
  
        // Find the index of the currently playing track in new list, to do an
        // optimistic update. This is not optional, since the actual player only appends
        // the next context and will not send an update until the next skip.
        // (If it does, because of play/pause for example, it will still report the old
        // list that we're not showing in the UI)
  
        // At the same time, find the index that we will use to actually update the playback
        const updateIndices = updatesUtils.getPlayingAndUpdateIndicesInList(rowsList);
        playOptions.index = updateIndices.update;
        if (updateIndices.playing !== null) {
          model.update({ index: { page: 0, track: updateIndices.playing } });
        }
  
        if (model.get('playOrigin') && model.get('playOrigin').get('referrerIdentifier') !== playOptions.referrerId) {
          model.update({ contextUpdatedCrossFrame: true });
        }
  
        playbackControl.updateWithRows(rowsList, playOptions, function () {
          model.update({ pending: false });
          // The state is updated both for success and error cases.
          updateState(model);
        });
  
        updatesUtils.listen(rowsList, playOptions, function (updateOptions) {
          playbackControl.updateWithRows(rowsList, updateOptions, function (updateError) {
            if (updateError) {
              updateState(model); // Revert state
            }
          });
        });
      });
    });
  }
  
  /**
   * Handler for when the model receives the 'update-show-context' event.
   *
   * @param {LiveObject} model The player live object.
   * @param {Object} event An event object with a `context` property (string).
   */
  function onUpdateShowContext(model, event) {
    const contextUri = event.context;
    if (!contextUri) return;
  
    // Cancel all previous play/update context requests
    cancellationTokenSource.cancel();
    const cancellationToken = cancellationTokenSource.token();
  
    // Update player model optimistically with context and no index.
    // We can't know the index yet, so we must set it to nothing to
    // not get wrong highlights.
    model.update(playerApi.addContext({}, contextUri));
  
    const showQueryTimeout = 3000;
    live(contextUri).query('show(name, uri), rows(track(uri, name, mediaTypeEnum))', (error, data) => {
      // Abort if a new play request has happened since this request started
      if (cancellationToken.isCanceled()) {
        return;
      } else if (error) {
        updateState(model); // Revert state
      } else {
        const context = {
          uri: data.show.uri,
          pages: [{
            tracks: data.rows.map(({ track }) => ({
              uri: track.uri,
              uid: track.uri,
              metadata: {
                'title': track.name,
                'album_title': data.show.name,
                'media.type': track.mediaTypeEnum === 1 ? 'audio' : 'video',
                // manifestId is not specifically queried above, since audio
                // tracks don't have that property, so it would then just stall on
                // that query. Querying for anything for the show will request all
                // the show data, so it should be available anyway.
                'media.manifest_id': live(track.uri).get('manifestId') || ''
              }
            }))
          }],
          metadata: {
            'zelda.context_uri': contextUri
          }
        };
  
        playbackControl.updateContext(context, updateContextError => {
          if (updateContextError) {
            updateState(model); // Revert state
          }
        });
      }
    }, showQueryTimeout);
  }
  
  /**
   * Request to start playback based on passed options.
   * If the request fails, it will revert the player model to the latest
   * state.
   *
   * @param {LiveObject} model The player live object.
   * @param {CancellationToken} cancellationToken
   * @param {Object} options Object with options and data of what to play.
   */
  function playWithOptions(model, cancellationToken, options) {
    const contextUri = options.context;
    const resolverType = getPlaybackResolverType(contextUri);
  
    if (resolverType === TYPE_TRACK) {
      playbackControl.playTrack(contextUri, options, function (error) {
        if (error) {
          updateState(model); // Revert state
        }
      });
    } else if (resolverType === TYPE_CP_ALBUM_CONTEXT) {
      playbackControl.playFromResolver(contextUri, options, function (error) {
        if (error) {
          updateState(model); // Revert state
        }
      });
    } else if (resolverType === TYPE_ROWS) {
      const isSortedList = sortUriUtils.isValid(contextUri);
  
      // If it's a sorted list that is played by rows, we want to request all the track
      // URIs of the sorted list and then play that list of tracks. We will wait for all
      // tracks to be fetched and sorted, to allow clicking play on a media object in
      // Browse for example and have it play the stored sorted variant of the playlist.
      if (isSortedList) {
        getSortedRows(contextUri, function (error, rowsList) {
          if (error) {
            updateState(model); // Revert state
          } else {
            // Abort if a new play request has happened since this request started
            if (!cancellationToken.isCanceled()) {
              playRows(model, rowsList, options);
            }
          }
        });
  
        // Play any other context from the rows list
      } else {
        live(contextUri).query('rows(track(uri))', function (err) {
          if (!err && !cancellationToken.isCanceled()) {
            playRows(model, live(contextUri).get('rows'), options);
          }
        });
      }
    } else if (resolverType === TYPE_CP_RESOLVER_PLAYLIST) {
      playbackControl.playFromPlaylistResolver(contextUri, options, function (error) {
        if (error) {
          updateState(model); // Revert state
        }
      });
    } else if (resolverType === TYPE_CP_RESOLVER_COLLECTION) {
      playbackControl.playFromCollectionResolver(contextUri, options, function (error) {
        if (error) {
          updateState(model); // Revert state
        }
      });
    } else if (resolverType === TYPE_CONTEXT_OBJECT) {
      const context = contextDataStore[contextUri];
      playbackControl.playContext(context, options, function (error) {
        if (error) {
          updateState(model); // Revert state
        }
      });
    } else if (resolverType === TYPE_CP_CONTEXT) {
      const context = {
        url: `context://${contextUri}`,
        entity_uri: contextUri
      };
  
      playbackControl.playContext(context, options, function (error) {
        if (error) {
          updateState(model); // Revert state
        }
      });
    } else if (resolverType === TYPE_STATION_CONTEXT) {
      const station = live(contextUri);
      station.query('rows', function (err) {
        if (!err) {
          playbackControl.playContext(createStationPlayContext(station), createStationPlayOptions(station, options), function (error) {
            if (error) {
              updateState(model); // Revert state
            }
          });
          // Post message to create station in zlink
          (0, _messageProxy.messageProxy)({
            method: 'POST',
            uri: 'station-create',
            target: 'top',
            body: { stationUri: contextUri }
          });
        }
      });
    } else if (resolverType === TYPE_CP_ARTIST_CONTEXT) {
      playbackControl.playFromArtist(contextUri, options, function (error) {
        if (error) {
          updateState(model); // Revert state
        }
      });
    } else if (resolverType === TYPE_CONTEXT_EPISODE) {
      const episodeQueryTimeout = 3000;
  
      live(contextUri).query('name, mediaTypeEnum, show(name)', function (error, data) {
        if (error) {
          updateState(model); // Revert state
        } else {
          const context = {
            uri: contextUri,
            pages: [{
              tracks: [{
                uri: contextUri,
                metadata: {
                  'title': data.name,
                  'album_title': data.show.name,
                  'media.type': data.mediaTypeEnum === 1 ? 'audio' : 'video',
                  'media.manifest_id': live(contextUri).get('manifestId') || ''
                }
              }]
            }],
            restrictions: {
              disallow_toggling_repeat_context_reasons: ['disallow-video'],
              disallow_toggling_repeat_track_reasons: ['disallow-video'],
              disallow_toggling_shuffle_reasons: ['disallow-video']
            }
          };
  
          if (!options.player_options_override) {
            options.player_options_override = {};
          }
          // Avoid repeating context when playing episode
          // This might be removed in future when we have proper shows support
          options.player_options_override.repeating_context = false;
          options.player_options_override.repeating_track = false;
  
          playbackControl.playContext(context, options, function (playContextError) {
            if (playContextError) {
              updateState(model); // Revert state
            }
          });
        }
      }, episodeQueryTimeout);
    } else if (resolverType === TYPE_CONTEXT_SHOW) {
      const showQueryTimeout = 3000;
      live(contextUri).query('show(name, uri), rows(track(uri, name, mediaTypeEnum))', (error, data) => {
        if (error) {
          updateState(model); // Revert state
        } else {
          const context = {
            uri: data.show.uri,
            pages: [{
              tracks: data.rows.map(({ track }) => ({
                uri: track.uri,
                uid: track.uri,
                metadata: {
                  'title': track.name,
                  'album_title': data.show.name,
                  'media.type': track.mediaTypeEnum === 1 ? 'audio' : 'video',
  
                  // manifestId is not specifically queried above, since audio
                  // tracks don't have that property, so it would then just stall on
                  // that query. Querying for anything for the show will request all
                  // the show data, so it should be available anyway.
                  'media.manifest_id': live(track.uri).get('manifestId') || ''
                }
              }))
            }],
            restrictions: {
              disallow_toggling_repeat_context_reasons: ['disallow-show'],
              disallow_toggling_repeat_track_reasons: ['disallow-show'],
              disallow_toggling_shuffle_reasons: ['disallow-show']
            },
            metadata: {
              'zelda.context_uri': contextUri
            }
          };
  
          if (!options.player_options_override) {
            options.player_options_override = {};
          }
  
          options.player_options_override.repeating_context = false;
          options.player_options_override.repeating_track = false;
          options.player_options_override.shuffling_context = false;
  
          playbackControl.playContext(context, options, playContextError => {
            if (playContextError) {
              updateState(model); // Revert state
            }
          });
        }
      }, showQueryTimeout);
    }
  }
  
  /**
   * Request to start playback based on passed rows list and options.
   * If the request fails, it will revert the player model to the latest
   * state.
   *
   * @param {LiveObject} model The player live object.
   * @param {LiveList} rowsList The rows live list.
   * @param {Object} options Object with options and data of what to play.
   */
  function playRows(model, rowsList, options) {
    playbackControl.playRows(rowsList, options, function (error) {
      if (error) {
        updateState(model); // Revert state
      }
  
      updatesUtils.listen(rowsList, options, function (updateOptions) {
        playbackControl.updateWithRows(rowsList, updateOptions, function (updateError) {
          if (updateError) {
            updateState(model); // Revert state
          }
        });
      });
    });
  }
  
  /**
   * Get the type of playback resolver to use for a certain context URI.
   *
   * @param {string} contextUri Context URI.
   *
   * @return {string} A resolver type.
   */
  function getPlaybackResolverType(contextUri) {
    const contextType = URI.from(contextUri).type;
  
    const isTrack = contextType === URI.Type.TRACK;
    const isLocal = contextType === URI.Type.LOCAL;
    const isPlaylist = contextType === URI.Type.PLAYLIST || contextType === URI.Type.PLAYLIST_V2;
    const isCollectionAlbum = contextType === URI.Type.COLLECTION_ALBUM;
    const isCollectionArtist = contextType === URI.Type.COLLECTION_ARTIST;
    const isPlaylistFolder = contextType === URI.Type.FOLDER;
    const isArtist = contextType === URI.Type.ARTIST;
    const isAlbum = contextType === URI.Type.ALBUM;
    const isSortedList = sortUriUtils.isValid(contextUri);
    const isFilteredList = filterUriUtils.isValid(contextUri);
    const isStation = isStationUri(contextUri);
    const isEpisode = contextType === URI.Type.EPISODE;
    const isShow = contextType === URI.Type.SHOW;
  
    if (isTrack || isLocal) {
      return TYPE_TRACK;
    }
  
    if (isPlaylist) {
      return TYPE_CP_RESOLVER_PLAYLIST;
    }
  
    if (isCollectionAlbum || isCollectionArtist) {
      return TYPE_CP_RESOLVER_COLLECTION;
    }
  
    if (isPlaylistFolder) {
      return TYPE_CP_CONTEXT;
    }
  
    if (isStation) return TYPE_STATION_CONTEXT;
  
    let originUri;
  
    if (isSortedList) {
      originUri = sortUriUtils.parse(contextUri).originUri;
  
      // If the sorted URI is in fact a filtered URI, the origin URI that _that_ is based on
      // needs to be taken into account when judging the origin context type.
      if (filterUriUtils.isValid(originUri)) {
        originUri = filterUriUtils.parse(originUri).originUri;
      }
  
      if (URI.isPlaylistV1OrV2(originUri)) {
        return TYPE_CP_RESOLVER_PLAYLIST;
      }
  
      if (URI.isShow(originUri)) {
        return TYPE_CONTEXT_SHOW;
      }
  
      return TYPE_ROWS;
    }
  
    if (isFilteredList) {
      // If the sorted URI is in fact a filtered URI, the origin URI that _that_ is based on
      // needs to be taken into account when judging the origin context type.
      originUri = filterUriUtils.parse(contextUri).originUri;
  
      if (URI.isPlaylistV1OrV2(originUri)) {
        return TYPE_CP_RESOLVER_PLAYLIST;
      }
  
      return TYPE_ROWS;
    }
  
    if (isEpisode) {
      return TYPE_CONTEXT_EPISODE;
    }
  
    if (isShow) {
      return TYPE_CONTEXT_SHOW;
    }
  
    const contextData = contextDataStore[contextUri];
    if (contextData) {
      return TYPE_CONTEXT_OBJECT;
    }
  
    // If it's a different URI, check if we have rows
    const rowsList = live(contextUri).get('rows');
    if (rowsList && rowsList.length) {
      return TYPE_ROWS;
    }
  
    // If it's an artist context but context object or rows were not defined
    if (isArtist) {
      return TYPE_CP_ARTIST_CONTEXT;
    }
  
    if (isAlbum) {
      return TYPE_CP_ALBUM_CONTEXT;
    }
  
    // This should not happen ;)
    return null;
  }
  
  /**
   * Get the live list for the rows of the sorted URI.
   *
   * @param {string} sortUri The URI of the sorted list.
   * @param {Function} callback Callback function where second argument is the
   *     sorted live list.
   */
  function getSortedRows(sortUri, callback) {
    const playlistUri = sortUriUtils.getOriginUri(sortUri);
  
    // Get the rows list for the regular playlist.
    // We could have queried for the track of every row, but by not querying we
    // avoid the conversions from live objects to plain objects.
    live(playlistUri).get('rows', function (error, rows) {
      if (error) {
        callback(error);
      } else {
        // Get the live models for all rows.
        rows.get(0, rows.length, function (rowError) {
          if (rowError) {
            callback(error);
          } else {
            // When we know we have the rows, we can safely sort the list
            live(sortUri).get('rows', function (liveError, rowsList) {
              if (liveError) {
                callback(liveError);
              } else {
                // Finally return the sorted rows list
                callback(null, rowsList);
              }
            });
          }
        });
      }
    });
  }
  
  /**
   * Update the player model with the current data of the cosmos player model.
   *
   * @param {LiveObject} model The player live object.
   */
  function updateState(model) {
    dataUtils.getPlayerState(function (error, data) {
      if (error) {
        if (global.console) console.error(error);
        return;
      }
      model.update(data);
    });
  }
  
  const regExp = exports.matches = /^spotify:player$/;
  let registered;
  let playerSubscription;
  let cancelPlayerErrorSubscription;
  
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    live.subscribe(regExp, 'play', onPlay);
    live.subscribe(regExp, 'pause', onPause);
    live.subscribe(regExp, 'resume', onResume);
    live.subscribe(regExp, 'skip-previous', onSkipPrev);
    live.subscribe(regExp, 'skip-next', onSkipNext);
    live.subscribe(regExp, 'update-context', onUpdateContext);
    live.subscribe(regExp, 'update-show-context', onUpdateShowContext);
    live.subscribe(regExp, 'update', onUpdate);
    live.subscribe(regExp, 'wait', onWait);
    live.subscribe(regExp, 'sync-position', onSync);
  
    playerSubscription = dataUtils.subscribeForPlayerState(onPlayerStateUpdate);
  
    // This is a workaround the fact that some errors aren't reported to the
    // callbacks passed to Player API when we intereact normally with it.
    //
    // Here we observe all errors. If we know, by means of observation, that an
    // error is not being handled properly elsewhere, we revert the live-model
    // state when the error is reported here.
    cancelPlayerErrorSubscription = playerApi.onError(function (error, response) {
      if (error) {
        console.error(error);
        return;
      }
      const body = response.getJSONBody();
      switch (body.error) {
        case 'one_track_unplayable':
        case 'one_track_unplayable_auto_stopped':
        case 'all_tracks_unplayable_auto_stopped':
          // Revert state
          exports._syncPlayerModelState();
          break;
        default:
          break;
      }
    });
  };
  
  exports._syncPlayerModelState = function () {
    const model = live('spotify:player');
    updateState(model);
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    live.unsubscribe(regExp, 'play', onPlay);
    live.unsubscribe(regExp, 'pause', onPause);
    live.unsubscribe(regExp, 'resume', onResume);
    live.unsubscribe(regExp, 'skip-previous', onSkipPrev);
    live.unsubscribe(regExp, 'skip-next', onSkipNext);
    live.unsubscribe(regExp, 'update-context', onUpdateContext);
    live.unsubscribe(regExp, 'update-show-context', onUpdateShowContext);
    live.unsubscribe(regExp, 'update', onUpdate);
    live.unsubscribe(regExp, 'wait', onWait);
    live.unsubscribe(regExp, 'sync-position', onSync);
  
    playerSubscription.unsubscribe();
    if (cancelPlayerErrorSubscription) {
      cancelPlayerErrorSubscription();
    }
    playerSubscription = null;
  };
  
  exports.setContextData = function (contextUri, contextData) {
    contextDataStore[contextUri] = contextData;
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../../libs/cancellation-token-source":54,"../../../libs/message-proxy":187,"../../live":172,"../../live-filter-uri":116,"../../live-sort-uri":170,"../station":157,"../station/create-play-context":158,"../station/create-play-options":159,"./control":139,"./data":141,"./player-api":143,"./updates":145,"spotify-liburi":553}],143:[function(require,module,exports){
  (function (global){
  'use strict';
  
  var _data = require('./data');
  
  var _data2 = _interopRequireDefault(_data);
  
  var _cosmos = require('../util/cosmos');
  
  var _cosmos2 = _interopRequireDefault(_cosmos);
  
  var _liveSortUri = require('../../live-sort-uri');
  
  var _liveSortUri2 = _interopRequireDefault(_liveSortUri);
  
  var _liveFilterUri = require('../../live-filter-uri');
  
  var _liveFilterUri2 = _interopRequireDefault(_liveFilterUri);
  
  var _v = require('spotify-player/lib/v2');
  
  var _v2 = _interopRequireDefault(_v);
  
  var _spotifyCosmosApi = require('spotify-cosmos-api');
  
  var _spotifyCosmosApi2 = _interopRequireDefault(_spotifyCosmosApi);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  /**
   * @module live-models/player/player-api
   * @private
   */
  function getPlayer(optionalPlayOptions, callback) {
    const playOptions = optionalPlayOptions || {};
    
    _data2.default.getReferrer(playOptions, (error, viewUri, referrerId, featureId) => {
      if (error) {
        callback(error);
        return;
      }
  
      // One of a fixed list of features, since this is used for logging. It should
      // be the readable name of the feature, for example "radio", "album", "playlist"
      // and it should be the same for a given feature across platforms.
      // The list is probably this:
      // https://ghe.spotify.net/datainfra/log-parser/blob/master/spotify/log_parser/messages_specs.py#L29
      let featureIdentifier = featureId;
      const featureVersion = _data2.default.getReferrerVersion();
  
      if (!featureIdentifier) {
        const appManifest = global.__spotify && global.__spotify.app_manifest;
        if (appManifest) {
          featureIdentifier = appManifest.BundleIdentifier || null;
        }
      }
      
      // Hard coded overwrites for apps where we needed to choose a new
      // BundleIdentifier for compat reasons
      // Todo: This is incomplete and would better be fixed by letting features
      // provide values specifically for this. See KM-6973.
      const radioFeatureIdentifier = 'radio';
      const overwrites = {
        'station': radioFeatureIdentifier,
        'stations': radioFeatureIdentifier,
        'radio-hub': radioFeatureIdentifier,
        'daily-mix-hub': radioFeatureIdentifier
      };
  
      if (featureIdentifier && overwrites[featureIdentifier]) {
        featureIdentifier = overwrites[featureIdentifier];
      }
  
      const options = referrerId ? { referrerIdentifier: referrerId } : null;
  
      const player = new _v2.default.Player(_spotifyCosmosApi2.default.resolver, viewUri, featureIdentifier, featureVersion, options);
      callback(player);
    });
  }
  
  function play(tracks, playOptions, callback) {
    getPlayer(playOptions, function (p) {
      const context = {
        pages: [{ tracks }],
        metadata: {
          'zelda.context_uri': playOptions.context
        },
        entity_uri: getOriginUri(playOptions.context)
      };
  
      const options = {};
      if (typeof playOptions.index === 'number') {
        options.skip_to_index = {
          page: 0,
          track: playOptions.index
        };
      } else if (playOptions.uid || playOptions.uri) {
        options.skip_to = {};
        if (playOptions.uid) {
          options.skip_to.track_uid = playOptions.uid;
        } else if (playOptions.uri) {
          options.skip_to.track_uri = playOptions.uri;
        }
      }
  
      p.play(context, options, callback);
    });
  }
  
  function playContext(context, playOptions, callback) {
    if (playOptions.uid || playOptions.uri || playOptions.page) {
      playOptions.skip_to = {};
      if (playOptions.uid) {
        playOptions.skip_to.track_uid = playOptions.uid;
      } else if (playOptions.uri) {
        playOptions.skip_to.track_uri = playOptions.uri;
      }
  
      // Together with UID send page_index property, so Connect can
      // figure out where to look for a track that needs to
      // be played. (Connect doesn't support UIDs at the moment).
      if (playOptions.page) {
        playOptions.skip_to.page_index = playOptions.page;
      }
    } else if (playOptions.range) {
      playOptions.skip_to_index = {
        page: 0,
        track: playOptions.range.start
      };
    } else if (typeof playOptions.index === 'number') {
      playOptions.skip_to_index = {
        page: 0,
        track: playOptions.index
      };
    }
  
    getPlayer(playOptions, function (p) {
      p.play(context, playOptions || {}, callback);
    });
  }
  
  function updateContext(context, callback) {
    getPlayer(null, function (p) {
      p.update(context, callback);
    });
  }
  
  function update(tracks, playOptions, callback) {
    getPlayer(playOptions, function (p) {
      // Todo: When currently playing from the new playlist and folder resolver,
      // uids provided by the core resolver need to be used.
  
      const context = {
        pages: [{ tracks }],
        metadata: {
          'zelda.context_uri': playOptions.context
        },
        entity_uri: getOriginUri(playOptions.context)
      };
  
      p.update(context, callback);
    });
  }
  
  function playFromResolver(contextUri, playOptions, callback) {
    getPlayer(playOptions, player => {
      _cosmos2.default.sanitizeURL(contextUri, (error, uri) => {
        if (error) {
          if (callback) callback(error);
          return;
        }
  
        const context = {
          uri,
          url: `context://${encodeURI(uri)}`
        };
  
        let options = null;
        if (typeof playOptions.index === 'number') {
          options = {
            skip_to_index: {
              page: 0,
              track: playOptions.index
            }
          };
        } else if (playOptions.trackUri) {
          options = {
            skip_to_index: {
              track_uri: playOptions.trackUri
            }
          };
        }
  
        if (typeof playOptions.seekTo === 'number') {
          options.seek_to = playOptions.seekTo;
        }
  
        player.play(context, options, callback);
      });
    });
  }
  
  let cancelSubscribe;
  
  function subscribe(callback, options = {}) {
    cancelSubscribe = false;
  
    let subscription;
    getPlayer(null, p => {
      if (cancelSubscribe) return;
      subscription = p.subscribe((error, response) => {
        if (error) {
          callback(error);
        } else {
          callback(null, addContext(response.getJSONBody()));
        }
      }, options);
    });
  
    return {
      unsubscribe: () => {
        cancelSubscribe = true;
        if (!subscription) return;
        subscription.cancel();
        subscription = null;
      }
    };
  }
  
  let onErrorSubscription;
  
  function onError(callback) {
    getPlayer(null, p => {
      onErrorSubscription = p.onError(callback);
    });
  
    return () => {
      if (onErrorSubscription) {
        onErrorSubscription.cancel();
        onErrorSubscription = null;
      }
    };
  }
  
  function getState(callback) {
    getPlayer(null, p => {
      p.getState((error, response) => {
        if (error) {
          callback(error, null);
        } else {
          callback(null, addContext(response.getJSONBody()));
        }
      });
    });
  }
  
  function pause(callback) {
    getPlayer(null, p => {
      p.pause(callback);
    });
  }
  
  function resume(callback) {
    getPlayer(null, p => {
      p.resume(callback);
    });
  }
  
  /**
   * Adds the context to a given player state.
   *
   * @param {Object} data Input data in the format of the cosmos player live model (v2).
   * @param {string?} optUri Optional URI to be used in first place.
   * @return {Object} Data object.
   */
  function addContext(data, optUri) {
    const uri = optUri || // We provide the URI of the context.
    data.context_metadata['zelda.context_uri'] || // Best case scenario.
    data.context_uri || // No special context, use the standard one.
    data.track && data.track.uri || // If a track is present, use it.
    null; // Nothing found; there's nothing being played.
  
    const context = getOriginUri(uri);
  
    // Avoid writing it into the context. People should always use the context object.
    delete data.context_uri;
  
    if (uri) {
      data.variant = { uri };
      data.context = { uri: context };
    } else {
      data.variant = null;
      data.context = null;
    }
  
    // Track and index are flagged as "optional", meaning that when the player is stopped
    // will not be returned. Because of the way "live" works (waiting for properties until
    // they're set), it could happen that we wait forever for "track" and "index". For
    // aoviding this, we set them both to "null".
    data.track = data.track || null;
    data.index = data.index || null;
  
    if (data.track) {
      data.uid = data.track.uid;
      delete data.track.uid;
    } else {
      data.uid = null;
    }
  
    return removeUnderscores(data);
  }
  
  /**
   * Returns the original URI from a given filtered & sorted URI. For example, given
   * this URL:
   *
   * spotify:internal:sortlist:desc:track(name):internal:filterlist:playable%20eq%20true:user:myuser:playlist:7h5o3lf1ithsxec0wgjznj
   *
   * This method would return:
   *
   * spotify:user:myuser:playlist:7h5o3lf1ithsxec0wgjznj
   *
   * @param {string} uri An internal URI format with sort and/or query parts.
   * @return {string} The original URI.
   */
  function getOriginUri(uri) {
    if (!uri) {
      return uri;
    }
  
    const sorted = _liveSortUri2.default.parse(uri);
    let newUri = uri;
    if (sorted) {
      newUri = sorted.originUri;
    }
  
    const filtered = _liveFilterUri2.default.parse(newUri);
    if (filtered) {
      newUri = filtered.originUri;
    }
  
    return newUri;
  }
  
  /**
   * Transforms C++ syntax for variable names and keys (underscore_names) to the JS format
   * (camelCase).
   */
  function removeUnderscores(data) {
    let transformedKey;
  
    for (const key in data) {
      if (!data.hasOwnProperty(key)) continue;
  
      transformedKey = key.replace(/_(.)/g, (str, chr) => chr.toUpperCase());
  
      if (data[key] instanceof Object) {
        data[transformedKey] = removeUnderscores(data[key]);
      } else {
        data[transformedKey] = data[key];
      }
  
      if (transformedKey !== key) {
        delete data[key];
      }
    }
  
    return data;
  }
  
  exports.play = play;
  exports.playFromResolver = playFromResolver;
  exports.playContext = playContext;
  exports.pause = pause;
  exports.resume = resume;
  exports.update = update;
  exports.subscribe = subscribe;
  exports.onError = onError;
  exports.getState = getState;
  exports.addContext = addContext;
  exports.updateContext = updateContext;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../live-filter-uri":116,"../../live-sort-uri":170,"../util/cosmos":167,"./data":141,"spotify-cosmos-api":543,"spotify-player/lib/v2":554}],144:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.play = play;
  const playlistUtils = require('../../../libs/playlist-utils');
  const isPlaylist2Shows = require('../../../libs/playlist-utils').isPlaylist2Shows;
  const live = require('../../../libs/live');
  const wrappedUri = require('../../../libs/live-wrapped-uri');
  
  function play(contextUri, options, callback) {
    checkIfPlaylistsToShowsContext(contextUri).then(includeEpisodes => {
      playlistUtils.playlistRequest(contextUri, {
        method: 'POST',
        includeEpisodes,
        type: 'play',
        body: options
      }, callback);
    });
  }
  
  function checkIfPlaylistsToShowsContext(contextUri) {
    return new Promise(resolve => {
      const parsedUri = wrappedUri.parse(contextUri);
      const originUri = parsedUri ? parsedUri.originUri : contextUri;
      live(originUri).get('formatListType', (error, formatListType) => {
        resolve(isPlaylist2Shows(formatListType));
      }, live.ASAP);
    });
  }
  
  },{"../../../libs/live":172,"../../../libs/live-wrapped-uri":171,"../../../libs/playlist-utils":195}],145:[function(require,module,exports){
  /**
   * @module live-models/player/updates
   * @private
   */
  'use strict';
  
  let lastRowsList;
  let lastRowsListUri;
  let lastRowsListHandler;
  let lastRowsListReferrer;
  let playerRowKey;
  let playerKeys;
  
  /**
   * Listen for updates on a rows list and update the player when
   * changes happen.
   *
   * @param {LiveList} rowsList A live list of rows.
   * @param {Object} options Object with options and data of what to play.
   * @param {Function} handler A handler function that gets called whenever
   *     the list is updated. Gets called with one argument: an options object.
   */
  function listen(rowsList, options, handler) {
    if (lastRowsList) {
      removeRowsListener();
    }
  
    lastRowsList = rowsList;
    lastRowsListUri = options.context;
    lastRowsListReferrer = options.referrerId;
  
    lastRowsListHandler = function () {
      // Todo: Only add this for TPM.
      options.index = getPlayingAndUpdateIndicesInList(rowsList).update;
      handler(options);
    };
  
    rowsList.on('update', lastRowsListHandler);
  }
  
  /**
   * Check if the last added rows listener is still valid for the current
   * player state, and clean up the update handler if it's not valid.
   *
   * @param {LiveObject} model The player live object.
   */
  function clean(model) {
    if (!lastRowsListHandler) return;
  
    const context = model.get('variant');
  
    // Remove listener if the player context is no longer the same
    if ((context && context.uri) !== lastRowsListUri) {
      removeRowsListener();
  
      // Remove listener if the player referrer is no longer the same
    } else {
      const origin = model.get('playOrigin');
      const playerReferrer = origin && origin.get('referrerIdentifier');
  
      if (playerReferrer !== lastRowsListReferrer && !model.get('contextUpdatedCrossFrame')) {
        removeRowsListener();
      }
    }
  }
  
  /**
   * Save which item is currently playing.
   *
   * @param {number} index The index for the playing row.
   */
  function setPlayingIndex(index, optRowsList) {
    if (!lastRowsList && !optRowsList) {
      return;
    }
  
    // If the index is out of bounds (e.g. is -1 because the track is not found),
    // then just keep the old playerRowKey.
    if (index < 0) {
      return;
    }
  
    const list = optRowsList || lastRowsList;
  
    playerRowKey = list.keys[index] || null;
    playerKeys = list.keys.slice();
  }
  
  /**
   * Remove the saved rows listener data.
   */
  function removeRowsListener() {
    if (lastRowsList) {
      lastRowsList.off('update', lastRowsListHandler);
    }
  
    lastRowsList = null;
    lastRowsListUri = '';
    lastRowsListHandler = null;
    lastRowsListReferrer = '';
  }
  
  /**
   * Get the index of the playing row in the provided list.
   *
   * @param {LiveList} rowsList A live list of rows.
   *
   * @return {number} The index of the playing row, or -1 if not found.
   */
  function getPlayingAndUpdateIndicesInList(rowsList) {
    if (!playerRowKey) return { update: -1, playing: -1 };
  
    // Find the currently playing row in the new list
    const playingIndexInNewList = rowsList.keys.indexOf(playerRowKey);
    let updateIndex = playingIndexInNewList;
  
    // If the playing row was not found in the new list, loop through the row
    // keys of the list that the player is currently playing from, and try to
    // find the next row key that exist both in the currently playing list and
    // the new list.
    if (playingIndexInNewList === -1) {
      const indexInPlayerList = playerKeys.indexOf(playerRowKey);
      for (let i = indexInPlayerList, l = playerKeys.length; i < l; i++) {
        updateIndex = rowsList.keys.indexOf(playerKeys[i]);
        if (updateIndex > -1) {
          // Index must be -1 since we've moved down several rows. The cosmos
          // player wants the index of the next row, so before passing it to
          // cosmos, the index will be incremented again.
          updateIndex--;
  
          break;
        }
      }
    }
  
    return {
      update: updateIndex,
      playing: playingIndexInNewList
    };
  }
  
  /**
   * Check if the player is currently playing from a rows list.
   *
   * @param {string} uri Context URI. If passed, it checks for that specific URI.
   *
   * @return {boolean} True if it is playing from a rows list, false otherwise.
   */
  function isPlayingFromRowsContext(uri) {
    if (uri) return lastRowsListUri === uri;
  
    return !!lastRowsList;
  }
  
  exports.setPlayingIndex = setPlayingIndex;
  exports.listen = listen;
  exports.clean = clean;
  exports.getPlayingAndUpdateIndicesInList = getPlayingAndUpdateIndicesInList;
  exports.isPlayingFromRowsContext = isPlayingFromRowsContext;
  
  },{}],146:[function(require,module,exports){
  (function (global){
  'use strict';
  
  var _live = require('../live');
  
  var _live2 = _interopRequireDefault(_live);
  
  var _offlineStatus = require('../playlist-utils/offline-status');
  
  var _bridge = require('../bridge');
  
  var _playlistUtils = require('../playlist-utils');
  
  var _messageProxy = require('../message-proxy');
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function onBroadcast(error, body) {
    if (error) {
      if (global.console) console.error(error);
      return;
    }
  
    (0, _live2.default)(body.uri).update(body);
  }
  
  function onWait(model, properties) {
    const policy = {};
    if (properties.includes('name')) policy.name = true;
    if (properties.includes('description')) policy.description = true;
    if (properties.includes('image') || properties.includes('annotatedImage')) {
      policy.picture = true;
    }
    if (properties.includes('added')) policy.followed = true;
    if (properties.includes('formatListType')) policy.formatListType = true;
    if (properties.includes('annotatedImage')) policy.pictureFromAnnotate = true;
    if (properties.includes('owner')) {
      policy.owner = { link: true, username: true, name: true };
      policy.ownedBySelf = true;
    }
    if (properties.includes('published')) policy.published = true;
    if (properties.includes('collaborative')) policy.collaborative = true;
    if (properties.includes('allows')) {
      policy.allows = { insert: true, remove: true };
    }
    if (Object.keys(policy).length === 0) return;
  
    (0, _playlistUtils.subscribeToMetadata)(model.uri, policy, (error, data = {}) => {
      if (error) {
        console.error(error);
        return;
      }
  
      if (data && 'picture' in data) {
        data.image = data.picture;
        delete data.picture;
      }
  
      if (data && 'pictureFromAnnotate' in data) {
        data.annotatedImage = data.pictureFromAnnotate && data.image || null;
        delete data.pictureFromAnnotate;
      }
  
      if (data && 'offline' in data) {
        data.offlineStatus = data.offline;
        data.shouldBeOffline = (0, _offlineStatus.shouldBeOffline)(data.offline);
        delete data.offline;
      }
  
      if (properties.includes('formatListType')) {
        data.formatListType = data.formatListType || 'playlist';
      }
  
      if ('owner' in data && 'link' in data.owner) {
        data.owner.uri = data.owner.link;
      }
  
      if ('ownedBySelf' in data && 'owner' in data) {
        data.owner.currentUser = data.ownedBySelf;
      }
  
      if (data && 'allows' in data) {
        data.allows = {
          insertTracks: data.allows.insert,
          removeTracks: data.allows.remove
        };
      }
  
      model.update(data);
    });
  }
  
  function onHeart(model, trackUri) {
    const entityUri = _playlistUtils.formatListUtils.originalPlaylistUri(model.get('uri'));
    (0, _live2.default)(entityUri).get('formatListAttributes', function (err, res) {
      if (err) {
        console.error(err);
        return;
      }
  
      if (res.get('like-feedback-available') === '1') {
        (0, _messageProxy.messageProxy)({
          method: 'POST',
          uri: 'format-list-heart',
          body: {
            entityUri,
            trackUri
          }
        });
      }
  
      const feedbackUri = _playlistUtils.formatListUtils.makeFeedbackUri(entityUri, trackUri);
      (0, _live2.default)(feedbackUri).update({ thumb: 'up' });
    });
  }
  
  function onUndoFeedback(model, trackUri) {
    const entityUri = _playlistUtils.formatListUtils.originalPlaylistUri(model.get('uri'));
    (0, _live2.default)(entityUri).get('formatListAttributes', function (err, res) {
      if (err) {
        console.error(err);
        return;
      }
  
      if (res.get('like-feedback-available') === '1') {
        (0, _messageProxy.messageProxy)({
          method: 'POST',
          uri: 'format-list-undo-feedback',
          body: {
            entityUri,
            trackUri
          }
        });
  
        const feedbackUri = _playlistUtils.formatListUtils.makeFeedbackUri(entityUri, trackUri);
        (0, _live2.default)(feedbackUri).update({ thumb: null });
      }
    });
  }
  
  const matches = exports.matches = /^spotify:(user:[^:]+:)?playlist:[^:]+$/;
  
  let broadcastSubscriptionCancel;
  let registered;
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    _live2.default.subscribe(matches, 'wait', onWait);
    _live2.default.subscribe(matches, 'heart', onHeart);
    _live2.default.subscribe(matches, 'undo-heart', onUndoFeedback);
    _live2.default.subscribe(matches, 'undo-ban', onUndoFeedback);
  
    broadcastSubscriptionCancel = (0, _bridge.cosmosJSON)({ method: 'SUB', uri: 'sp://messages/v1/playliststate' }, onBroadcast);
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    _live2.default.unsubscribe(matches, 'wait', onWait);
  
    broadcastSubscriptionCancel();
    broadcastSubscriptionCancel = null;
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../bridge":53,"../live":172,"../message-proxy":187,"../playlist-utils":195,"../playlist-utils/offline-status":196}],147:[function(require,module,exports){
  (function (global){
  /**
   * @module live-models/session-storage
   */
  'use strict';
  
  const forOwn = require('mout/object/forOwn');
  const combine = require('mout/array/combine');
  const remove = require('mout/array/remove');
  const contains = require('mout/array/contains');
  
  const URI = 'spotify:session-storage';
  const live = require('../live');
  const cosmos = require('./util/cosmos');
  const endpoints = {
    broadcast: 'sp://messages/v1/session-storage'
  };
  
  let waiting = [];
  
  function onInit(model) {
    // Set a flag for showing that the model has been implemented and
    // registered. This is so that we can check this in player without
    // making a breaking change.
    model.update({ implemented: true });
  }
  
  function onWait(model, properties) {
    // Ask the top frame to broadcast the properties.
    // Values will be null when they weren't stored.
    waiting = combine(waiting, properties);
    global.top.postMessage({
      type: 'session_storage',
      name: 'broadcast_session_storage',
      data: properties
    }, '*');
  }
  
  function onPublish(model, properties) {
    // Tell the top frame to persist and broadcast
    global.top.postMessage({
      type: 'session_storage',
      name: 'set_session_storage',
      data: properties
    }, '*');
  
    // Optimistic update
    model.update(properties);
  }
  
  function onBroadcast(error, event) {
    const update = {};
    const model = live(URI);
  
    forOwn(event.body, function (value, key) {
      // Update model if this instance of storage knows that the model waits for it
      const isWaiting = contains(waiting, key);
  
      // Or if the model has a potentially outdated value in the live cache
      const hasKey = model.get(key) !== undefined;
  
      if (isWaiting || hasKey) update[key] = value;
      if (isWaiting) remove(waiting, key);
    });
    model.update(update);
  }
  
  let broadcastSubscription;
  const regExp = exports.matches = new RegExp(`^${URI}$`);
  let registered;
  
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    live.subscribe(regExp, 'init', onInit);
    live.subscribe(regExp, 'wait', onWait);
    live.subscribe(regExp, 'publish', onPublish);
    broadcastSubscription = cosmos.subscribe({ url: endpoints.broadcast }, onBroadcast);
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    live.unsubscribe(regExp, 'init', onInit);
    live.unsubscribe(regExp, 'wait', onWait);
    live.unsubscribe(regExp, 'publish', onPublish);
  
    if (broadcastSubscription) {
      broadcastSubscription.cancel();
      broadcastSubscription = null;
    }
    waiting.length = 0;
  };
  
  exports._endpoints = endpoints;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../live":172,"./util/cosmos":167,"mout/array/combine":433,"mout/array/contains":434,"mout/array/remove":443,"mout/object/forOwn":468}],148:[function(require,module,exports){
  'use strict';
  
  var _live = require('../live');
  
  var _live2 = _interopRequireDefault(_live);
  
  var _cosmos = require('./util/cosmos');
  
  var _cosmos2 = _interopRequireDefault(_cosmos);
  
  var _spotifyLiburi = require('spotify-liburi');
  
  var _spotifyLiburi2 = _interopRequireDefault(_spotifyLiburi);
  
  var _liveWrappedUri = require('../live-wrapped-uri');
  
  var _liveWrappedUri2 = _interopRequireDefault(_liveWrappedUri);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  const regExp = exports.matches = /^spotify:((internal:sortlist:(asc|desc):([^:]*):(internal:filterlist:[^:]*):)|(internal:sortlist:(asc|desc):([^:]*):)|(internal:filterlist:[^:]*):)?show:[^:]+$/;
  let registered;
  const subscribingForUris = [];
  
  exports.register = () => {
    if (registered) return;
    registered = true;
  
    subscribingForUris.length = 0;
  
    _live2.default.subscribe(regExp, 'wait', onWait);
  };
  
  exports.unregister = () => {
    if (!registered) return;
    registered = false;
  
    subscribingForUris.length = 0;
  
    _live2.default.unsubscribe(regExp, 'wait', onWait);
  };
  
  function onWait(model, properties) {
    if (!properties.includes('show') && !properties.includes('rows')) {
      return;
    }
  
    if (subscribingForUris.includes(model.uri)) {
      return;
    }
  
    subscribingForUris.push(model.uri);
  
    let showUri = model.uri;
    let additionalFilter = '';
    let sort = 'number,publishDate,name';
  
    const parsed = _liveWrappedUri2.default.parse(model.uri);
  
    if (parsed) {
      showUri = parsed.originUri;
  
      if (parsed.sort && parsed.sort.query) {
        sort = sortToString(parsed.sort);
      }
  
      if (parsed.filter && parsed.filter.query) {
        additionalFilter = filterQueryToString(parsed.filter.query);
      }
    }
  
    const showId = _spotifyLiburi2.default.from(showUri).getBase62Id();
  
    _cosmos2.default.subscribe({
      url: `sp://core-show/unstable/show/${showId}?filter=available eq true${additionalFilter}&sort=${sort}`
    }, (error, data) => {
      if (error) throw error;
      const { show, items } = data.body;
  
      // Add a URI property so the data is put into the live cache
      show.uri = show.link;
      items.forEach(item => {
        item.uri = item.link;
        item.show.uri = item.show.link;
      });
  
      // we have to map the items array this way to enable row selection
      model.update({
        show,
        rows: items.map(item => ({ track: item })),
  
        // This timestamp will cause an 'update' event to be sent out even when
        // the real diff was just something nested.
        lastUpdateTimestamp: Date.now()
      });
    });
  }
  
  /**
   * @param  {Object} parsedSort Object with .direction and .query
   * @return {String}
   */
  function sortToString(parsedSort) {
    const direction = parsedSort.direction.toUpperCase();
    let sort = 'number,publishDate,name';
  
    switch (parsedSort.query) {
      case 'track(name)':
        sort = `name ${direction}`;
        break;
      case 'track(duration)':
        sort = `length ${direction}`;
        break;
      case 'track(timeLeft)':
        {
          sort = `progress ${direction}`;
          break;
        }
      case 'timeSinceAdded,track(album(name),disc,number)':
        sort = `number ${direction},publishDate ${direction},name ${direction}`;
        break;
      default:
        console.error('Invalid query', parsedSort.query);
        break;
    }
  
    return sort;
  }
  
  /**
   * @param  {String} parsedFilterQuery
   * @return {String}
   */
  function filterQueryToString(parsedFilterQuery) {
    if (parsedFilterQuery.length > 0) {
      return `,${parsedFilterQuery}`;
    }
    return '';
  }
  
  },{"../live":172,"../live-wrapped-uri":171,"./util/cosmos":167,"spotify-liburi":553}],149:[function(require,module,exports){
  "use strict";
  
  /**
   * This module splits up a list into many chunks.
   *
   * @module live-models/sorting/chunker
   * @private
   */
  
  /**
   * Splits up an array of data into smaller chunks.
   *
   * @param {Array} list The array to be split up.
   * @param {number=} size Optional size of each chunk.
   *
   * @return {Array} Array of chunks, where each chunk is an array of data.
   */
  module.exports = function (list, size = 50) {
    function split(array, segments = 2) {
      const results = [];
      if (array === null) {
        return results;
      }
  
      const minLength = Math.floor(array.length / segments);
      const remainder = array.length % segments;
      let i = 0;
      const len = array.length;
      let segmentIndex = 0;
      let segmentLength;
  
      while (i < len) {
        segmentLength = minLength;
        if (segmentIndex < remainder) {
          segmentLength++;
        }
  
        results.push(array.slice(i, i + segmentLength));
  
        segmentIndex++;
        i += segmentLength;
      }
  
      return results;
    }
  
    const chunkCount = Math.ceil(list.length / size);
    const chunks = split(list, chunkCount);
  
    return chunks;
  };
  
  },{}],150:[function(require,module,exports){
  /**
   * This file contains transformations from legacy sort queries to new sort
   * queries. We need this because when a user sorts by a certain column, we store
   * the full URI for that list and the sort query is part of the URI. Whenever
   * we change the sort query of a column, we will break sorting for users who
   * have a stored sort state with the old query. That's why we need to maintain
   * this conversion from old to new to make the right list load for all users.
   *
   * When to remove a conversion needs to be weighed case by case I guess. If a
   * user sorts by a certain column and doesn't launch the client for three months
   * and we change the query during that time, it's expected to still work for
   * that user when logging in the next time.
   *
   * History of this file is in the play-desktop app,
   * https://ghe.spotify.net/zelda/client-zelda-js/blob/master/apps/playlist/src/legacy-sort-uris.js
   */
  'use strict';
  
  exports.legacyQueries = {
    // Change date: 2015-03-16
    'track(album(name))': 'track(album(name),disc,number)',
    // Change date: 2015-03-16
    'track(artists(name))': 'track(artists[0:1](name),album(name),disc,number)',
    // Change date: 2015-03-17, value changed 2015-07-14 because we introduced the conversion below.
    'track(addedBy)': 'addedBy(name)',
    // Change date: 2015-07-14
    'track(addedBy(name))': 'addedBy(name)',
    // Change date: 2015-06-26
    'timeSinceAdded': 'timeSinceAdded,track(album(name),disc,number)'
  };
  
  },{}],151:[function(require,module,exports){
  'use strict';
  
  /**
   * This module helps posting chunked data to/from a worker.
   *
   * @module live-models/sorting/poster
   * @private
   */
  
  /**
   * Posts data from/to a worker.
   *
   * @param {Object} worker A web worker.
   * @param {Array} chunks Array of split up data.
   * @param {number} index Index of the current chunk to post. Initiate the
   *     poster with 0, and it posts all data sequentially until all data
   *     is posted.
   * @param {string} id An ID for the full payload.
   */
  module.exports = function poster(worker, chunks, index, id) {
    'use strict';
  
    const data = {
      items: chunks[index],
      id: id
    };
  
    if (index === chunks.length - 1) {
      data.last = true;
    }
  
    worker.postMessage(data);
  
    if (!data.last) {
      poster(worker, chunks, index + 1, id);
    }
  };
  
  },{}],152:[function(require,module,exports){
  (function (global){
  'use strict';
  
  /**
   * This module sorts an array of data in the best possible way.
   * If Web Workers are supported, it will spawn new workers and
   * sort on different threads to speed it up.
   *
   * @module live-models/sorting/sort
   * @private
   */
  
  const chunker = require('./chunker');
  const poster = require('./poster');
  const sorter = require('./sorter');
  const sortWorker = require('./sortworker');
  const defer = require('../../../libs/prime/defer');
  
  let workerFile;
  const workers = {};
  const callbacks = {};
  const waitingForReset = {};
  
  // A generated ID will be stored here for each sorted list. Whenever a sorted
  // list is asked to resort, a new ID will be generated and set here for that
  // list. That allows us to cancel a previous sort if a newer one has started
  // before the old one finished.
  let idCounter = 0;
  const lastIds = {};
  
  /**
   * The local sorter that sorts on the main thread.
   *
   * @param {string} sortUri URI of the sorted list.
   * @param {Array.<Object>} items Array of items to sort. Each item is
   *     an object with the properties `data` and `key`. The `data`
   *     property is an array of values to sort on. The `key` property
   *     is a string for the row ID.
   * @param {Function} callback Async callback where the data is the array of
   *     sorted items.
   */
  function localSorter(sortUri, items, callback) {
    const sortedItems = sorter(items);
  
    // Saving the callback as the latest callback for this sorted list
    // will allow us to only respond with the latest sorted values (if
    // multiple sort calls were made at the same time).
    callbacks[sortUri] = callback;
  
    // Make the callback async
    defer.immediate(function () {
      // Only allow the latest callback to run
      if (callback === callbacks[sortUri]) {
        delete callbacks[sortUri];
  
        callback(null, sortedItems);
      }
    });
  }
  
  /**
   * The Web Worker sorter.
   *
   * @param {string} sortUri URI of the sorted list.
   * @param {Array.<Object>} items Array of items to sort. Each item is
   *     an object with the properties `data` and `key`. The `data`
   *     property is an array of values to sort on. The `key` property
   *     is a string for the row ID.
   * @param {Function} callback Async callback where the data is the array of
   *     sorted items.
   */
  function workerSorter(sortUri, items, callback) {
    let worker = workers[sortUri];
  
    // If we already have a worker for this sorted list, reset that worker
    // and tell it to sort the new data. By saving the callback we make sure
    // that the original worker event handler will run the latest callback
    // when the sorting is done.
    if (worker) {
      callbacks[sortUri] = callback;
      waitingForReset[sortUri] = (waitingForReset[sortUri] || 0) + 1;
      const id = (++idCounter).toString(36);
      lastIds[sortUri] = id;
      worker.postMessage({ reset: true, newId: id });
      sendItemsToWorker(items, worker, id);
      return;
    }
  
    worker = new Worker(workerFile);
    workers[sortUri] = worker;
    callbacks[sortUri] = callback;
  
    const id = (++idCounter).toString(36);
    lastIds[sortUri] = id;
    sendItemsToWorker(items, worker, id);
  
    const result = { items: [] };
  
    worker.addEventListener('message', function (event) {
      onWorkerMessage(event, result, sortUri);
    }, false);
  }
  
  /**
   * Event handler for receiving a message from a worker.
   * This will handle collecting all chunks of data and post it to the latest
   * callback when all chunks have been received.
   *
   * @param {Event} event An event object from the worker 'message' event.
   * @param {Object} result An object with a property `items` for the sorted items.
   * @param {string} sortUri The sort URI for the current sort.
   */
  function onWorkerMessage(event, result, sortUri) {
    // When the main thread tells the worker to reset, the worker will respond with
    // a message to reset the array of already sorted items.
    if (event.data.reset) {
      waitingForReset[sortUri]--;
      result.items.length = 0;
      return;
    }
  
    // Skip messages that don't belong to the latest sort
    if (event.data.id !== lastIds[sortUri]) {
      return;
    }
  
    // If we are still waiting for reset messages, don't continue to add more items
    // to the list of sorted items.
    if (waitingForReset[sortUri]) {
      return;
    }
  
    // If we are not waiting for any resets, just save the chunk of sorted items.
    result.items = result.items.concat(event.data.items);
  
    // When all data has been passed over from the worker, kill the worker and run
    // the latest registered callback for this sorted list.
    if (event.data.last) {
      workers[sortUri].terminate();
  
      const callback = callbacks[sortUri];
      delete callbacks[sortUri];
      delete workers[sortUri];
  
      callback(null, result.items);
    }
  }
  
  /**
   * Create the Web Worker file path.
   * Since the worker can't share objects with the main thead, we can't share
   * dependencies. To solve this, we convert the dependencies to strings and pass
   * them as arguments to the worker file.
   * To circumvent the need of a separate file for the worker, we make the worker
   * itself and the passed dependencies a single string and make a blob out of it.
   * From the blob we can get a URL that we can pass to the worker constructor.
   *
   * @return {string} Blob URL that can be passed to the worker.
   */
  function createWorkerFile() {
    const workerStr = sortWorker.toString();
    const sortStr = sorter.toString();
    const chunkerStr = chunker.toString();
    const posterStr = poster.toString();
    const argsStr = [sortStr, chunkerStr, posterStr].join(', ');
    const stringWorker = `(${workerStr})(${argsStr})`;
  
    const blob = new Blob([stringWorker], { type: 'text/javascript' });
    const url = URL.createObjectURL(blob);
  
    return url;
  }
  
  /**
   * Send the provided items to the provided worker. It will do this by splitting
   * the items into chunks and sending the chunks.
   *
   * @param {Array} items An array of items.
   * @param {Worker} worker A worker instance.
   * @param {string} id The ID for the payload.
   */
  function sendItemsToWorker(data, worker, id) {
    const chunks = chunker(data);
    poster(worker, chunks, 0, id);
  }
  
  /**
   * Get the sort function to use.
   * If Web Workers are supported, it will use that, otherwise it will use the
   * default sorter on the main thread.
   * Having the code in a function allows for testing where worker support can
   * be removed and added while running the tests.
   *
   * @return {Function} The sorter function.
   */
  function getSorter() {
    if (global.Worker && global.Blob && global.URL) {
      if (!workerFile) {
        workerFile = createWorkerFile();
      }
      return workerSorter;
    }
    return localSorter;
  }
  
  /**
   * Sort the data using the best supported method.
   *
   * @param {string} sortUri URI of the sorted list.
   * @param {Array.<Object>} items Array of items to sort. Each item is
   *     an object with the properties `data` and `key`. The `data`
   *     property is an array of values to sort on. The `key` property
   *     is a string for the row ID.
   * @param {Function} callback Async callback where the data is the array of
   *     sorted items.
   */
  module.exports = function (sortUri, items, callback) {
    const sort = getSorter();
    sort(sortUri, items, callback);
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../../libs/prime/defer":200,"./chunker":149,"./poster":151,"./sorter":153,"./sortworker":154}],153:[function(require,module,exports){
  'use strict';
  
  /**
   * Sorts an array of data. Supports multiple levels of values
   * (if two items are the same, it tests the values in the next level).
   *
   * @param {Array.<Object>} data Array of items to sort. Each item is
   *     an object with the properties `data` and `key`. The `data`
   *     property is an array of values to sort on. The `key` property
   *     is a string for the row ID.
   *
   * @return {Array.<Object>} A new array of the same objects that were
   *     passed in, but sorted.
   */
  module.exports = function (data) {
    'use strict';
  
    data.sort(function (a, b) {
      const length = a.data.length;
  
      for (let i = 0; i < length; i++) {
        const aValue = a.data[i];
        const bValue = b.data[i];
  
        const aNumber = parseInt(aValue, 10);
        const bNumber = parseInt(bValue, 10);
  
        const aStartsWithNumber = !isNaN(aNumber);
        const bStartsWithNumber = !isNaN(bNumber);
  
        if (aStartsWithNumber && bStartsWithNumber) {
          if (aNumber < bNumber) return -1;
          if (aNumber > bNumber) return 1;
        } else if (aStartsWithNumber) {
          return -1;
        } else if (bStartsWithNumber) {
          return 1;
        }
  
        if (aValue.localeCompare) {
          const value = aValue.localeCompare(bValue);
          if (value !== 0) return value;
        } else {
          if (aValue < bValue) return -1;
          if (aValue > bValue) return 1;
        }
      }
  
      return -1;
    });
  
    return data;
  };
  
  },{}],154:[function(require,module,exports){
  'use strict';
  
  /**
   * This is the Web Worker file that sorts the data and passes it back again.
   * The file cannot be passed directly to new Worker(), as it needs some
   * dependencies. These dependencies are required before creating the worker,
   * and then serialized together with this file into a single blob, which is
   * used to create the worker.
   *
   * @module live-models/sorting/sortworker
   * @private
   */
  
  /**
   * Sort worker.
   *
   * @param {Function} sort Function that sorts an array of data based on a
   *     property.
   * @param {Function} chunk Function that splits up data into chunks.
   * @param {Function} post Function that posts chunks of data back to main
   *     thread.
   * @param {Object} optWorker Optional worker instance (mainly for testability).
   */
  module.exports = function (sort, chunk, post, optWorker) {
    'use strict';
  
    const worker = optWorker || self;
  
    let itemsToSort = [];
    let id = null;
  
    worker.addEventListener('message', function (event) {
      // When the main thread tells the worker to reset, the worker will respond with
      // a message to reset the array of already sorted items.
      if (event.data.reset) {
        itemsToSort.length = 0;
        id = event.data.newId;
        worker.postMessage({ reset: true, newId: id });
        return;
      }
  
      // Only set the stored id for the first message
      if (id === null) {
        id = event.data.id;
      }
  
      // If the event id does not match the stored id, don't do anything.
      // This allows a sort to be aborted in the middle.
      if (id !== event.data.id) {
        return;
      }
  
      itemsToSort = itemsToSort.concat(event.data.items);
  
      if (event.data.last) {
        const sortedItems = sort(itemsToSort);
        const chunks = chunk(sortedItems);
        post(worker, chunks, 0, id);
      }
    }, false);
  };
  
  },{}],155:[function(require,module,exports){
  'use strict';
  
  /**
   * Get the row values used for sorting.
   *
   * @module live-models/sorting/values
   * @private
   */
  
  /**
   * Get the values from the row based on the passed mask.
   * String values will be sanitized (lowercase, removing articles like 'the' etc).
   * The values can then be used for sorting.
   *
   * @param {Object} mask A mask object from the query parser.
   * @param {Object} row Row data object.
   *
   * @return {Array>} Array of values to sort on.
   *
   * @example
   * var parse = require('../../live/util/parser');
   * var mask = parse('track(name), track(artists(name))').mask;
   * var row = {track: {name: 'Track', artists: [{name: 'Artist'}]}};
   * var values = getValues(mask, row);
   * console.log(values);
   * // ['track', 'artist']
   */
  function getValues(mask, row) {
    let result = [];
    mask.forEach(function (m) {
      const k = m.key;
      const value = row[k];
  
      // If the value is an array, we will concatenate all the
      // values with a comma in between, so that sorting takes
      // all values into account. An example is sorting tracks
      // based on 'artists(name)', where there can be many
      // artists for a track. We will in that case concatenate
      // all artists with comma in between and use a string with
      // all the artists when we compare in the sort function.
      if (Array.isArray(value)) {
        if (m.mask) {
          let values = [];
          for (let i = 0, l = value.length; i < l; i++) {
            values = values.concat(getValues(m.mask, value[i]));
          }
          result.push(values.join(', '));
        } else {
          result.push(value.join(', '));
        }
      } else {
        if (m.mask) result.push.apply(result, getValues(m.mask, value));else result.push(value);
      }
    });
  
    result = sanitizeStrings(result);
  
    return result;
  }
  
  /**
   * Sanitize string values. This includes trimming whitespace, converting
   * the string to lower case and removing common articles like 'the'.
   *
   * @param {Array} values Array of values.
   *
   * @return {Array} A new array of sanitized values.
   */
  function sanitizeStrings(values) {
    const numberRe = /\b[0-9]+\b/g;
    const pad = '000000';
  
    return values.map(function (value) {
      if (typeof value !== 'string') return value;
  
      let newValue = value.toLowerCase();
  
      // Remove the first article
      const articles = ['the ', '(the) '];
      for (let i = 0; i < articles.length; i++) {
        const article = articles[i];
  
        if (newValue.indexOf(article) === 0) {
          newValue = newValue.replace(article, '');
          break;
        }
      }
  
      // Zero-pad numbers for natural numeric sorting (i.e. 2 before 10)
      newValue = newValue.replace(numberRe, function (match) {
        if (match.length >= pad.length) return match;
        return pad.substr(0, pad.length - match.length) + match;
      });
  
      return newValue.trim();
    });
  }
  
  module.exports = getValues;
  
  },{}],156:[function(require,module,exports){
  (function (global){
  /**
   * @module live-models/sortlist
   */
  'use strict';
  
  const live = require('../live');
  const mixIn = require('mout/object/mixIn');
  
  const parse = require('../live/util/parser');
  const sortUriUtils = require('../live-sort-uri');
  const listOperations = require('../live-list-operations');
  
  const sort = require('./sorting/sort');
  const getValues = require('./sorting/values');
  
  // A generated ID will be stored here for each sorted list. Whenever a sorted
  // list is asked to resort, a new ID will be generated and set here for that
  // list. That allows us to cancel a previous sort if a newer one has started
  // before the old one finished.
  let idCounter = 0;
  const lastIds = {};
  
  /**
   * Event handler for the live 'init' event on sorted models. Keeps sorted model
   * and tracking model in sync (properties, not rows).
   *
   * This will set up an update listener for the tracking model, so that any
   * changes to the tracking model are also applied to the sort model.
   *
   * This also sets up a publish listener for the sorted model, so that anything
   * that is published on the sorted model gets published on the tracking model,
   * which can then update backend with the data and then update the tracking
   * model with the correct data, which will then trickle back down to the
   * sorted model.
   *
   * @param {LiveObject} sortModel The sorted live model.
   */
  function onInit(sortModel) {
    const trackingModel = getTrackingModel(sortModel.uri);
  
    lastIds[sortModel.uri] = (++idCounter).toString(36);
  
    // Update the sorted model when any property on the tracking model changes (except rows)
    trackingModel.on('update', function (properties) {
      const updateObject = mixIn({}, properties);
      delete updateObject.rows;
      sortModel.update(updateObject);
    });
  
    // Update the tracking model when any property on the sort model changes (except rows)
    sortModel.on('publish', function (properties) {
      const updateObject = mixIn({}, properties);
      delete updateObject.rows;
      trackingModel.publish(updateObject);
    });
  }
  
  /**
   * Event handler for the live 'wait' event on sorted models.
   * This will fetch any requested property from the tracking model, or if
   * rows are requested, it will perform a sort of the rows from the tracking
   * model.
   *
   * It will also listen for changes. If a change is published on the sorted list,
   * it will be republished on the tracking list. When the tracking list is updated
   * with the new data (maybe after a save on the backend), the sorted list will be
   * updated and resorted if needed.
   *
   * @param {LiveObject} sortModel The sorted live model.
   * @param {Array} properties Array of properties that the model is waiting for.
   */
  function onWait(sortModel, properties) {
    const trackingModel = getTrackingModel(sortModel.uri);
  
    properties.forEach(function (key) {
      // Requesting rows will create a live list of rows, perform sorting,
      // fill the list with the sorted items and also start to listen for changes.
      if (key === 'rows') {
        setRowsProperty(sortModel, trackingModel);
  
        // Requesting any other property than rows will just get that property from
        // the tracking model.
      } else {
        trackingModel.get(key, function (error, value) {
          if (error) {
            if (global.console) console.error(error);
            return;
          }
          const obj = {};
          obj[key] = value;
          sortModel.update(obj);
        });
      }
    });
  }
  
  /**
   * Set the `rows` property on the sort model. It will create a new live list
   * and perform sorting based on the rows of the tracking model. It will also
   * set up listeners for any changes to the lists to automatically keep
   * everything in sync.
   *
   * @param {LiveObject} sortModel The live object for the sorted list.
   * @param {LiveObject} trackingModel The live object for the list the sorted
   *     list is tracking.
   */
  function setRowsProperty(sortModel, trackingModel) {
    const sortParams = sortUriUtils.parse(sortModel.uri);
    const direction = sortParams.direction;
    const query = sortParams.query;
  
    // Start the first sort
    performRowSorting(trackingModel, sortModel, query, direction);
  
    // Whenever a publish call happens on the sorted list, publish it to the
    // tracking list.
    sortModel.get('rows', function (error, sortList) {
      if (error) {
        if (global.console) console.error(error);
        return;
      }
      sortList.on('publish', function (operations) {
        const trackingList = trackingModel.get('rows');
        if (!trackingList) return;
        syncRows(trackingList, operations, 'publish');
      });
    });
  
    // We need to get the rows list of the tracking model to be able to know the
    // length of the list, so that we can create the rows list for the sorted list.
    trackingModel.get('rows', function (error, trackingList) {
      if (error) {
        if (global.console) console.error(error);
        return;
      }
  
      // Listen for updates on the tracking list and apply the changes (and
      // maybe resort if needed) on the sorted list.
      trackingList.on('update', function (operations) {
        const sortList = sortModel.get('rows');
        if (!sortList) return;
        const mustSort = syncRows(sortList, operations, 'update');
        if (mustSort) {
          performRowSorting(trackingModel, sortModel, query, direction);
        }
      });
    });
  }
  
  /**
   * Get the tracking model for a sort URI.
   * For an ascending sort URI, the tracking model would be the original list.
   * For a descending sort URI, the tracking model would be the ascending sort list.
   *
   * @param {string} sortUri The sort URI.
   *
   * @return {LiveObject} The live object for the tracking model.
   */
  function getTrackingModel(sortUri) {
    const parsed = sortUriUtils.parse(sortUri);
    const direction = parsed.direction;
    const originUri = parsed.originUri;
    const query = parsed.query;
  
    // We can use the sorting from the ascending list when requesting the
    // descending list, by just depending on the ascending list. Asking for
    // data from the ascending list will make that list ask for the data
    // from the original list and then sort it, and then the descending list
    // will reverse that.
    if (direction === 'desc') {
      return live(sortUriUtils.create(originUri, 'asc', query));
    }
    return live(originUri);
  }
  
  /**
   * Get data that is needed for performing a sort.
   *
   * @param {Array.<Object>} rows Array of objects of metadata about the rows.
   * @param {Object} mask A mask object from the query parser.
   * @param {LiveList} trackingList The live list for the list the sorted
   *     list is tracking.
   *
   * @return {Array.<Object>} A new array of items used for sorting. Each item is
   *     an object with the properties `data` and `key`. The `data`
   *     property is an array of values to sort on. The `key` property
   *     is a string for the row ID.
   *
   * @example
   * {
   *   data: ['Name of Song', 'Whatever', 100, 0],
   *   key: '0'
   * }
   */
  function getSortData(rows, mask, trackingList) {
    return rows.map(function (row, i) {
      const array = getValues(mask, row);
      array.push(i);
      return { data: array, key: trackingList.keys[i] };
    });
  }
  
  /**
   * Perform sorting of all rows in a list.
   *
   * @param {LiveObject} trackingModel The model that the sort model is tracking.
   *     For ascending lists, this is the original model. For descending lists,
   *     this is the ascending sort model.
   * @param {LiveObject} sortModel The model to perform the sort on.
   * @param {string} sortQuery The sort query (live compatible query).
   * @param {string} sortDirection The sort direction ('asc' or 'desc').
   */
  function performRowSorting(trackingModel, sortModel, sortQuery, sortDirection) {
    lastIds[sortModel.uri] = (++idCounter).toString(36);
    const sortId = lastIds[sortModel.uri];
  
    // Querying the rows of the tracking model might perform a sort. An example is when
    // getting the sort for a descending list, it will query the rows from the ascending
    // list, which will in turn query the rows from the original list and then perform a
    // sort. When the ascending sort is done, the query will be done and the descending
    // list can just use the ascending list and reverse it.
    trackingModel.get('rows', function (error, rows) {
      if (error) {
        if (global.console) console.error(error);
        return;
      }
  
      // Create a list of loaded rows. This allows partial loading of a list,
      // and the sorted version will only contain the loaded rows.
      const rowsToSort = [];
      const keys = [];
      for (let i = 0, l = rows.length; i < l; i++) {
        if (rows.get(i)) {
          rowsToSort.push(rows.get(i));
          keys.push(rows.keys[i]);
        }
      }
  
      // Empty lists won't sort anyway, so quit here, but empty the sorted list
      // to make it up to date.
      if (rowsToSort.length === 0) {
        const sortList = sortModel.get('rows');
        if (sortList) {
          sortList.update([{
            type: 'remove',
            index: 0,
            length: sortList.length
          }]);
        } else {
          sortModel.update({ rows: [] });
        }
        return;
      }
  
      const partialTrackingList = live([]);
  
      partialTrackingList.update([{
        type: 'insert',
        index: 0,
        values: rowsToSort,
        keys: keys
      }]);
  
      // Now we have a list containing only the loaded rows
      partialTrackingList.query(sortQuery, function (queryError, queryRows) {
        if (queryError) {
          if (global.console) console.error(queryError);
        } else {
          // If direction is descending, the tracking model is ascending. Simply reverse that.
          if (sortDirection === 'desc') {
            updateListWithReversedData(sortModel, partialTrackingList);
  
            // For an ascending sort, perform a real sort.
          } else {
            const mask = parse(sortQuery).mask;
            const sortData = getSortData(queryRows, mask, partialTrackingList);
  
            sort(sortModel.uri, sortData, function (sortError, sortedData) {
              if (sortError) {
                if (global.console) console.error(sortError);
                return;
              }
              if (lastIds[sortModel.uri] !== sortId) return;
              updateListWithSortedData(sortModel, partialTrackingList, sortedData);
            });
          }
        }
      });
    });
  }
  
  /**
   * Update the sorted live list with the right data after a sort.
   *
   * @param {LiveObject} sortModel The live object for the sorted list.
   * @param {LiveList} trackingList The live list for the list the sorted
   *     list is tracking.
   * @param {Array.<Object>} sortedItems Array of sorted items. Each item is
   *     an object with a property `key`, which is a string for the row ID.
   */
  function updateListWithSortedData(sortModel, trackingList, sortedItems) {
    let sortList = sortModel.get('rows');
  
    // Copy the rows from the list that the sorted list is tracking,
    // if the sorted list has no tracks yet. Only for the first time.
    if (!sortList) {
      sortList = new live.List();
      sortModel.update({ rows: sortList });
    }
  
    const length = sortedItems.length;
    const keys = new Array(length);
    const values = new Array(length);
  
    for (let i = 0, l = sortedItems.length; i < l; i++) {
      keys[i] = sortedItems[i].key;
      values[i] = trackingList.valueOf(keys[i]);
    }
  
    sortList.update([{
      type: 'remove',
      index: 0,
      length: sortList.length
    }, {
      type: 'insert',
      index: 0,
      keys: keys,
      values: values
    }]);
  }
  
  /**
   * Update the sorted live list with the data of the list it is tracking,
   * but reversed.
   *
   * @param {LiveObject} sortModel The live object for the sorted list.
   * @param {LiveList} trackingList The live list for the list the sorted
   *     list is tracking.
   */
  function updateListWithReversedData(sortModel, trackingList) {
    let sortList = sortModel.get('rows');
  
    if (!sortList) {
      sortList = new live.List();
      sortModel.update({ rows: sortList });
    }
  
    sortList.update([{
      type: 'remove',
      index: 0,
      length: sortList.length
    }, {
      type: 'insert',
      index: 0,
      length: trackingList.length,
      values: trackingList.values.slice().reverse(),
      keys: trackingList.keys.slice().reverse()
    }]);
  }
  
  /**
   * Converting the passed in operations to be relative to the list that was
   * passed in. Passing operations for a sorted list can convert the operations
   * to be used for the original list. This also updates (or publishes) the list
   * with the converted operations.
   *
   * @param {LiveList} rows The list to sync operations to.
   * @param {Array.<Object>} operations Array of operation objects.
   * @param {string} mode The mode for updates. 'update' or 'publish'.
   *
   * @return {boolean} True if a resort must happen after applying the operations.
   */
  function syncRows(rows, operations, mode) {
    let mustSort = false;
  
    operations.forEach(function (operation) {
      let newOperations = [];
  
      switch (operation.type) {
        // If rows are removed from the tracking list, we can "simply" remove
        // the same rows from the sorted model.
        case 'remove':
          newOperations = convertRemoveOperationForList(rows, operation);
          break;
  
        // If rows are inserted in the tracking list, we must add the rows to
        // the sorted list and resort. Where we insert doesn't matter as we will
        // perform a new sort anyway.
        case 'insert':
          newOperations = [{
            type: 'insert',
            index: 0,
            values: operation.values,
            keys: operation.keys
          }];
          mustSort = true;
          break;
  
        // If rows are moved in the tracking list, we don't need to perform any
        // new moves directly, but we will need to perform a resort, as sorting
        // uses stable sort (falls back on index in original list if compared
        // values are the same).
        case 'move':
          mustSort = true;
          break;
  
        default:
          console.error('Invalid operation type', operation.type);
          break;
      }
  
      if (mode === 'publish') {
        rows.publish(newOperations);
      } else if (mode === 'update') {
        rows.update(newOperations);
      }
    });
  
    return mustSort;
  }
  
  /**
   * Convert a remove operation from one list to an operation that can
   * be used in another list. An example is when we have an operation that
   * happened in the original list, we need to convert that to operations
   * that can be applied to the sorted list.
   *
   * @param {LiveList} list The live list to create the operations for.
   * @param {Object} operation The input 'remove' operation.
   *
   * @return {Array.<Object>} Array of remove operation objects.
   */
  function convertRemoveOperationForList(list, operation) {
    const indices = [];
    const operationKeys = operation.keys;
  
    list.keys.forEach(function (key, i) {
      if (operationKeys.indexOf(key) > -1) indices.push(i);
    });
  
    return listOperations.getRemoveOperations(indices);
  }
  
  const regExp = exports.matches = /^spotify:internal:sortlist:(asc|desc):([^:]*):(.*)$/;
  
  let registered = false;
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    live.subscribe(regExp, 'init', onInit);
    live.subscribe(regExp, 'wait', onWait);
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    live.unsubscribe(regExp, 'init', onInit);
    live.unsubscribe(regExp, 'wait', onWait);
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../live":172,"../live-list-operations":117,"../live-sort-uri":170,"../live/util/parser":175,"./sorting/sort":152,"./sorting/values":155,"mout/object/mixIn":471}],157:[function(require,module,exports){
  'use strict';
  
  var _messageProxy = require('../../libs/message-proxy');
  
  /**
   * @module live-models/station
   */
  
  const live = require('../live');
  
  // Utilities
  const trackToRow = require('./station/track-to-row');
  const contains = require('mout/array/contains');
  const spotifyURI = require('spotify-liburi');
  const thumbActions = require('./station/thumb-actions');
  
  const URI = exports.URI = 'spotify:station:';
  const regExp = exports.matches = new RegExp(`^${URI}(` + '((artist:|album:|track:)[a-zA-Z0-9]{22})' + '|' + '(user:[^:]+:' + '(playlist:[a-zA-Z0-9]{22}|top:tracks|toplist))' + '|' + '(user:[^:]+:cluster:[a-zA-Z0-9]{22})' + '|' + '(genre:[a-zA-Z0-9]*)' + ')');
  const isStationUri = exports.isStationUri = regExp.test.bind(regExp);
  
  const clusterRegExp = new RegExp(`^${URI}(` + '(user:[^:]+:cluster:[a-zA-Z0-9]{22})' + ')');
  exports.isClusterStationUri = regExp.test.bind(clusterRegExp);
  
  const DEFAULT_FETCH_COUNT = 15;
  
  // Station Types
  const PLAYLIST = 'playlist';
  const TRACK = 'track';
  const ALBUM = 'album';
  const ARTIST = 'artist';
  const GENRE = 'genre';
  const CLUSTER = 'cluster';
  const UNKNOWN = 'unknown';
  
  let registered = false;
  let metadataUpdateUnsubscribe;
  
  let trackingRows = []; // A list of stations that need row updates.
  let trackingMetadata = []; // A list of stations that need metadata updates.
  
  /**
   * Get the station's base seed from the live model uri
   * @param {Object} model
   * @return {String}
   */
  function getBaseSeedFromUri(model) {
    const seed = model.uri.replace(/^spotify:station:/, 'spotify:');
    return decodeURIComponent(seed);
  }
  
  /**
   * Get the station type
   * @param {String} seed
   * @return {String}
   */
  function getType(seed) {
    const uri = spotifyURI.from(seed);
    if (!uri) {
      return UNKNOWN;
    }
    const contextType = uri.type;
    if (spotifyURI.isPlaylistV1OrV2(uri)) {
      return PLAYLIST;
    } else if (contextType === spotifyURI.Type.ARTIST) {
      return ARTIST;
    } else if (contextType === spotifyURI.Type.ALBUM) {
      return ALBUM;
    } else if (contextType === spotifyURI.Type.TRACK) {
      return TRACK;
    } else if (seed.indexOf('spotify:genre:') === 0) {
      return GENRE;
    } else if (seed.indexOf(':cluster:') > 0) {
      return CLUSTER;
    }
    return UNKNOWN;
  }
  
  /**
   * Thumb up a track
   * @param {Object} model
   * @param {String} trackUID
   */
  function onThumbUp(model, trackUri) {
    (0, _messageProxy.messageProxy)({
      method: 'POST',
      uri: 'station-thumb',
      body: {
        stationUri: model.get('uri'),
        trackUri: trackUri,
        thumb: thumbActions.UP
      }
    });
  }
  
  /**
   * Thumb down a track
   * @param {Object} model
   * @param {String} trackUID
   */
  function onThumbDown(model, trackUri) {
    (0, _messageProxy.messageProxy)({
      method: 'POST',
      uri: 'station-thumb',
      body: {
        stationUri: model.get('uri'),
        trackUri: trackUri,
        thumb: thumbActions.DOWN
      }
    });
  }
  
  /**
   * Heart a track
   * @param {Object} model
   * @param {String} trackUID
   */
  function onHeart(model, trackUri) {
    if (!isStationUri(model.get('uri'))) {
      return;
    }
  
    (0, _messageProxy.messageProxy)({
      method: 'POST',
      uri: 'station-heart',
      body: {
        stationUri: model.get('uri'),
        trackUri: trackUri
      }
    });
  }
  
  function onUndoHeart(model, trackUri) {
    if (!isStationUri(model.get('uri'))) {
      return;
    }
  
    (0, _messageProxy.messageProxy)({
      method: 'POST',
      uri: 'station-undo-feedback',
      body: {
        stationUri: model.get('uri'),
        trackUri: trackUri,
        thumbAction: thumbActions.UNDO_HEART
      }
    });
  }
  
  /**
   * Post a request for data from the container (station-manager in zlink).
   * @param {Object} model
   * @param {Array} properties
   * @param {Boolean} needsRows
   * @param {Integer} fetchCount
   */
  function requestStationData(model, properties, needsRows, fetchCount) {
    const count = needsRows ? fetchCount || model.get('fetchCount') : false;
    (0, _messageProxy.messageProxy)({
      method: 'GET',
      uri: 'station',
      target: 'top',
      body: {
        stationUri: model.get('uri'),
        metadata: properties,
        trackCount: count
      }
    }, function onStationGet(err, opts) {
      if (err) {
        (0, _messageProxy.messageProxy)({
          method: 'POST',
          uri: 'station-error',
          body: { stationUri: model.get('uri'), message: err }
        });
        return;
      }
      const stationUri = opts.stationUri;
      const station = opts.station;
      if (station.hasOwnProperty('tracks')) {
        if (trackingRows.indexOf(stationUri) !== -1) {
          const timeStamp = new Date().getTime();
          station.rows = station.tracks.map(trackToRow.bind(null, timeStamp));
        }
        // Never keep a tracks object in the station (needs to be rows).
        delete station.tracks;
      }
      live(stationUri).update(station);
    });
  }
  
  /**
   * Callback for rows wait event.
   * @param {Object} model
   * @param {Array} ranges
   */
  function onRowsWait(model, ranges) {
    if (ranges && ranges.length > 0) {
      let fetchCount = 0;
      let largestTrack = 0;
      ranges.forEach(function (a) {
        if (a.end > largestTrack) {
          largestTrack = a.end;
        }
      });
      fetchCount = largestTrack - model.get('rows').length;
      requestStationData(model, [], true, fetchCount);
    }
  }
  
  /**
   * Subscriber for updateMetadata messages
   * @param {Object} opts
   */
  function onUpdateMetadata(opts) {
    const stationUri = opts.stationUri;
    const metadata = opts.metadata;
    if (trackingMetadata.indexOf(stationUri) === -1) {
      return;
    }
    if (metadata.hasOwnProperty('tracks')) {
      if (trackingRows.indexOf(stationUri) !== -1) {
        const timeStamp = new Date().getTime();
        metadata.rows = metadata.tracks.map(trackToRow.bind(null, timeStamp));
      }
      // Never keep a tracks object in the station (needs to be rows).
      delete metadata.tracks;
    }
    live(stationUri).update(metadata);
  }
  
  /**
   * When the model intializes
   * @param {Object} model
   */
  function onInit(model) {
    const baseSeed = getBaseSeedFromUri(model);
    if (!baseSeed) {
      return;
    }
    model.update({
      baseSeed: baseSeed,
      type: getType(baseSeed),
      fetchCount: DEFAULT_FETCH_COUNT
    });
    model.on('update', function onModelUpdate(properties) {
      if (properties.hasOwnProperty('rows')) {
        model.get('rows').on('wait', onRowsWait.bind(null, model));
        model.off('update', onModelUpdate);
      }
    });
    trackingMetadata.push(model.get('uri'));
  }
  
  /**
   * Model publish listener
   * @param {Object} model station
   * @param {Object} properties
   */
  function onPublish(model, properties) {
    if (properties.hasOwnProperty('added')) {
      const added = properties.added;
      if (typeof added === 'boolean') {
        model.update({ added: added });
        (0, _messageProxy.messageProxy)({
          method: 'POST',
          uri: 'station-add',
          body: {
            stationUri: model.get('uri'),
            add: added
          }
        });
      } else {
        console.error('Added can only be a boolean');
      }
    }
  }
  
  /**
   * Unset props listener
   * @param {Object} model station
   * @param {Object} properties
   */
  function onWait(model, properties) {
    let rowsIndex = -1;
    let needsMetadata;
    const metaFields = ['added', 'name', 'subtitles', 'relatedArtists', 'image', 'nextPageUrl'];
  
    properties.forEach(function (key, index) {
      if (key === 'rows') {
        rowsIndex = index;
      }
      if (!needsMetadata && contains(metaFields, key)) {
        needsMetadata = true;
      }
    });
  
    if (rowsIndex !== -1) {
      trackingRows.push(model.get('uri'));
      properties.splice(rowsIndex, 1);
      requestStationData(model, properties, true);
    } else if (needsMetadata) {
      requestStationData(model, properties);
    }
  }
  
  exports.register = function () {
    if (registered) {
      return;
    }
    registered = true;
  
    live.subscribe(regExp, 'wait', onWait);
    live.subscribe(regExp, 'init', onInit);
    live.subscribe(regExp, 'thumb-up', onThumbUp);
    live.subscribe(regExp, 'thumb-down', onThumbDown);
    live.subscribe(regExp, 'heart', onHeart);
    live.subscribe(regExp, 'undo-heart', onUndoHeart);
    live.subscribe(regExp, 'publish', onPublish);
    metadataUpdateUnsubscribe = (0, _messageProxy.messageProxy)({
      method: 'SUB',
      uri: 'station-metadata'
    }, onUpdateMetadata);
  };
  
  exports.unregister = function () {
    if (!registered) {
      return;
    }
    registered = false;
  
    live.unsubscribe(regExp, 'wait', onWait);
    live.unsubscribe(regExp, 'init', onInit);
    live.unsubscribe(regExp, 'thumb-up', onThumbUp);
    live.unsubscribe(regExp, 'thumb-down', onThumbDown);
    live.unsubscribe(regExp, 'heart', onHeart);
    live.unsubscribe(regExp, 'publish', onPublish);
    metadataUpdateUnsubscribe();
    trackingRows = [];
    trackingMetadata = [];
  };
  
  },{"../../libs/message-proxy":187,"../live":172,"./station/thumb-actions":161,"./station/track-to-row":162,"mout/array/contains":434,"spotify-liburi":553}],158:[function(require,module,exports){
  'use strict';
  
  const rowToPlayerTrack = require('./row-to-player-track');
  
  const PLAYER_RESTRICTIONS = {
    disallow_toggling_repeat_context_reasons: ['disallow-radio'],
    disallow_toggling_repeat_track_reasons: ['disallow-radio'],
    disallow_toggling_shuffle_reasons: ['disallow-radio']
  };
  
  /**
   * Create an array of tracks formatted for the context player
   * @param {Object} model
   * @return {Array} tracks array
   */
  function createPlayerTracks(model) {
    const rows = model.get('rows');
    if (!rows) {
      return [];
    }
    return rows.values.map(rowToPlayerTrack);
  }
  
  /**
   * Construct a context object for context-player
   * @param {Object} model station live model
   * @return {Object} context
   */
  module.exports = function createPlayContext(model) {
    return {
      entity_uri: model.get('uri'),
      pages: [{
        tracks: createPlayerTracks(model),
        next_page_url: model.get('nextPageUrl')
      }],
      metadata: {
        context_description: model.get('name')
      },
      restrictions: PLAYER_RESTRICTIONS
    };
  };
  
  },{"./row-to-player-track":160}],159:[function(require,module,exports){
  'use strict';
  
  const PLAYER_OPTIONS = {
    shuffling_context: false,
    repeating_context: false,
    repeating_track: false
  };
  
  /**
   * This is neccesary because you dont get pages back from the context-player.
   * So if you want to resume a station with a set of rows (not pages)
   * you have to know the index of the last played row,
   * which you can get by comparing currentTrackUid with the row's Uid.
   * @param {Object} model station live model
   * @return {Int} index
   */
  function getLastIndex(model) {
    const rows = model.get('rows');
    const currentTrackUid = model.get('currentTrackUid');
  
    if (!rows || !currentTrackUid) {
      return 0;
    }
  
    let currentIndex;
  
    const foundRow = rows.values.some(function (row, index) {
      if (row.get('uid') === currentTrackUid) {
        currentIndex = index;
        return true;
      }
      return false;
    });
  
    return foundRow ? currentIndex : 0;
  }
  
  /**
   * Creates the context-player options for a station context
   * @param {Object} model station model
   * @param {Object} options optional
   * @return {Object} playOptions
   */
  module.exports = function createPlayOptions(model, options) {
    let trackIndex;
    if (options && (options.index || options.index === 0)) {
      trackIndex = options.index;
    } else {
      trackIndex = getLastIndex(model);
    }
    const playOptions = {
      skip_to_index: {
        page: 0,
        track: trackIndex
      },
      player_options_override: PLAYER_OPTIONS
    };
    if (options && options.uid) {
      playOptions.uid = options.uid;
    }
    return playOptions;
  };
  
  },{}],160:[function(require,module,exports){
  'use strict';
  
  /**
   * Converts a station row into a track for the context-player
   * @param {Object} row station row
   * @return {Object} track
   */
  
  module.exports = function rowToPlayerTrack(row) {
    return {
      uid: row.get('uid'),
      uri: row.get('track').get('uri'),
      album_uri: row.get('track').get('albumUri'),
      artist_uri: row.get('track').get('artistUri'),
      metadata: {
        'image_url': row.get('track').get('image'),
        'radio.thumb': row.get('track').get('thumb')
      }
    };
  };
  
  },{}],161:[function(require,module,exports){
  'use strict';
  
  module.exports = {
    UP: 'ups',
    DOWN: 'downs',
    UNDO_HEART: 'undo-heart',
    UNDO_BAN_ARTIST: 'undo-ban-artist',
    UNDO_BAN_TRACK: 'undo-ban-track'
  };
  
  },{}],162:[function(require,module,exports){
  'use strict';
  
  /**
   * Convert track objects coming from station-manager into a row.
   * @param {Object} track
   * @return {Object} row
   */
  
  module.exports = function trackToRow(timeStamp, track, index) {
    const metadata = track.metadata || {};
    const albumUri = track.album_uri || metadata.album_uri;
    let uid = track.uid;
  
    if (!track.uid) {
      const id = track.uri.replace('spotify:track:', '');
      uid = timeStamp + id + index;
    }
  
    return {
      uid: uid,
      uri: uid,
      track: {
        uri: track.uri,
        name: metadata.title,
        image: metadata.image_url,
        albumUri: albumUri,
        artists: transformArtists(track.metadata),
        album: { name: metadata.album_title, uri: albumUri },
        thumb: metadata['radio.thumb'] || ''
      }
    };
  };
  
  function transformArtists(metadata) {
    const artists = [];
  
    for (let i = 0;; i++) {
      const suffix = i > 0 ? `:${i}` : '';
      const uri = metadata[`artist_uri${suffix}`];
      const name = metadata[`artist_name${suffix}`];
      if (uri && name) {
        artists.push({ uri: uri, name: name });
      } else {
        break;
      }
    }
  
    return artists;
  }
  
  },{}],163:[function(require,module,exports){
  (function (global){
  /**
   * @module live-models/track
   */
  'use strict';
  
  const live = require('../live');
  const liburi = require('spotify-liburi');
  const intersection = require('mout/array/intersection');
  const contains = require('mout/array/contains');
  const bridge = require('./util/bridge').request;
  const cosmos = require('./util/cosmos');
  const util = require('./util/common');
  
  const BACKOFF = 100;
  let localFilesRequest = null;
  
  const metaFields = ['album', 'artists', 'availability', 'disc', 'duration', 'explicit', 'image', 'local', 'name', 'number', 'placeholder', 'playable', 'linkedTrack'];
  
  function onWait(model, properties) {
    // Set the 'unavailable' property based on the 'playable' property
    if (contains(properties, 'unavailable')) {
      model.get('playable', function (error, playable) {
        if (error) {
          if (global.console) console.error(error);
          return;
        }
        model.update({ unavailable: !playable });
      });
    }
  
    const hasMetadata = !!intersection(properties, metaFields).length;
  
    if (hasMetadata) updateMetadata(model);
  
    const needsShouldBeOffline = contains(properties, 'shouldBeOffline');
    const needsOfflineProgress = contains(properties, 'offlineProgress');
    if (needsShouldBeOffline || needsOfflineProgress) {
      updateOffline(model);
    }
  }
  
  function updateMetadata(model) {
    bridge('track_metadata', [model.uri], function (error, payload) {
      if (error) {
        if (global.console) console.error(error);
        return;
      }
      util.setLocalProperties(payload);
      payload.image = largestImage(payload);
  
      // The linkedTrack is not available everywhere, so do fallback
      if (payload.linkedTrack) {
        payload.linkedTrack = { uri: payload.linkedTrack };
      } else {
        payload.linkedTrack = null;
      }
  
      model.update(payload);
    });
  }
  
  function largestImage(trackMetadata) {
    let imageURI = trackMetadata.image;
    let largestSize;
  
    if (trackMetadata.images) {
      for (let i = 0; i < trackMetadata.images.length; i++) {
        const currentImage = trackMetadata.images[i];
        if (!largestSize || currentImage[0] > largestSize) {
          largestSize = currentImage[0];
          imageURI = currentImage[1];
        }
      }
    }
  
    return imageURI;
  }
  
  function updateOffline(model) {
    // Local tracks are available offline if they are playable
    if (liburi.fromString(model.uri).type === liburi.Type.LOCAL) {
      model.get('playable', function (error, playable) {
        if (error) {
          if (global.console) console.error(error);
          return;
        }
        model.update({
          shouldBeOffline: playable,
          offlineProgress: playable ? 1 : 0
        });
      });
      return;
    }
  
    cosmos.subscribe({ url: `sp://offline/v1/resources?uri=${model.uri}` }, function (error, response) {
      if (!error) {
        const resource = response.body.resources;
        updateModelWithOfflineData(model, resource);
      } else {
        // Status -104 (or 404 in web player) means 'No endpoint resolver that could handle the request was found'.
        // If that's the case, we handle it like the client doesn't support offline.
        const statusCode = error.response && error.response.getStatusCode();
        if (statusCode === -104 || statusCode === 404) {
          updateModelWithOfflineData(model, { offline_availability: 'no' });
        } else {
          if (global.console) console.error(error);
          return;
        }
      }
    });
  }
  
  function updateModelWithOfflineData(model, resource) {
    let shouldBeOffline = false;
    let offlineProgress = 0;
  
    switch (resource.offline_availability) {
      case 'waiting':
        shouldBeOffline = true;
        offlineProgress = 0;
        break;
      case 'downloading':
        shouldBeOffline = true;
        offlineProgress = 0.5;
        break;
      case 'yes':
        shouldBeOffline = true;
        offlineProgress = 1;
        break;
      case 'no':
        shouldBeOffline = false;
        offlineProgress = 0;
        break;
      default:
        console.error('Unexpected resource.offline_availability', resource.offline_availability);
        break;
    }
  
    model.update({
      shouldBeOffline: shouldBeOffline,
      offlineProgress: offlineProgress
    });
  }
  
  let registered;
  
  function waitForLocalFilesChange() {
    localFilesRequest = cosmos.subscribe({ url: 'sp://local-files/v1/changes' }, function (error, response) {
      if (!registered) return;
  
      if (error) {
        // Status -104 (or 404 in web player) means 'No endpoint resolver that could handle the request was found'.
        // If that's the case, we bail out and don't do anything.
        const statusCode = error.response && error.response.getStatusCode();
        if (statusCode === -104 || statusCode === 404) {
          return;
        }
        if (global.console) console.error(error);
        return;
      }
  
      const event = response.body;
      const data = event.data;
      const type = event.type;
      const isAdded = type === 'added';
      const isRemoved = type === 'removed';
  
      if (isAdded || isRemoved) {
        data.tracks.forEach(function (track) {
          live(track.uri).update({
            shouldBeOffline: isAdded,
            offlineProgress: isAdded ? 1 : 0,
            playable: isAdded
          });
        });
      }
    });
  }
  
  function waitForTrackChange() {
    bridge('track_event_wait_any', [], function (error, event) {
      if (!registered) return;
  
      if (error) {
        setTimeout(function () {
          waitForTrackChange();
        }, BACKOFF);
        if (global.console) console.error(error);
        return;
      }
  
      waitForTrackChange();
  
      if (event.type === 'change') {
        if (event.data.playable !== undefined) {
          event.data.unavailable = !event.data.playable;
        }
        live(event.data.uri).update(event.data);
      }
    });
  }
  
  const regExp = exports.matches = /^spotify:track:|^spotify:local:[^:]*:[^:]*:[^:]*:\d*$/;
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    live.subscribe(regExp, 'wait', onWait);
  
    waitForLocalFilesChange();
  
    // Only desktop has support for track relinking
    if (global._getSpotifyModule) {
      waitForTrackChange();
    }
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    live.unsubscribe(regExp, 'wait', onWait);
  
    if (localFilesRequest) {
      localFilesRequest.cancel();
      localFilesRequest = null;
    }
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../live":172,"./util/bridge":165,"./util/common":166,"./util/cosmos":167,"mout/array/contains":434,"mout/array/intersection":441,"spotify-liburi":553}],164:[function(require,module,exports){
  (function (global){
  /**
   * @module live-models/user
   */
  'use strict';
  
  const live = require('../live');
  const liburi = require('spotify-liburi');
  const contains = require('mout/array/contains');
  const intersection = require('mout/array/intersection');
  const bridge = require('./util/bridge').request;
  
  function updateMetadata(model) {
    bridge('user_metadata', [model.uri], function (error, payload = {}) {
      if (error && global.console) console.error(error);
  
      payload.username = payload.username || liburi.from(model.uri).username;
      payload.name = payload.name || payload.username;
      payload.image = payload.image || '';
      payload.images = payload.images || [];
  
      // Has to be truthy check
      // because can be undefined or null
      if (!payload.subscribed) {
        payload.subscribed = false;
      }
  
      // Has to be truthy check
      // because can be undefined or null
      if (!payload.currentUser) {
        live('spotify:client').query('currentUser(uri)', function (innerError, data) {
          if (innerError) {
            if (global.console) console.error(innerError);
          } else {
            model.update({ currentUser: model.uri === data.currentUser.uri });
          }
        });
      }
  
      model.update(payload);
    });
  }
  
  function updateArtist(model) {
    bridge('user_associated_artist', [model.uri], function (error, payload) {
      if (error) {
        if (global.console) console.error(error);
        return;
      }
      const artist = payload.artist ? { uri: payload.artist } : null;
      model.update({ artist: artist });
    });
  }
  
  function onWait(model, properties) {
    const hasArtist = contains(properties, 'artist');
    const metaFields = ['currentUser', 'name', 'username', 'image', 'images', 'subscribed'];
    const hasMetadata = !!intersection(properties, metaFields).length;
  
    if (hasMetadata) updateMetadata(model);
    if (hasArtist) updateArtist(model);
  }
  
  const regExp = exports.matches = /^spotify:user:[^:]+$/;
  let registered;
  exports.register = function () {
    if (registered) return;
    registered = true;
  
    live.subscribe(regExp, 'wait', onWait);
  };
  
  exports.unregister = function () {
    if (!registered) return;
    registered = false;
  
    live.unsubscribe(regExp, 'wait', onWait);
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../live":172,"./util/bridge":165,"mout/array/contains":434,"mout/array/intersection":441,"spotify-liburi":553}],165:[function(require,module,exports){
  /**
   * @module live-models/util/bridge
   * @private
   */
  
  'use strict';
  
  const bridge = require('../../bridge');
  
  module.exports = {
    request: bridge.request
  };
  
  },{"../../bridge":53}],166:[function(require,module,exports){
  'use strict';
  
  const liburi = require('spotify-liburi');
  
  function setLocalProperties(track) {
    if (track.album && track.album.uri) {
      const albumType = liburi.from(track.album.uri).type;
      track.album.local = albumType === liburi.Type.LOCAL_ALBUM;
    }
    if (track.artists) {
      for (let i = 0, l = track.artists.length; i < l; i++) {
        const artist = track.artists[i];
        if (artist.uri) {
          const artistType = liburi.from(artist.uri).type;
          artist.local = artistType === liburi.Type.LOCAL_ARTIST;
        }
      }
    }
  }
  
  module.exports = {
    setLocalProperties: setLocalProperties
  };
  
  },{"spotify-liburi":553}],167:[function(require,module,exports){
  (function (global){
  /**
   * @module live-models/util/cosmos
   * @private
   */
  
  'use strict';
  
  const cosmos = require('spotify-cosmos-api');
  
  function DELETE(options, callback) {
    options.method = exports.cosmos.Action.DELETE;
    return request(options, callback);
  }
  
  function GET(options, callback) {
    options.method = exports.cosmos.Action.GET;
    return request(options, callback);
  }
  
  function SUB(options, callback) {
    options.method = exports.cosmos.Action.SUB;
    return request(options, callback);
  }
  
  function POST(options, callback) {
    options.method = exports.cosmos.Action.POST;
    return request(options, callback);
  }
  
  function PUT(options, callback) {
    options.method = exports.cosmos.Action.PUT;
    return request(options, callback);
  }
  
  function HEAD(options, callback) {
    options.method = exports.cosmos.Action.HEAD;
    return request(options, callback);
  }
  
  function request(options, callback) {
    const method = options.method;
    delete options.method;
  
    let subscription;
    let canceled;
  
    sanitizeURL(options.url, function (error, url) {
      if (error) {
        if (callback) {
          callback(error);
        }
      } else {
        // if you don't cancel before the userName is replaced.
        if (!canceled) {
          const innerRequest = new exports.cosmos.Request(method || exports.cosmos.Action.GET, url, options.headers, options.body);
          subscription = exports.cosmos.resolver.resolve(innerRequest, function (innerError, response) {
            if (!callback) return;
            if (callback && innerError) {
              callback(innerError);
            } else {
              try {
                callback(null, {
                  body: JSON.parse(response.getBody() || '{}'),
                  headers: response.getHeaders(),
                  status: response.getStatusCode()
                });
              } catch (parseError) {
                parseError.response = response;
                callback(parseError);
              }
            }
          });
        }
      }
    });
  
    return {
      cancel: function () {
        if (subscription && subscription.cancel) {
          // we already subscribed, cancel it.
          subscription.cancel();
          subscription = null;
        } else if (!canceled) {
          // not subscribed yet, don't even subscribe.
          canceled = true;
        }
        return null;
      }
    };
  }
  
  function sanitizeURL(url, callback) {
    if (url.indexOf('@') > -1) {
      callback(null, url.replace('@', encodeURIComponent(global.__spotify.username)));
    } else {
      callback(null, url);
    }
  }
  
  exports.request = request;
  exports.get = GET;
  exports.post = POST;
  exports.subscribe = SUB;
  exports.delete = DELETE;
  exports.put = PUT;
  exports.head = HEAD;
  exports.cosmos = cosmos;
  
  exports.sanitizeURL = sanitizeURL;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"spotify-cosmos-api":543}],168:[function(require,module,exports){
  (function (global){
  "use strict";
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  
  exports.default = () => global.location.href;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{}],169:[function(require,module,exports){
  'use strict';
  
  const Preferences = require('../../preferences');
  
  /**
   * Subscribe for changes of the 'Show Unplayable Tracks' preference.
   *
   * @param {Function} callback Callback function that gets called for
   *                            each change.
   *
   * @return {Object} An object with a `cancel` method.
   */
  function subscribe(callback) {
    const preferences = new Preferences('ui');
  
    return preferences.subscribe('show_unplayable_tracks', function (error, value) {
      if (error) {
        callback(error);
      } else {
        callback(null, !!value);
      }
    });
  }
  
  exports.subscribe = subscribe;
  
  },{"../../preferences":199}],170:[function(require,module,exports){
  'use strict';
  
  const base = 'spotify:internal:sortlist';
  const regExp = /^spotify:internal:sortlist:(asc|desc):([^:]*):(.*)$/;
  
  /**
   * Create a URI representing a sorted variant of a list.
   *
   * @param {string} originUri The original list URI.
   * @param {string} direction The direction of the sorted list ('asc' or 'desc').
   * @param {string} query The query string (compatible with live). Sorting
   *     implementations might not look at all values. The list of produced values
   *     from the query will be tried in order from left to right. If two items have
   *     the same value, it will look at the next produced value from this query.
   *     Any spaces in the query will be removed.
   *
   * @return {string} URI of the sorted variant of the list. The URI will have
   *     the query string URL encoded.
   */
  exports.create = function (originUri, direction, query) {
    const newOriginUri = originUri.replace(/^spotify:/, '');
  
    const newQuery = encodeURIComponent(query).replace(/%20/g, '');
  
    return [base, direction, newQuery, newOriginUri].join(':');
  };
  
  /**
   * Get the direction of the sorted list.
   *
   * @param {string} sortUri The URI of the sorted list.
   *
   * @return {string} The direction of the sort ('asc' or 'desc'). If URI is
   *     invalid, this returns an empty string.
   */
  exports.getDirection = function (sortUri) {
    const matches = sortUri.match(regExp);
    if (!matches) return '';
  
    return matches[1];
  };
  
  /**
   * Get the query string from a sort URI.
   *
   * @param {string} sortUri The URI of the sorted list.
   *
   * @return {string} The query string, URL decoded. If URI is
   *     invalid, this returns an empty string.
   */
  exports.getQuery = function (sortUri) {
    const matches = sortUri.match(regExp);
    if (!matches) return '';
  
    return decodeURIComponent(matches[2]);
  };
  
  /**
   * Get the URI of the original list.
   *
   * @param {string} sortUri The URI of the sorted list.
   *
   * @return {string} The URI of the original list. If the sort URI is invalid,
   *     this returns an empty string.
   */
  exports.getOriginUri = function (sortUri) {
    const matches = sortUri.match(regExp);
    if (!matches) return '';
  
    return `spotify:${matches[3]}`;
  };
  
  /**
   * Parse and return all parts of the sort URI.
   *
   * @param {string} sortUri The URI of the sorted list.
   *
   * @return {Object?} An object with properties `direction`, `query` and `originUri`,
   *     or null if the URI is not valid.
   */
  exports.parse = function (sortUri) {
    const matches = sortUri.match(regExp);
    if (!matches) return null;
  
    return {
      direction: matches[1],
      query: decodeURIComponent(matches[2]),
      originUri: `spotify:${matches[3]}`
    };
  };
  
  /**
   * Test if the provided URI is a valid sort URI.
   *
   * @param {string} sortUri The URI of the sorted list.
   *
   * @return {boolean} True if it's valid, false otherwise.
   */
  exports.isValid = function (sortUri) {
    return regExp.test(sortUri);
  };
  
  /**
   * The regular expression that matches sort URIs.
   */
  exports.regExp = regExp;
  
  },{}],171:[function(require,module,exports){
  'use strict';
  
  const sortUriUtils = require('../live-sort-uri');
  const filterUriUtils = require('../live-filter-uri');
  
  /**
   * Create a URI representing a wrapped variant of a list.
   *
   * @param {object} opts The options.
   * @param {string} opts.originUri The origin uri.
   * @param {object?} opts.filter The filter, if any
   * @param {object?} opts.sort The sorting, if any
   *
   * @return {string} URI of the wrapped variant of the list.
   */
  exports.create = function (opts) {
    let uri = opts.originUri;
    if (opts.filter) {
      uri = filterUriUtils.create(uri, opts.filter.query);
    }
    if (opts.sort) {
      uri = sortUriUtils.create(uri, opts.sort.direction, opts.sort.query);
    }
    return uri;
  };
  
  /**
   * Get the URI of the original list.
   *
   * @param {string} sortUri The URI of the wrapped list.
   *
   * @return {string} The URI of the original list. If the wrapped URI is invalid,
   *     this returns an empty string.
   */
  exports.getOriginUri = function (wrappedUri) {
    const parsed = exports.parse(wrappedUri);
    return parsed && parsed.originUri || '';
  };
  
  /**
   * Parse and return all parts of the wrapped URI.
   *
   * @param {string} wrappedUri The URI of the wrapped list.
   *
   * @return {object} An object with properties `originUri`, `sort` and `filter`.
   */
  exports.parse = function (wrappedUri) {
    const sort = sortUriUtils.parse(wrappedUri);
    const filter = filterUriUtils.parse(sort && sort.originUri || wrappedUri);
    if (!(sort || filter)) return null;
  
    return {
      originUri: filter && filter.originUri || sort && sort.originUri,
      sort: sort,
      filter: filter
    };
  };
  
  /**
   * Test if the provided URI is a valid wrapped URI.
   *
   * @param {string} wrappedUri The URI of the wrapped list.
   *
   * @return {boolean} True if it's valid, false otherwise.
   */
  exports.isValid = function (wrappedUri) {
    return !!exports.parse(wrappedUri);
  };
  
  },{"../live-filter-uri":116,"../live-sort-uri":170}],172:[function(require,module,exports){
  (function (global){
  /* eslint-disable no-use-before-define */
  
  /**
   * @module live
   */'use strict';
  
  // prime
  
  const prime = require('../../libs/prime');
  const defer = require('../../libs/prime/defer');
  const Emitter = require('../../libs/prime/emitter');
  
  // mout
  const isPlainObject = require('mout/lang/isPlainObject');
  const isRegExp = require('mout/lang/isRegExp');
  const isNumber = function (n) {
    return typeof n === 'number';
  };
  const isString = function (s) {
    return typeof s === 'string';
  };
  
  const escapeRegExp = require('mout/string/escapeRegExp');
  
  const _difference = require('mout/array/difference');
  const filter = require('mout/array/filter');
  const combine = require('mout/array/combine');
  const map = require('mout/array/map');
  
  const deepMixIn = require('mout/object/deepMixIn');
  const pick = require('mout/object/pick');
  
  function parallel(tasks, cb) {
    const results = [];
    let pending = tasks.length;
  
    if (!pending) cb(null, results);else {
      tasks.forEach((task, i) => {
        task((err, result) => {
          results[i] = result;
          if (--pending === 0 || err) cb(err, results);
        });
      });
    }
  }
  
  // util
  const OrderedSet = require('./util/ordered-set');
  const Range = require('../range2');
  const parse = require('./util/parser');
  const throttle = require('./util/throttle');
  
  // debugging
  const debug = require('../debug')('live');
  
  // methods
  
  const isLiveList = function (item) {
    return item instanceof LiveList;
  };
  
  const isLiveObject = function (item) {
    return item instanceof LiveObject;
  };
  
  const simpleClone = function (object) {
    return Object.assign({}, object);
  };
  
  const difference = function (a, b) {
    return a.length === 0 ? [] : _difference.call(this, a, b);
  };
  
  const values = function (object, keys) {
    const newValues = [];
    let key;
    for (let i = 0, len = keys.length; i < len; i++) {
      key = keys[i];
      if (key in object) newValues.push(object[key]);else newValues.length++;
    }
    return newValues;
  };
  
  // subtract ranges2 from ranges1
  const rdifference = function (ranges1, ranges2) {
    if (!ranges1.length) return [];
  
    let resultingRanges = [];
  
    for (let i = 0, len = ranges1.length; i < len; i++) {
      const range = ranges1[i];
      resultingRanges = resultingRanges.concat(range.subtract(ranges2));
    }
  
    // Make sure we don't have overlapping ranges
    resultingRanges = new Range(0, 0).merge(resultingRanges);
  
    // Remove any empty ranges
    resultingRanges = filter(resultingRanges, function (range) {
      return !!range.length;
    });
  
    return resultingRanges;
  };
  
  // merge ranges1 into ranges2
  const rcombine = function (ranges1, ranges2) {
    let newRanges2 = ranges2;
    for (let i = 0, len = ranges1.length; i < len; i++) {
      const range = ranges1[i];
      newRanges2 = range.merge(newRanges2);
    }
    return newRanges2;
  };
  
  // util
  let IDX = 0;
  
  const slice_ = Array.prototype.slice;
  
  const transform = function (item, method) {
    if (isPlainObject(item)) {
      // plain objects need to be liveified
      if ('operations' in item) return new LiveList().update(item.operations);
      return live(item.uri)._update(item, method);
    }
  
    if (Array.isArray(item)) {
      // arrays need to be liveified
      return new LiveList()._update([{
        type: 'insert',
        index: 0,
        length: item.length,
        values: item
      }], method);
    }
    return item;
  };
  
  // Creates a callback with a timeout.
  function createTimedCallback(callback, timeout) {
    let called;
    const timeoutId = setTimeout(function () {
      called = true;
      callback(new Error(`Timeout Expired: ${timeout} milliseconds`));
    }, timeout);
    return function () {
      if (!called) {
        clearTimeout(timeoutId);
        callback.apply(this, arguments);
      }
    };
  }
  
  const LiveList = prime(
  /** @lends LiveList.prototype */{
    mixin: Emitter,
  
    /**
     * A representation of an array which may be observed for changes
     * @constructs
     * @mixes Emitter
     * @param {Number} [length] - A number representing the length of the liveList.
     */
    constructor: function LiveList(length) {
      this._data = new OrderedSet(length);
      this._mergeThrottled = throttle(this._merge, this);
      this._waiting = [];
  
      let lastWait = '';
  
      if (live.debug) {
        setInterval(() => {
          const thisWait = this._waiting.toString();
          if (lastWait === thisWait) return;
          lastWait = thisWait;
  
          if (this._waiting.length) {
            debug(this, 'waiting for', this._waiting);
          } else {
            debug(this, 'all done');
          }
        }, 2000);
      }
    },
  
    get index() {
      return this._data.index;
    },
  
    get length() {
      return this._data.length;
    },
  
    get keys() {
      return this._data.keys;
    },
  
    get values() {
      return this._data.values;
    },
  
    indexOf: function (key) {
      return this._data.indexOf(key);
    },
  
    indexOfMany: function (keys) {
      return this._data.indexOfMany(keys);
    },
  
    valueOf: function (key) {
      return this._data.valueOf(key);
    },
  
    hasKey: function (key) {
      return this._data.hasKey(key);
    },
  
    forEach: function (fn, ctx) {
      this._data.forEach(fn, ctx);
      return this;
    },
  
    map: function (fn, ctx) {
      return this._data.map(fn, ctx);
    },
  
    _merge: function () {
      const data = this._data; // actual data
      const publish = this._publish; // new items on top of a copy of data || null
      const before = this._before; // an old copy of data || null
  
      if (publish) {
        delete this._publish;
        if (this._listeners && this._listeners.publish) {
          const publishDiff = data.diff(publish);
          if (publishDiff.length) this.emit('publish', publishDiff, EMIT_SYNC);
        }
      } else if (before) {
        delete this._before;
        if (this._listeners && this._listeners.update) {
          const updateDiff = before.diff(data);
          if (updateDiff.length) this.emit('update', updateDiff, EMIT_SYNC);
        }
      }
      return this;
    },
  
    _update: function (operations, method) {
      let branch;
      if (method === PUBLISH) {
        // it was receiving, cannot publish.
        if (this._before) return this;
        branch = this._publish || (this._publish = this._data.clone());
      } else if (method === UPDATE) {
        // it was publishing, delete it.
        if (this._publish) delete this._publish;
        if (!this._before) this._before = this._data.clone();
        branch = this._data;
      }
  
      let op;
      for (let opIndex = 0, opLen = operations.length; opIndex < opLen; opIndex++) {
        op = operations[opIndex];
        const newValues = [];
        const keys = op.keys || [];
  
        switch (op.type) {
          case 'length':
            branch.length = op.length;
            break;
          case 'sort':
            branch.sort(op.compareFunction);
            break;
          case 'move':
            branch.move(op.from, op.to, op.length);
            break;
          case 'remove':
            branch.remove(op.index, op.length);
            break;
          case 'insert':
            for (let i = 0, len = op.values.length; i < len; i++) {
              if (!op.keys || !op.keys[i]) keys[i] = (IDX++).toString(36);
              newValues[i] = transform(op.values[i], method);
            }
            branch.insert(op.index, keys, newValues);
            break;
          default:
            console.error('Invalid operation type', op.type);
            break;
        }
      }
  
      if (method === UPDATE) {
        this._waiting = rdifference(this._waiting, this.index);
      }
  
      this._mergeThrottled();
      return this;
    },
  
    publish: function (operations) {
      return this._update(operations, PUBLISH);
    },
  
    update: function (operations) {
      return this._update(operations, UPDATE);
    },
  
    /**
     * Serialize the data in this list into a plain object.
     *
     * @param {number=} limit Optional limit parameter. Controls how many levels
     *     deep to serialize.
     *
     * @return {Array} The data array.
     */
    serialize: function (limit) {
      if (limit === 0) return [];
  
      const array = [];
      const nextLimit = limit === undefined ? undefined : limit - 1;
  
      for (let i = 0; i < this.length; i++) {
        const value = this.values[i];
        let serializedValue = value;
        if (value && value.serialize) {
          serializedValue = value.serialize(nextLimit);
        }
        array.push(serializedValue);
      }
  
      return array;
    },
  
    _getDataFromMask: function (mask) {
      const items = [];
  
      if (mask.length > 0) {
        // The provided mask to a list is the mask for each list item, so we need
        // to loop through all the items and get the data from each item based on
        // the mask.
        for (let i = 0, l = this.length; i < l; i++) {
          const item = this.get(i);
          let parsedItem;
          const isObjectOrList = item && item._getDataFromMask;
          if (isObjectOrList) {
            parsedItem = item._getDataFromMask(mask);
          }
          items.push(parsedItem);
        }
      }
  
      return items;
    },
  
    _query: function (selector, callback, mode) {
      const query = () => {
        queryList(this, selector, (error, data, wasSync) => {
          if (error) {
            callback(error);
          } else {
            if (!wasSync) {
              this._query(selector, callback, mode);
            } else {
              callback(null, data);
            }
          }
        });
      };
  
      if (mode === ASYNC) defer.immediate(query);else query();
    },
  
    /**
     * Query properties in this list.
     * This can be called in two different ways: with a callback or without. When
     * calling it with a callback, it will wait for any missing data and always
     * return the data you queried for. When calling it without a callback, it
     * will return an array structure with only the data that was found in the
     * object at the moment. It will not kick off any events for waiting for
     * properties.
     *
     * @param {String} selector - The query selector. Only selectors without
     *     filters are supported when not providing a callback.
     * @param {Array} params - The query replace parameters.
     * @param {LiveList~queryCallback} callback - The callback that handles the response.
     * @param {Object} [mode] live.SYNC: Force sync, 2: live.ASAP: Don't force async
     * @param {Number} timeout - optional timeout in milliseconds
     *
     * @return {LiveList|Array} If no callback is passed it returns an array with
     *                             the data matching the query, that was found at
     *                             the moment in the list.
     *                             If a callback is passed it returns the instance.
     */
    query: function (selector, params, callback, mode, timeout) {
      let newParams = params;
      let newCallback = callback;
      let newMode = mode;
      let newTimeout = timeout;
  
      /**
       * @callback LiveList~queryCallback
       * @param {Error|Null} error - An error that you can throw, or null.
       * @param {Array} [data] - The response as an array.
       */
      if (typeof newParams === 'function') {
        // shift for empty params
        newTimeout = newMode;
        newMode = newCallback;
        newCallback = newParams;
        newParams = [];
      }
  
      if (!newCallback) {
        return this._getDataFromMask(parse(selector, newParams).mask);
      }
  
      if (!newMode) {
        newMode = ASYNC;
      } else if (isNumber(newMode)) {
        // shift once again
        newTimeout = newMode;
        newMode = ASYNC;
      }
  
      if (newTimeout) {
        newCallback = createTimedCallback(newCallback, newTimeout);
      }
  
      this._query(parse(selector, newParams), newCallback, newMode);
      return this;
    },
  
    _wait: function (ranges) {
      if (!this._required) {
        // if _required is set, it was deferred already
        this._required = [];
        defer.immediate(function () {
          const missingFromIndex = this.missing(this._required);
          const missingFromWaiting = rdifference(missingFromIndex, this._waiting);
          this._waiting = rcombine(this._waiting, missingFromWaiting);
          delete this._required;
          if (missingFromWaiting.length) {
            this.emit('wait', missingFromWaiting, EMIT_SYNC);
          }
        }, this);
      }
      this._required = rcombine(this._required, ranges);
    },
  
    missing: function (ranges) {
      return rdifference(ranges, this.index);
    },
  
    // the raw, unbeautified get
    // this can callback sync or async
    _get: function (ranges, callback) {
      if (this.missing(ranges).length) {
        // has missing stuff (no matter what)
        this._wait(ranges);
  
        const check = function () {
          const needed = this.missing(ranges);
          if (!needed.length) {
            this.off('update', check);
            callback.call(this);
          }
        };
  
        this.on('update', check);
      } else {
        // the callback is called with 'true' when syncronous.
        callback.call(this, null, true);
      }
    },
  
    /**
     * Checks if the range has been set with values in the list.
     *
     * @param {number} fromIndex The index to start from.
     * @param {number=} toIndex Optional index to stop at (non-inclusive). If no
     *     toIndex is specified, it will default to the index after fromIndex, to
     *     check for the single item at fromIndex.
     *
     * @return {Boolean} True if the range has been set.
     */
    has: function (fromIndex, toIndex) {
      const hasToIndex = toIndex !== undefined;
      let range;
      if (!hasToIndex) {
        range = new Range(fromIndex, fromIndex + 1);
      } else {
        range = new Range(fromIndex, toIndex);
      }
  
      return range.contained(this.index);
    },
  
    /**
     * Gets items from the list between the specified indices.
     * @param {Number} what - The index to start from.
     * @param {Number} [toIndex=fromIndex + 1] - The index to stop at (non-inclusive).
     * @param {LiveList~getCallback} [callback] - Optional callback function. If no toIndex
     *     is specified, the callback can be placed as the second argument.
     * @param {Object} [mode] live.SYNC: Force sync, 2: live.ASAP: Don't force async
     * @param {Number} timeout - optional timeout in milliseconds
     * @return {LiveList|Array} If no callback is specified, returns the requested items in an array.
     *                          If a callback is specified, the method returns the instance.
     */
    get: function (fromIndex, toIndex) {
      /**
       * @callback LiveList~getCallback
       * @param {Error|Null} error - An error that you can throw, or null.
       * @param {Array|*} [data] - The response as an array.
       *                           If toIndex is not specified a single item on the list is passed.
       */
  
      // fast, get one
      if (arguments.length === 1) {
        if (isNumber(fromIndex)) return this.values[fromIndex];
        if (isString(fromIndex)) return this.valueOf(fromIndex);
      }
  
      const args = slice_.call(arguments);
      let callback;
      let mode;
      let requested;
  
      for (let i = 0, len = args.length; i < len; i++) {
        const arg = args[i];
        if (typeof arg === 'function') {
          const split = args.splice(i, 3);
          len = args.length;
          callback = split[0];
          if (split[1]) {
            if (split[1] === SYNC || split[1] === ASYNC || split[1] === ASAP) {
              mode = split[1];
            } else {
              // assume this arg is a timeout
              mode = ASYNC;
              callback = createTimedCallback(callback, split[1]);
              break;
            }
            // is there a timeout ?
            if (split[2] && mode !== SYNC) {
              callback = createTimedCallback(callback, split[2]);
            }
          } else {
            mode = ASYNC;
          }
          break;
        }
      }
  
      let asArray = false;
      let asSingleValue = false;
  
      if (fromIndex instanceof Range) {
        // one range
        requested = args;
        asArray = !callback && requested.length > 1;
      } else if (Array.isArray(fromIndex)) {
        // array of ranges
        asArray = true;
        requested = fromIndex;
      } else {
        // numbers ?
        const hasToIndex = !isNaN(toIndex);
        if (!hasToIndex) {
          requested = [new Range(fromIndex, fromIndex + 1)];
        } else {
          requested = [new Range(fromIndex, toIndex)];
        }
        asSingleValue = !hasToIndex;
      }
  
      const result = () => {
        return map(requested, range => {
          return this.values.slice(range.start, range.end);
        });
      };
  
      const done = () => {
        if (asArray) callback.call(this, null, result());else if (asSingleValue) {
          callback.call(this, null, this.values[fromIndex]);
        } else callback.apply(this, [null].concat(result()));
      };
  
      if (!callback) {
        if (asArray) return result();
        return result()[0];
      } else if (mode === SYNC) {
        done();
      } else {
        if (mode === ASYNC && !this.missing(requested).length) {
          // it has all the data but needs to be ASYNC
          defer.immediate(function () {
            this._get(requested, done);
          }, this);
        } else {
          this._get(requested, done);
        }
      }
      return this;
    }
  });
  
  const LiveObject = prime(
  /** @lends LiveObject.prototype */{
    mixin: Emitter,
  
    /**
     * A representation of an object which may be observed for changes
     * @constructs
     * @mixes Emitter
     */
    constructor: function LiveObject(_uri) {
      this.uri = _uri;
      this._data = { uri: _uri };
      this._mergeThrottled = throttle(this._merge, this);
      this.index = [];
      this._waiting = [];
      this.emit('init', EMIT_SYNC);
  
      let lastWait = '';
  
      if (live.debug) {
        setInterval(() => {
          const thisWait = this._waiting.toString();
          if (lastWait === thisWait) return;
          lastWait = thisWait;
  
          if (this._waiting.length) {
            debug(this, this._waiting);
          } else {
            debug(this, 'all done');
          }
        }, 2000);
      }
    },
  
    emit: function () {
      const uri = this.uri;
      if (uri) {
        const keys = emitters.keys;
        const innerValues = emitters.values;
        let key;
        let args;
  
        for (let i = 0, len = keys.length; i < len; i++) {
          key = keys[i];
          if (uri.match(key)) {
            if (!args) {
              args = new Array(arguments.length);
              for (let j = 0; j < arguments.length; ++j) {
                args[j] = arguments[j];
              }
              args.splice(1, 0, this);
            }
            const emitter = innerValues[i];
            emitter.emit.apply(emitter, args);
          }
        }
      }
  
      Emitter.prototype.emit.apply(this, arguments);
    },
  
    _merge: function () {
      const data = this._data; // the data
      const before = this._before; // an old copy of data
      const publish = this._publish; // published data
      let value;
      let key;
  
      // published changes
      if (publish) {
        delete this._publish;
  
        // emit change events if the object has a uri that can be subscribed too,
        // or the object in itself has listeners
        if (this.uri || this._listeners && this._listeners.publish) {
          const publishEvent = {};
  
          for (key in publish) {
            if (!publish.hasOwnProperty(key)) continue;
            value = publish[key];
            if (data[key] !== value) publishEvent[key] = value;
          }
  
          if (Object.keys(publishEvent).length) {
            this.emit('publish', publishEvent, EMIT_SYNC);
          }
        }
      }
  
      // if there are changes
      if (before) {
        delete this._before;
  
        const updateEvent = {};
  
        // emit change events if the object has a uri that can be subscribed too,
        // or the object in itself has listeners
        if (this.uri || this._listeners && this._listeners.update) {
          // check changed and added keys
          for (key in data) {
            if (!data.hasOwnProperty(key)) continue;
            value = data[key];
            // there was no key, or value is different
            if (!(key in before) || before[key] !== value) {
              updateEvent[key] = value;
            }
          }
  
          // check deleted keys
          for (key in before) {
            if (!before.hasOwnProperty(key)) continue;
            value = before[key];
            // there is no key so it was deleted
            if (!(key in data)) updateEvent[key] = undefined;
          }
  
          if (Object.keys(updateEvent).length) {
            this.emit('update', updateEvent, EMIT_SYNC);
          }
        }
      }
  
      return this;
    },
  
    _update: function (object, method) {
      let branch;
  
      if (method === PUBLISH) {
        branch = this._publish || (this._publish = simpleClone(this._data));
      } else if (method === UPDATE) {
        if (!this._before) this._before = simpleClone(this._data);
        branch = this._data;
      }
  
      let value;
      for (const key in object) {
        if (!object.hasOwnProperty(key)) continue;
        value = object[key];
        const previous = !(key in branch) ? undefined : branch[key];
  
        if (isLiveList(previous)) {
          // updating lists with an array will keep the reference but replace every item.
          if (Array.isArray(value)) {
            previous._update([{
              type: 'remove',
              index: 0,
              length: previous.length
            }, {
              type: 'insert',
              index: 0,
              values: value
            }], method);
            continue;
          } else if (isPlainObject(value) && 'operations' in value) {
            previous._update(value.operations, method);
            continue;
          }
        }
  
        if (method === UPDATE && value === undefined) delete branch[key];else branch[key] = transform(value, method);
      }
  
      if (method === UPDATE) {
        this.index = Object.keys(branch);
        this._waiting = difference(this._waiting, this.index);
      }
  
      this._mergeThrottled();
      return this;
    },
  
    delete: function (key) {
      const object = {};
      object[key] = undefined;
      return this.update(object);
    },
  
    update: function (object) {
      return this._update(object, UPDATE);
    },
  
    publish: function (object) {
      return this._update(object, PUBLISH);
    },
  
    /**
     * Serialize the data in this object into a plain object.
     *
     * @param {number=} limit Optional limit parameter. Controls how many levels
     *     deep to serialize.
     *
     * @return {Object} The data object.
     */
    serialize: function (limit) {
      if (limit === 0) return {};
  
      const object = {};
      const data = this._data;
      const nextLimit = limit === undefined ? undefined : limit - 1;
  
      for (const key in data) {
        if (!data.hasOwnProperty(key)) continue;
        const value = data[key];
        if (value === undefined) continue;
        let serializedValue = value;
        if (value && value.serialize) {
          serializedValue = value.serialize(nextLimit);
        }
        object[key] = serializedValue;
      }
  
      return object;
    },
  
    _getDataFromMask: function (mask) {
      const data = {};
  
      for (let i = 0, l = mask.length; i < l; i++) {
        const thisMask = mask[i];
        const nextMask = thisMask.mask;
        const key = thisMask.key;
  
        const realValue = this.get(key);
        let value = realValue;
  
        // Set the value to an empty object or array, and then it will be filled
        // in with the found data in the next step.
        if (isLiveObject(realValue)) value = {};
        if (isLiveList(realValue)) value = [];
  
        if (nextMask) {
          if (isLiveObject(realValue)) {
            const innerData = realValue._getDataFromMask(nextMask);
            deepMixIn(value, innerData);
          } else if (isLiveList(realValue)) {
            value = realValue._getDataFromMask(nextMask);
          }
        }
  
        data[key] = value;
      }
  
      return data;
    },
  
    _query: function (selector, callback, mode) {
      const query = () => {
        queryObject(this, selector, (error, data, wasSync) => {
          if (error) {
            callback.call(this, error);
          } else {
            if (!wasSync) {
              this._query(selector, callback, mode);
            } else {
              callback.call(this, null, data);
            }
          }
        });
      };
  
      if (mode === ASYNC) defer.immediate(query);else query();
    },
  
    /**
     * Query properties in this object.
     * This can be called in two different ways: with a callback or without. When
     * calling it with a callback, it will wait for any missing properties and
     * always return the data you queried for. When calling it without a callback,
     * it will return an object structure with only the data that was found in the
     * object at the moment. It will not kick off any events for waiting for
     * properties.
     *
     * @param {String} selector - The query selector. Only selectors without
     *     filters are supported when not providing a callback.
     * @param {Array} params - The query replace parameters. Optional.
     * @param {LiveObject~queryCallback} callback - The callback function.
     * @param {Object} [mode] live.SYNC: Force sync, 2: live.ASAP: Don't force async
     * @param {Number} timeout - optional timeout in milliseconds
     *
     * @return {LiveObject|Object} If no callback is passed it returns an object
     *                             with the data matching the query, that was found
     *                             at the moment in the object.
     *                             If a callback is passed it returns the instance.
     */
    query: function (selector, params, callback, mode, timeout) {
      let newParams = params;
      let newCallback = callback;
      let newMode = mode;
      let newTimeout = timeout;
  
      /**
       * @callback LiveObject~queryCallback
       * @param {Error|Null} error - An error that you can throw, or null.
       * @param {LiveObject} [data] - The response as a plain object.
       */
      if (typeof newParams === 'function') {
        // shift for empty params
        newTimeout = newMode;
        newMode = newCallback;
        newCallback = newParams;
        newParams = [];
      }
  
      if (!newCallback) {
        return this._getDataFromMask(parse(selector, newParams).mask);
      }
  
      if (!newMode) {
        newMode = ASYNC;
      } else if (isNumber(newMode)) {
        // shift once again
        newTimeout = newMode;
        newMode = ASYNC;
      }
  
      if (newTimeout) {
        newCallback = createTimedCallback(newCallback, newTimeout);
      }
  
      this._query(parse(selector, newParams), newCallback, newMode);
      return this;
    },
  
    _wait: function (keys) {
      if (!this._required) {
        // if _required is set, it was deferred already
        this._required = [];
        defer.immediate(function () {
          const missingFromIndex = this.missing(this._required);
          const missingFromWaiting = difference(missingFromIndex, this._waiting);
          this._waiting = combine(this._waiting, missingFromWaiting);
          delete this._required;
          if (missingFromWaiting.length) {
            this.emit('wait', missingFromWaiting, EMIT_SYNC);
          }
        }, this);
      }
      this._required = combine(this._required, keys);
    },
  
    missing: function (keys) {
      return difference(keys, this.index);
    },
  
    /**
     * Checks if the key has been set in the object.
     *
     * @param {string} key The name of the key.
     *
     * @return {Boolean} True if the key has been set.
     */
    has: function (key) {
      return this.index.indexOf(key) > -1;
    },
  
    // the raw, unbeautified get
    // this can callback sync or async
    _get: function (keys, callback) {
      if (this.missing(keys).length) {
        this._wait(keys);
  
        const check = function () {
          const needed = this.missing(keys);
          if (!needed.length) {
            this.off('update', check);
            callback.call(this);
          }
        };
        this.on('update', check);
      } else {
        // the callback is called with 'true' when syncronous.
        callback.call(this, null, true);
      }
    },
  
    /**
     * Gets values from the object.
     * @param {...String|Array} keys - An array of strings as arguments.
     * @param {LiveObject~getCallback} [callback] - Optional callback function.
     * @param {Object} [mode] live.SYNC: Force sync, 2: live.ASAP: Don't force async
     * @param {Number} timeout - optional timeout in milliseconds
     * @return {LiveObject|Array|*} If no callback is passed and keys is either an array or multiple arguments
     *                             returns the values in an array.
     *                             If no callback is passed and keys is a single parameter it
     *                             returns the requested value (any type).
     *                             If a callback is passed it returns the instance.
     */
    get: function (key) {
      /**
       * @callback LiveObject~getCallback
       * @param {Error|Null} error - An error that you can throw, or null.
       * @param {Array|...*} [data] - If keys are requested as arguments (any #)
       *                              it will return many results as arguments.
       *                              If keys are requested as an array (any #), it will return data as an array
       * @param {Number} [mode] 1: FORCE SYNC, 2: DON'T FORCE ASYNC
       */
  
      // fast, get one
      const data = this._data;
      if (arguments.length === 1 && isString(key)) return data[key];
  
      const args = slice_.call(arguments);
      let callback;
      let mode;
      let keys;
  
      for (let i = 0, len = args.length; i < len; i++) {
        const arg = args[i];
        if (typeof arg === 'function') {
          const split = args.splice(i, 3);
          len = args.length;
          callback = split[0];
          if (split[1]) {
            if (split[1] === SYNC || split[1] === ASYNC || split[1] === ASAP) {
              mode = split[1];
            } else {
              // assume this arg is a timeout
              mode = ASYNC;
              callback = createTimedCallback(callback, split[1]);
              break;
            }
            // is there a timeout ?
            if (split[2] && mode !== SYNC) {
              callback = createTimedCallback(callback, split[2]);
            }
          } else {
            mode = ASYNC;
          }
          break;
        }
      }
  
      let asArray = false;
  
      if (Array.isArray(key)) {
        keys = key;
        asArray = true;
      } else {
        keys = args;
      }
  
      const done = () => {
        const vals = values(data, keys);
        if (asArray) {
          callback.call(this, null, vals);
        } else {
          vals.unshift(null); // unshift no error
          callback.apply(this, vals);
        }
      };
  
      if (!callback) {
        return values(data, keys);
      } else if (mode === SYNC) {
        done();
      } else {
        if (mode === ASYNC && !this.missing(keys).length) {
          // it has all the data but needs to be ASYNC
          defer.immediate(() => {
            this._get(keys, done);
          });
        } else {
          this._get(keys, done);
        }
      }
  
      return this;
    }
  });
  
  const queryAny = function (object, selector, callback) {
    if (isPlainObject(object) || isLiveObject(object)) {
      return queryObject(object, selector, callback);
    } else if (Array.isArray(object) || isLiveList(object)) {
      return queryList(object, selector, callback);
    }
    return callback(null, object, true);
  };
  
  const queryObject = function (object, selector, callback) {
    const data = {};
    let isSync = true;
  
    if (!selector) callback(null, data, isSync);
  
    const keys = map(selector.mask, 'key');
  
    const done = function (innerValues) {
      parallel(innerValues.map(function (objectValue, i) {
        return function (innerCallback) {
          queryAny(objectValue, selector.mask[i], function (error, dataValue, wasSync) {
            if (error) {
              innerCallback(error);
              return;
            }
            if (!wasSync) {
              isSync = false;
            }
            if (data[keys[i]]) {
              deepMixIn(data[keys[i]], dataValue);
            } else {
              data[keys[i]] = dataValue;
            }
            innerCallback();
          });
        };
      }), function (error) {
        callback(error, data, isSync);
      });
    };
  
    if (isLiveObject(object)) {
      object._get(keys, function (error, wasSync) {
        if (error) {
          callback(error);
        } else {
          if (!wasSync) isSync = false;
          const innerValues = map(keys, function (key) {
            return object._data[key];
          });
          done(innerValues);
        }
      });
    } else {
      const innerValues = pick(object, keys);
      done(innerValues);
    }
  };
  
  const satisfies = function (value, op, right) {
    if (!op) return !!value;
    if (isRegExp(right)) return right.test(value);
  
    if (op === '=') return value === right;
    if (op === '!=') return value !== right;
  
    if (isNumber(right)) {
      if (op === '>') return value > right;
      if (op === '>=') return value >= right;
      if (op === '<') return value < right;
      if (op === '<=') return value <= right;
    }
  
    if (isString(right)) {
      const escapedRight = escapeRegExp(right);
      // starts with
      if (op === '^=') return new RegExp(`^${escapedRight}`).test(value);
      // ends with
      if (op === '$=') return new RegExp(`${escapedRight}$`).test(value);
      // contains separated by space e.g. 'list of things to look for' ~= 'list'
      if (op === '~=') {
        return new RegExp(`(^|\\s)${escapedRight}(\\s|$)`).test(value);
      }
      // contains separated by - e.g. 'list-of-things-to-look-for' ~= 'things'
      if (op === '|=') return new RegExp(`^${escapedRight}(-|$)`).test(value);
      // contains e.g. 'listofthingstolookfor' ~= 'look'
      if (op === ' *=') return value.toString().indexOf(right) !== -1;
    }
  
    return false;
  };
  
  const filterArray = function (array, left, op, right, callback) {
    let isSync = true;
    const filtered = [];
  
    let todo = array.length;
    let failed = false;
    function done(error) {
      if (error) {
        if (!failed) {
          failed = true;
          callback(error);
        }
        return;
      }
      if (failed || --todo > 0) {
        return;
      }
      const clean = [];
      for (let i = 0; i < filtered.length; i++) {
        if (i in filtered) {
          clean.push(filtered[i]);
        }
      }
      callback(null, clean, isSync);
    }
  
    for (let i = 0; i < array.length; i++) {
      const item = array[i];
  
      if (isLiveObject(item)) {
        /* eslint-disable */
        item._get([left], function (error, wasSync) {
          if (error) {
            done(error);
            return;
          }
          if (!wasSync) {
            isSync = false;
          }
          let value = item._data[left];
          if (satisfies(value, op, right)) {
            filtered[i] = item;
          }
          done();
        });
        /* eslint-enable */
      } else {
        if (isPlainObject(item)) {
          // filter objects only
          if (satisfies(item[left], op, right)) {
            filtered[i] = item;
          }
        }
        done();
      }
    }
  };
  
  // default filter that filters the whole list.
  const defaultFilters = [[{
    left: 0,
    op: ':'
  }]];
  
  function runSeriallyOnArray(array, startAtIndex, asyncFn, callback) {
    if (startAtIndex >= array.length) {
      callback();
      return;
    }
    asyncFn(array[startAtIndex], function (error) {
      if (error) {
        callback(error);
        return;
      }
      runSeriallyOnArray(array, startAtIndex + 1, asyncFn, callback);
    });
  }
  
  const queryList = function (list, selector, callback) {
    let isSync = true;
    // if there are no filters we need to query the full length
    if (!selector.filters) selector.filters = defaultFilters;
  
    function processConditions(conditions, serialTaskCallback) {
      let ref = [];
  
      function processCondition(condition, parallelTaskCallback) {
        let left = condition.left;
        let right = condition.right;
        let op = condition.op;
  
        if ('left' in condition && !('right' in condition) && isNumber(left)) {
          if (!op) right = left + 1;else if (op === ':') right = list.length;
          op = ':';
        }
  
        if ('right' in condition && !('left' in condition) && isNumber(right) && op === ':') {
          left = 0;
        }
  
        // filter by range.
        if (isNumber(left) && isNumber(right) && op === ':') {
          // if the list is an array it means it has already been filtered.
          if (Array.isArray(list)) {
            const filtered = slice_.call(list, left, right);
            ref = ref.concat(filtered);
            parallelTaskCallback();
          } else {
            list._get([new Range(left, right)], function (error, wasSync) {
              if (error) {
                parallelTaskCallback(error);
              } else {
                for (let i = left; i < right; i++) ref.push(list.values[i]);
                if (!wasSync) isSync = false;
                parallelTaskCallback();
              }
            });
          }
        } else {
          const done = function (array) {
            filterArray(array, left, op, right, function (error, filtered, wasSync) {
              if (error) {
                parallelTaskCallback(error);
              } else {
                if (!wasSync) isSync = false;
                ref = ref.concat(filtered);
                parallelTaskCallback();
              }
            });
          };
  
          if (Array.isArray(list)) {
            done(list);
          } else {
            // the list is not an array, never been filtered.
            // assume full length.
            // call a function that filters an array once you arraify it.
            list._get([new Range(0, list.length)], function (error, wasSync) {
              if (error) {
                parallelTaskCallback(error);
              } else {
                const array = [];
                for (let i = 0; i < list.length; i++) array.push(list.values[i]);
                if (!wasSync) isSync = false;
                done(array);
              }
            });
          }
        }
      }
  
      // list of conditions in a single filter.
      // can be parallel.
      parallel(conditions.map(function (condition) {
        return function (parallelTaskCallback) {
          processCondition(condition, parallelTaskCallback);
        };
      }), function (error) {
        if (error) {
          serialTaskCallback(error);
          return;
        }
  
        // eslint-disable-next-line no-param-reassign
        list = ref;
        serialTaskCallback();
      });
    }
  
    // single filter.
    // must be sequential.
    runSeriallyOnArray(selector.filters, 0, processConditions, function (error) {
      if (error) {
        callback(error);
      } else {
        const data = [];
  
        parallel(list.map(function (item, i) {
          return function (parallelTaskCallback) {
            queryAny(item, selector, function (err, res, wasSync) {
              if (!wasSync) isSync = false;
              if (data[i]) deepMixIn(data[i], res);else data[i] = res;
              parallelTaskCallback(err);
            });
          };
        }), function (err) {
          callback(err, data, isSync);
        });
      }
    });
  };
  
  let cache = {};
  
  /**
   * Create a new instance of a object, based on an unique identifier.
   * @function
   * @static
   * @param {String|LiveObject} item - An object with an uri property, or an uri as a string.
   * @return {LiveObject|LiveList} An instance of LiveObject or LiveList.
   */
  const live = function (item) {
    if (Array.isArray(item)) {
      return new LiveList().update([{
        type: 'insert',
        index: 0,
        length: item.length,
        values: item
      }]);
    } else if (isNumber(item)) {
      return new LiveList(item);
    } else if (isString(item)) {
      return cache[item] || (cache[item] = new LiveObject(item));
    } else if (isPlainObject(item)) {
      return live(item.uri).update(item);
    } else if (isLiveList(item) || isLiveObject(item)) {
      return item;
    }
  
    return new LiveObject();
  };
  
  // Allow localStorage.debug to also control live.debug.
  if (global.localStorage && global.localStorage.debug && (global.localStorage.debug.indexOf('live') > -1 || global.localStorage.debug.indexOf('*') > -1)) {
    live.debug = true;
  }
  
  const emitters = {
    keys: [],
    values: []
  };
  
  /**
   * Subscribe to the specific event of objects matching the pattern.
   * @function
   * @static
   * @param {RegExp} match - Regular expression for matching uris.
   * @param {String} name - The name of the event to listen for.
   * @param {Function} handle - The event handle.
   * @return {Function} The live function.
   */
  live.subscribe = function (match, name, handle) {
    let newMatch = match;
    if (match.matches) {
      newMatch = newMatch.matches;
    }
  
    const string = newMatch.toString();
  
    const keys = emitters.keys;
    const innerValues = emitters.values;
  
    let emitter;
    for (let i = 0, len = keys.length; i < len; i++) {
      const key = keys[i];
      if (key.toString() === string) {
        emitter = innerValues[i];
        if (emitter) break;
      }
    }
  
    if (!emitter) {
      keys.push(match);
      innerValues.push(emitter = new Emitter());
    }
  
    emitter.on(name, handle);
  
    return this;
  };
  
  /**
   * Unsubscribe to the specific event of objects matching the pattern.
   * @function
   * @static
   * @param {RegExp} match - Regular expression for matching uris.
   * @param {String} name - The name of the event to unsubscribe from.
   * @param {Function} handle - The event handle.
   * @return {Function} The live function.
   */
  live.unsubscribe = function (match, name, handle) {
    let newMatch = match;
    if (newMatch.matches) {
      newMatch = newMatch.matches;
    }
  
    const string = match.toString();
  
    const keys = emitters.keys;
    const innerValues = emitters.values;
  
    let emitter;
    for (let i = 0, len = keys.length; i < len; i++) {
      const key = keys[i];
      if (key.toString() === string) {
        emitter = innerValues[i];
        if (emitter) break;
      }
    }
  
    if (emitter) emitter.off(name, handle);
  
    return this;
  };
  
  /**
   * Delete a model from the live cache.
   *
   * @param {string} uri A uri.
   *
   * @return {Function} The live function.
   */
  live.delete = function (uri) {
    delete cache[uri];
    return this;
  };
  
  /**
   * Purge the entire cache.
   *
   * @return {Function} The live function.
   */
  live.purge = function () {
    cache = {};
    return this;
  };
  
  /**
   * Check if live has an object for the URI in the cache.
   *
   * @param {string} uri A uri.
   *
   * @return {Boolean} True if the object is in the cache.
   */
  live.has = function (uri) {
    return !!cache[uri];
  };
  
  const EMIT_SYNC = live.EMIT_SYNC = Emitter.EMIT_SYNC;
  
  const ASYNC = live.ASYNC = 'ASYNC';
  const SYNC = live.SYNC = 'SYNC';
  const ASAP = live.ASAP = 'ASAP';
  
  const PUBLISH = 3;
  const UPDATE = 4;
  
  /**
   * @static
   * @see LiveObject
   */
  live.Object = LiveObject;
  
  /**
   * @static
   * @see LiveList
   */
  live.List = LiveList;
  
  module.exports = live;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../libs/prime":202,"../../libs/prime/defer":200,"../../libs/prime/emitter":201,"../debug":58,"../range2":204,"./util/ordered-set":174,"./util/parser":175,"./util/throttle":176,"mout/array/combine":433,"mout/array/difference":435,"mout/array/filter":438,"mout/array/map":442,"mout/lang/isPlainObject":459,"mout/lang/isRegExp":460,"mout/object/deepMixIn":464,"mout/object/pick":472,"mout/string/escapeRegExp":474}],173:[function(require,module,exports){
  /**
   * @module live/diff
   */
  'use strict';
  
  // Original code from: https://github.com/codeparty/arraydiff
  // License: MIT
  // This has been forked to allow for optimizations and patching operations.
  
  // Based on some rough benchmarking, this algorithm is about O(n^2) worst case,
  // and it can compute diffs on random arrays of length 1024 in about 34ms,
  // though just a few changes on an array of length 1024 takes about 0.5ms
  
  const splice_ = Array.prototype.splice;
  const slice_ = Array.prototype.slice;
  
  const annotate = function annotate(before, after) {
    // Find all items in both the before and after array, and represent them
    // as moves. Many of these "moves" may end up being discarded in the last
    // pass if they are from an index to the same index, but we don't know this
    // up front, since we haven't yet offset the indices.
    //
    // Also keep a map of all the indicies accounted for in the before and after
    // arrays. These maps are used next to create insert and remove diffs.
    const beforeLength = before.length;
    const afterLength = after.length;
    const moves = [];
    const beforeMarked = {};
    const afterMarked = {};
    for (let beforeIndex = 0; beforeIndex < beforeLength; beforeIndex++) {
      const beforeItem = before[beforeIndex];
      for (let afterIndex = 0; afterIndex < afterLength; afterIndex++) {
        if (afterMarked[afterIndex]) continue;
        if (beforeItem !== after[afterIndex]) continue;
        const from = beforeIndex;
        const to = afterIndex;
        let length = 0;
        do {
          beforeMarked[beforeIndex++] = afterMarked[afterIndex++] = true;
          length++;
        } while (beforeIndex < beforeLength && afterIndex < afterLength && before[beforeIndex] === after[afterIndex] && !afterMarked[afterIndex]);
        const moveDiff = {
          type: 'move',
          from: from,
          to: to,
          length: length
        };
        moves.push(moveDiff);
        beforeIndex--;
        break;
      }
    }
  
    // Create a remove for all of the items in the before array that were
    // not marked as being matched in the after array as well
    const removes = [];
    for (let beforeIndex = 0; beforeIndex < beforeLength;) {
      if (beforeMarked[beforeIndex]) {
        beforeIndex++;
        continue;
      }
      const index = beforeIndex;
      let length = 0;
      while (beforeIndex < beforeLength && !beforeMarked[beforeIndex++]) {
        length++;
      }
      const removeDiff = {
        type: 'remove',
        index: index,
        length: length,
        values: slice_.call(before, index, index + length)
      };
      removes.push(removeDiff);
    }
  
    // Create an insert for all of the items in the after array that were
    // not marked as being matched in the before array as well
    const inserts = [];
    for (let afterIndex = 0; afterIndex < afterLength;) {
      if (afterMarked[afterIndex]) {
        afterIndex++;
        continue;
      }
      const index = afterIndex;
      let length = 0;
      while (afterIndex < afterLength && !afterMarked[afterIndex++]) {
        length++;
      }
      const values = slice_.call(after, index, index + length);
      inserts.push({
        type: 'insert',
        index: index,
        length: length,
        values: values
      });
    }
  
    return [removes, moves, inserts];
  };
  
  const reduceMoves = function reduceMoves(moves) {
    for (let i = 0; i < moves.length; i++) {
      // if we detect a move operation of a lots of items to a near position, we
      // can swap it by a move operation of less items to a farther position
      // (which is going to be much more performant).
      //
      // Example:
      // [1, 2, 3, 4, 5, 6, 7] => [2, 3, 4, 5, 6, 7, 1]
      // We could move [2, 3, 4, 5, 6, 7] one position ahead, but it's better to
      // move [1] six positions behind.
      if (moves[i].length > Math.abs(moves[i].to - moves[i].from)) {
        const to = moves[i].to;
        const length = moves[i].length;
  
        moves[i].length = Math.abs(moves[i].to - moves[i].from);
        moves[i].from = to;
        moves[i].to = to + length;
      }
    }
  };
  
  const offset = function offset(removes, moves, inserts) {
    const insertsLength = inserts.length;
    const removesLength = removes.length;
    const movesLength = moves.length;
  
    // Offset subsequent removes and moves by removes
    let count = 0;
    for (let i = 0; i < removesLength; i++) {
      const remove = removes[i];
      remove.index -= count;
      count += remove.length;
      for (let j = 0; j < movesLength; j++) {
        const move = moves[j];
        if (move.from >= remove.index) move.from -= remove.length;
      }
    }
  
    // Offset moves by inserts
    for (let i = insertsLength; i--;) {
      const insert = inserts[i];
      const length = insert.length;
      for (let j = movesLength; j--;) {
        const move = moves[j];
        if (move.to >= insert.index) move.to -= length;
      }
    }
  
    // Offset the to of moves by later moves
    for (let i = movesLength; i-- > 1;) {
      const move = moves[i];
      if (move.to === move.from) continue;
      for (let j = i; j--;) {
        const earlier = moves[j];
        if (earlier.to >= move.to) earlier.to -= move.length;
        if (earlier.to >= move.from) earlier.to += move.length;
      }
    }
  
    // Only output moves that end up having an effect after offsetting
    const outputMoves = [];
  
    // Offset the from of moves by earlier moves
    for (let i = 0; i < movesLength; i++) {
      const move = moves[i];
      if (move.to === move.from) continue;
      outputMoves.push(move);
      for (let j = i + 1; j < movesLength; j++) {
        const later = moves[j];
        if (later.from >= move.from) later.from -= move.length;
        if (later.from >= move.to) later.from += move.length;
      }
    }
  
    // try to reduce the number of move events
    reduceMoves(outputMoves);
  
    return removes.concat(outputMoves, inserts);
  };
  
  const insert = function insert(array, index, values) {
    for (let i = 0; i < values.length; i++) {
      if (i in values) {
        const idx = index + i;
        if (array.length < idx) array.length = idx;
        array.splice(idx, 0, values[i]);
      }
    }
    return values;
  };
  
  const remove = function remove(array, index, length) {
    return splice_.call(array, index, length);
  };
  
  const move = function move(array, from, to, length) {
    const moved = remove(array, from, length);
    insert(array, to, moved);
    return moved;
  };
  
  const patch = function patch(array, operations) {
    for (let i = 0; i < operations.length; i++) {
      const operation = operations[i];
      switch (operation.type) {
        case 'move':
          move(array, operation.from, operation.to, operation.length);
          break;
        case 'remove':
          remove(array, operation.index, operation.length);
          break;
        case 'insert':
          insert(array, operation.index, operation.values);
          break;
        default:
          console.error('Unexpected operation type', operation.type);
          break;
      }
    }
    return array;
  };
  
  const diff = function diff(before, after) {
    const operations = annotate(before, after);
    return offset.apply(this, operations);
  };
  
  diff.annotate = annotate;
  diff.offset = offset;
  
  diff.remove = remove;
  diff.insert = insert;
  diff.move = move;
  diff.patch = patch;
  
  module.exports = diff;
  
  },{}],174:[function(require,module,exports){
  /**
   * @module live/util/ordered-set
   * @private
   */
  'use strict';
  
  // prime
  
  const prime = require('../../../libs/prime');
  
  // mout
  const forEach = require('mout/array/forEach');
  
  const Range = require('../../range2');
  const diff = require('./diff');
  
  const OrderedSet = prime({
    constructor: function (length = 0) {
      this.index = [];
      this.keys = new Array(length);
      this.values = new Array(length);
      this.objectStorage = {};
    },
  
    get length() {
      return this.keys.length;
    },
  
    set length(value) {
      this.keys.length = value;
      this.values.length = value;
    },
  
    forEach: function (fn, ctx) {
      const index = this.index;
      for (let k = 0; k < index.length; k++) {
        const range = index[k];
        for (let i = range.start; i < range.end; i++) {
          if (fn.call(ctx, this.values[i], i, this.keys[i], this) === false) {
            break;
          }
        }
      }
      return this;
    },
  
    map: function (fn, ctx) {
      const values = new Array(this.length);
      this.forEach(function (value, index, key) {
        values[index] = fn.call(ctx, value, index, key, this);
      }, this);
      return values;
    },
  
    copy: function (set) {
      this.index = set.index.slice();
      this.keys = set.keys.slice();
      this.values = set.values.slice();
      const length = this.keys.length;
      this.objectStorage = {};
      for (let i = 0; i < length; i++) {
        this.objectStorage[this.keys[i]] = this.values[i];
      }
      return this;
    },
  
    clone: function () {
      return new OrderedSet().copy(this);
    },
  
    indexOf: function (key) {
      let index = -1;
      this.forEach(function (v, i, k) {
        if (key === k) {
          index = i;
        }
      });
      return index;
    },
  
    indexOfMany: function (keys) {
      const indexedKeys = {};
      const output = [];
  
      // create a hash with the keys that are going to be searched, so we can
      // access them really fast later
      for (let i = 0; i < keys.length; i++) {
        indexedKeys[keys[i]] = -1;
      }
  
      this.forEach(function (v, i, k) {
        // If the element one that is being searched, add its position
        // to the output array
        if (k in indexedKeys) {
          indexedKeys[k] = i;
        }
      });
  
      for (let i = 0; i < keys.length; i++) {
        output[i] = indexedKeys[keys[i]];
      }
  
      return output;
    },
  
    valueOf: function (key) {
      return this.objectStorage[key] || null;
    },
  
    hasKey: function (key) {
      return this.objectStorage.hasOwnProperty(key);
    },
  
    sort: function (fn) {
      const keys = this.keys;
      const values = this.values;
      const operations = diff(values.slice(), values.sort(fn));
  
      forEach(operations, function (op) {
        // unless something is broken, a sort only produces moves.
        diff.move(keys, op.from, op.to, op.length);
      });
  
      return this;
    },
  
    move: function (from, to, length) {
      let newLength = length;
      let newTo = to;
  
      if (from > this.length) {
        return [];
      }
      if (from + newLength > this.length) {
        newLength = from - this.length;
      }
      if (newTo > this.length) {
        newTo = this.length;
      }
      if (from === newTo) {
        return [];
      }
  
      diff.move(this.keys, from, newTo, newLength);
      diff.move(this.values, from, newTo, newLength);
  
      return this;
    },
  
    insert: function (index, keys, values) {
      if (keys.length !== values.length) throw new Error('length mismatch');
  
      const range = new Range(index, index + keys.length);
      this.index = range.insert(this.index);
  
      diff.insert(this.keys, index, keys);
      diff.insert(this.values, index, values);
  
      for (let i = 0, length = keys.length; i < length; i++) {
        this.objectStorage[keys[i]] = values[i];
      }
  
      return this;
    },
  
    remove: function (index, length) {
      let newLength = length;
  
      if (index >= this.length) {
        return [];
      }
      if (index + newLength > this.length) {
        newLength = this.length;
      }
  
      const range = new Range(index, index + newLength);
      this.index = range.extract(this.index);
  
      for (let i = 0; i < newLength; i++) {
        delete this.objectStorage[this.keys[i + index]];
      }
      diff.remove(this.keys, index, newLength);
      diff.remove(this.values, index, newLength);
  
      return this;
    },
  
    diff: function (target) {
      const operations = diff.annotate(this.keys, target.keys);
  
      forEach(operations[0], function (op) {
        // remove
        op.keys = op.values;
        op.values = this.values.slice(op.index, op.index + op.length);
      }, this);
  
      forEach(operations[1], function (op) {
        // move
        op.keys = op.values;
        op.values = this.values.slice(op.from, op.from + op.length);
      }, this);
  
      forEach(operations[2], function (op) {
        // insert
        op.keys = op.values;
        op.values = target.values.slice(op.index, op.index + op.length);
      });
  
      return diff.offset.apply(diff, operations);
    },
  
    patch: function (operations) {
      forEach(operations, function (op) {
        switch (op.type) {
          case 'move':
            this.move(op.from, op.to, op.length);
            break;
          case 'remove':
            this.remove(op.index, op.length);
            break;
          case 'insert':
            this.insert(op.index, op.keys, op.values);
            break;
          default:
            console.error('Invalid patch operation type', op.type);
            break;
        }
      }, this);
      return this;
    }
  });
  
  module.exports = OrderedSet;
  
  },{"../../../libs/prime":202,"../../range2":204,"./diff":173,"mout/array/forEach":439}],175:[function(require,module,exports){
  /**
   * @module live/util/parser
   */
  'use strict';
  
  const normalize = function (value) {
    if (value !== '' && !isNaN(value)) return +value;else if (value === 'true') return true;else if (value === 'false') return false;else if (value === 'null') return null;else if (value === 'undefined') return undefined;
    return value;
  };
  
  function escapeForRegExp(str) {
    return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
  }
  
  const COMMA = ',';
  const BMASK = '(';
  const EMASK = ')';
  const BFILTER = '[';
  const EFILTER = ']';
  const EQUALS = '=';
  const WHITESPACE = '\\s';
  
  const operators = ['=', '!=', '>=', '<=', '>', '<', '^=', '$=', '~=', '|=', '*='];
  
  const keyBlacklist = COMMA + BMASK + EMASK + escapeForRegExp(BFILTER) + escapeForRegExp(EFILTER) + EQUALS + WHITESPACE;
  const keyBlacklistCharacterClass = `[^${keyBlacklist}]`;
  
  const KEY_CHARACTER_REG_EXP = new RegExp(keyBlacklistCharacterClass);
  const EXACT_KEY_REG_EXP = new RegExp(`^${keyBlacklistCharacterClass}+$`);
  
  const OPERATORS_REG_EXP = new RegExp(operators.map(escapeForRegExp).join('|'));
  const REPLACE_REG_EXP = /\$([\d]+)/;
  const RANGE_FILTER_REG_EXP = /^(\d*):(\d*)$/;
  
  const parse = function (string, params = []) {
    let selector = { mask: [] };
    const history = [selector];
  
    let key = '';
    let filter;
  
    let filterStr = '';
    let left = '';
    let right = '';
    let op = '';
  
    for (let i = 0; i < string.length + 1; i++) {
      const c = string.charAt(i);
  
      if (filter) {
        // parse the filter
        filterStr = filterStr.trim();
        if (c === COMMA || c === EFILTER) {
          const match1 = filterStr.match(RANGE_FILTER_REG_EXP);
          const match2 = filterStr.match(OPERATORS_REG_EXP);
          const match3 = filterStr.match(EXACT_KEY_REG_EXP);
          if (match1) {
            left = match1[1];
            op = ':';
            right = match1[2];
          } else if (match2) {
            left = filterStr.substring(0, match2.index);
            op = match2[0];
            right = filterStr.substring(match2.index + op.length);
          } else if (match3) {
            left = match3[0];
          } else {
            throw new SyntaxError(`syntax error: \`${filterStr}\` contains characters not valid in a filter`);
          }
  
          filterStr = '';
  
          const condition = {};
  
          if (left) {
            const leftMatch = left.match(REPLACE_REG_EXP);
            left = leftMatch ? params[+leftMatch[1]] : normalize(left);
            condition.left = left;
          }
  
          if (op) condition.op = op;
  
          if (right) {
            const rightMatch = right.match(REPLACE_REG_EXP);
            right = rightMatch ? params[+rightMatch[1]] : normalize(right);
            condition.right = right;
          }
  
          if ('left' in condition || 'right' in condition) filter.push(condition);
  
          right = '';
          left = '';
          op = '';
  
          if (c === EFILTER) {
            if (filter.length) {
              const filters = selector.filters || (selector.filters = []);
              filters.push(filter);
            }
  
            filter = null;
          }
          continue;
        }
  
        filterStr += c;
      } else {
        // parse the key
  
        if (c && KEY_CHARACTER_REG_EXP.test(c)) {
          // key
          key += c;
        } else if (key && (!c || c === COMMA || c === BMASK || c === BFILTER || c === EMASK)) {
          // end key
          // end word is "a!" or "b(a!)" or "a!,c" or "a!(" or "a!["
          const keyMatch = key.match(REPLACE_REG_EXP);
          // begin a new selector
          history[0].mask.push(selector = {
            key: keyMatch ? params[+keyMatch[1]] : normalize(key)
          });
          key = '';
        } else if (c && key) {
          throw new SyntaxError(`syntax error: \`${c}\` is not a valid character in a key`);
        }
  
        if (c === BMASK) {
          selector.mask = [];
          history.unshift(selector);
        } else if (c === EMASK) {
          history.shift();
        }
  
        if (c === BFILTER) {
          filter = [];
        }
      }
    }
  
    if (history.length !== 1) throw new SyntaxError('syntax error');
  
    return history[0];
  };
  
  /** exports */
  module.exports = parse;
  
  },{}],176:[function(require,module,exports){
  /**
   * @module live/util/throttle
   * @private
   */
  'use strict';
  
  const defer = require('../../../libs/prime/defer');
  const isInteger = function (n) {
    return typeof n === 'number' && n % 1 === 0;
  };
  
  const slice = Array.prototype.slice;
  
  const _throttle = function (fn, method, context) {
    let queued;
    let args;
    let cancel;
  
    return function () {
      args = arguments;
      if (!queued) {
        queued = true;
        cancel = method(function (time) {
          queued = false;
          fn.apply(context, slice.call(args).concat(time));
        });
      }
      return cancel;
    };
  };
  
  const throttle = function (callback, argument, context) {
    if (isInteger(argument)) return throttle.timeout(callback, argument, context);
    return throttle.immediate(callback, argument);
  };
  
  throttle.timeout = function (callback, ms, context) {
    return _throttle(callback, function (run) {
      return defer.timeout(run, ms, context);
    }, context);
  };
  
  throttle.immediate = function (callback, context) {
    return _throttle(callback, function (run) {
      return defer.immediate(run, context);
    }, context);
  };
  
  module.exports = throttle;
  
  },{"../../../libs/prime/defer":200}],177:[function(require,module,exports){
  'use strict';
  
  const languages = require('./languages.json');
  
  function lookup(language, to) {
    const lang = languages[language];
    if (lang && lang[to]) {
      return lang[to];
    }
    return language;
  }
  
  module.exports = lookup;
  module.exports.all = function () {
    return Object.keys(languages);
  };
  
  },{"./languages.json":178}],178:[function(require,module,exports){
  module.exports={
    "cs": {
      "smartling": "cs-CZ"
    },
    "de": {
      "smartling": "de-DE"
    },
    "el": {
      "smartling": "el-GR"
    },
    "en": {
      "smartling": "en-GB"
    },
    "es": {
      "smartling": "es-ES"
    },
    "es-419": {
      "moment": "es",
      "smartling": "es-LA"
    },
    "fi": {
      "smartling": "fi-FI"
    },
    "fr": {
      "smartling": "fr-FR"
    },
    "fr-CA": {
      "smartling": "fr-CA",
      "moment": "fr-ca"
    },
    "hu": {
      "smartling": "hu-HU"
    },
    "id": {
      "smartling": "id-ID"
    },
    "it": {
      "smartling": "it-IT"
    },
    "ja": {
      "smartling": "ja-JP"
    },
    "nl": {
      "smartling": "nl-NL"
    },
    "pl": {
      "smartling": "pl-PL"
    },
    "pt-BR": {
      "moment": "pt-br",
      "smartling": "pt-BR",
      "intl": "pt-PT"
    },
    "sv": {
      "smartling": "sv-SE"
    },
    "th": {
      "smartling": "th-TH"
    },
    "tr": {
      "smartling": "tr-TR"
    },
    "vi": {
      "smartling": "vi-VN"
    },
    "zh-Hant": {
      "moment": "zh-tw",
      "smartling": "zh-TW"
    },
    "zsm": {
      "moment": "ms-my",
      "smartling": "ms-MY",
      "intl": "ms-MY"
    }
  }
  
  },{}],187:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.messageProxy = messageProxy;
  
  var _debug = require('../debug');
  
  var _debug2 = _interopRequireDefault(_debug);
  
  var _uris = require('./uris.json');
  
  var _uris2 = _interopRequireDefault(_uris);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  /**
   * This module is to be included in zlink
   */
  
  const METHODS = ['POST', 'SUB', 'GET', 'REPLY'];
  const BASE_URI = 'message-proxy';
  const debug = (0, _debug2.default)('message-proxy');
  
  function messageProxy(requestObj, callback) {
    if (typeof window === 'undefined' || !window.top || !window.top.postMessage) {
      return false;
    }
  
    const uri = requestObj.uri;
  
    // Validate Request
    if (!requestObj) throw new Error('Need a request object.');
    if (METHODS.indexOf(requestObj.method) === -1) {
      throw new Error('Need a valid method:', METHODS);
    }
    if (!callback && !requestObj.method === 'POST') {
      throw new Error('Callback needed');
    }
    if (!_uris2.default.hasOwnProperty(uri)) {
      throw new Error('Uri need to be one of these: ', Object.keys(_uris2.default));
    }
  
    const data = {};
    const requiredKeys = _uris2.default[uri][requestObj.method] || [];
  
    // GET
    if (requestObj.method === 'GET') {
      requiredKeys.forEach(requiredKey => {
        if (!requestObj.body.hasOwnProperty(requiredKey)) {
          throw new Error(`Uri 'GET' request body missing
                          a required key: ${requiredKey}`);
        }
        data[requiredKey] = requestObj.body[requiredKey];
      });
  
      const getListener = event => {
        if (!isEventValid(event, `${BASE_URI}-${uri}-reply`)) {
          return;
        }
        debug('getListener', uri, event.data);
        if (event.data.error) {
          callback(event.data.error);
        } else {
          delete event.data.uri;
          callback(null, event.data);
        }
        window.removeEventListener('message', getListener);
      };
      window.addEventListener('message', getListener);
  
      data.uri = `${BASE_URI}-${uri}-get`;
      debug('get', uri, data);
      const messagePosted = postMessageToFrame(requestObj.target, data);
      if (!messagePosted) {
        window.removeEventListener('message', getListener);
        callback('No frame found to post to.');
      }
      return true;
    }
  
    // REPLY
    if (requestObj.method === 'REPLY') {
      const replyListener = event => {
        if (!isEventValid(event, `${BASE_URI}-${uri}-get`)) {
          return;
        }
        debug('reply listener', uri, event.data);
        delete event.data.uri;
        callback(event.data, function reply(replyErr, rawData) {
          const replyData = {};
          if (replyErr) {
            replyData.error = replyErr;
          } else {
            requiredKeys.forEach(requiredKey => {
              if (!rawData.hasOwnProperty(requiredKey)) {
                throw new Error(`Reply data missing
                                a required key: ${requiredKey}`);
              }
              replyData[requiredKey] = rawData[requiredKey];
            });
          }
          replyData.uri = `${BASE_URI}-${uri}-reply`;
          debug('reply post', uri, replyData);
          event.source.postMessage(replyData, event.origin);
        });
      };
      window.addEventListener('message', replyListener, false);
  
      return () => {
        window.removeEventListener('message', replyListener);
      };
    }
  
    // POST
    if (requestObj.method === 'POST') {
      requiredKeys.forEach(requiredKey => {
        if (!requestObj.body.hasOwnProperty(requiredKey)) {
          throw new Error(`Uri 'POST' request body missing
                          a required key: ${requiredKey}`);
        }
        data[requiredKey] = requestObj.body[requiredKey];
      });
  
      data.uri = `${BASE_URI}-${uri}`;
      debug('post', uri, data);
      const messagePosted = postMessageToFrame(requestObj.target, data);
      if (callback) {
        if (!messagePosted) {
          callback('No frame found to post to.');
        } else {
          callback(null);
        }
      }
      return true;
    }
    // Subscription
    const sub = event => {
      if (!isEventValid(event, `${BASE_URI}-${uri}`)) {
        return;
      }
      debug('subscription event', uri, event.data);
      callback(event.data);
    };
  
    debug('subscription', uri);
    window.addEventListener('message', sub, false);
    return () => {
      window.removeEventListener('message', sub);
    };
  }
  
  const originRegex = new RegExp('^https?://[a-zA-Z-]*.app.spotify.com');
  
  function isEventValid(event, uri) {
    if (!originRegex.test(event.origin)) {
      return false;
    }
    if (event.data.uri !== uri) {
      return false;
    }
    return true;
  }
  
  function postMessageToFrame(target, data) {
    if (target === 'top' || !target) {
      debug('postMessageToFrame target=top data=%o', data);
      window.top.postMessage(data, '*');
      // no need to continue
      if (target === 'top') {
        return true;
      }
    }
    let frames;
    let frameFound;
    if (window === window.top) {
      frames = window.top.frames;
    } else {
      const topFrames = Array.prototype.slice.call(window.top.frames, 0, window.top.frames.length);
      const thisFrames = Array.prototype.slice.call(window.frames, 0, window.frames.length);
      frames = topFrames.concat(thisFrames);
    }
  
    const len = frames.length;
    for (let i = 0; i < len; i++) {
      if (target && target !== frames[i].frameElement.src) {
        continue;
      }
      debug('postMessageToFrame target=%o data=%o', frames[i].frameElement.target, data);
      frames[i].postMessage(data, '*');
      frameFound = true;
    }
  
    debug('postMessageToFrame framesLength=%o frameFound=%o', len, frameFound);
    return frameFound;
  }
  
  },{"../debug":58,"./uris.json":188}],188:[function(require,module,exports){
  module.exports={
    "unit-test": {
      "GET": ["key1"],
      "POST": ["key2"],
      "REPLY": ["key3"]
    },
    "client-state": {
      "SUB": [],
      "POST": ["state"],
      "GET": [],
      "REPLY": ["state"]
    },
    "history": {
      "GET": ["count"],
      "REPLY": ["history"]
    },
    "hpto": {
      "REPLY": ["ad"],
      "GET": []
    },
    "hpto-refresh": {
      "SUB": [],
      "POST": []
    },
    "station": {
      "GET": ["stationUri", "metadata", "trackCount"],
      "REPLY": ["stationUri", "station"]
    },
    "station-add": {
      "POST": ["stationUri", "add"]
    },
    "station-autoplay": {
      "POST": ["contextUri", "playerPlaybackId", "prevTracks"],
      "SUB": []
    },
    "station-create": {
      "POST": ["stationUri"],
      "SUB": []
    },
    "station-error": {
      "POST": ["stationUri", "message"],
      "SUB": []
    },
    "station-metadata": {
      "POST": ["stationUri", "metadata"]
    },
    "station-last-played": {
      "POST": ["stationUri"]
    },
    "station-thumb": {
      "POST": ["stationUri", "trackUri", "thumb"]
    },
    "station-heart": {
      "POST": ["stationUri", "trackUri"]
    },
    "station-undo-feedback": {
      "POST": ["stationUri", "trackUri", "thumbAction"]
    },
    "stations-all": {
      "GET": [],
      "REPLY": ["user_stations", "genre_stations", "recommended_stations"]
    },
    "stations-saved": {
      "GET": [],
      "REPLY": ["saved_stations"]
    },
    "format-list-heart": {
      "POST": ["entityUri", "trackUri"]
    },
    "format-list-undo-feedback": {
      "POST": ["entityUri", "trackUri"]
    }
  }
  
  },{}],189:[function(require,module,exports){
  'use strict';
  
  var _pageIdentifiers = require('./page-identifiers.js');
  
  var _pageIdentifiers2 = require('./page-identifiers.json');
  
  var _pageIdentifiers3 = _interopRequireDefault(_pageIdentifiers2);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  const UUID = require('uuid-js');
  
  /**
   * Object representing state of an app.
   *
   * @param {String} uri Spotify URI of app.
   * @constructor
   */
  function AppState(uri) {
    /**
     * Spotify URI.
     *
     * @type {String}
     */
    this._uri = undefined;
  
    if (uri !== undefined) {
      this.setURI(uri);
    }
  
    /**
     *  Unique ID of this Application.
     *
     *  @type {String}
     */
    this._uuid = UUID.create().toString();
  }
  
  /**
   * Create new instance of AppState based on JSON string.
   *
   * @param {String} json Serialized representation of AppState object.
   * @return {AppState} Instance of AppState.
   */
  AppState.unserialize = function (json) {
    const s = JSON.parse(json);
    const a = new AppState();
    a.setURI(s.uri);
    return a;
  };
  
  /**
   * Test if another object is considered equal to this.
   *
   * @param {AppState} state AppState instance to compare.
   */
  AppState.prototype.equals = function (state) {
    return typeof this._uri === 'string' && state instanceof AppState && this._uri === state._uri;
  };
  
  /**
   * Test if another app state is for the same app.
   *
   * @param {AppState} state AppState instance to compare.
   */
  AppState.prototype.equalsApp = function (state) {
    const id = this.getAppId();
    return state && id && state.getAppId() === id || false;
  };
  
  /**
   * Get App identifier.
   *
   * @return {String} Id of app contained in URI.
   */
  AppState.prototype.getAppId = function () {
    const uri = this.getURI();
    const parts = uri.split(':');
    if (parts.length < 3) {
      return undefined;
    }
    if (parts[1] !== 'app') {
      return undefined;
    }
    if (parts[2] === '') {
      return undefined;
    }
    return parts[2];
  };
  
  /**
   * Get App URI without arguments.
   *
   * @return {String} URI of app without arguments.
   */
  AppState.prototype.getAppURI = function () {
    return this.getURI().split(':').slice(0, 3).join(':');
  };
  
  /**
   * Get App arguments
   *
   * @return {Array} List of app arguments.
   */
  AppState.prototype.getArgs = function () {
    return this.getURI().split(':').slice(3).map(function (a) {
      return decodeURIComponent(a);
    });
  };
  
  /**
   * Get URI value.
   *
   * @return {String} URI as string.
   */
  AppState.prototype.getURI = function () {
    if (this._uri === undefined) {
      throw new Error('URI not set');
    }
    return this._uri;
  };
  
  /**
   * Create serialized representation of this instance.
   *
   * @return {String} Serialized representation of AppState object.
   */
  AppState.prototype.serialize = function () {
    return JSON.stringify({
      uri: this.getURI()
    });
  };
  
  /**
   * Set arguments for state.
   *
   * @param {Array} args Arguments as array.
   */
  AppState.prototype.setArgs = function (args) {
    if (!Array.isArray(args)) {
      throw new TypeError('Args must be array');
    }
  
    this._uri = this.getAppURI().split(':').concat(args.map(function (a) {
      return encodeURIComponent(a);
    })).join(':');
  };
  
  /**
   * Set URI value.
   *
   * @param {String} uri Spotify URI for app.
   */
  AppState.prototype.setURI = function (uri) {
    if (typeof uri !== 'string') {
      throw new TypeError('URI must be string');
    }
    this._uri = uri;
  };
  
  /**
   * Get UUID value.
   *
   * @return {String} UUID as string.
   */
  AppState.prototype.getUUID = function () {
    return this._uuid;
  };
  
  /**
   * Get Page ID.
   *
   * @return {String} Page ID as string.
   */
  AppState.prototype.getPageID = function () {
    return (0, _pageIdentifiers.getPageIdFromUri)(this.getAppId(), this.getURI());
  };
  
  /**
   * Check if page is covered
   *
   * @return {Boolean} True if page ID is known.
   */
  AppState.prototype.isPageCovered = function () {
    return this.getPageID() !== _pageIdentifiers3.default.unknownUncovered;
  };
  
  module.exports = AppState;
  
  },{"./page-identifiers.js":193,"./page-identifiers.json":192,"uuid-js":573}],191:[function(require,module,exports){
  (function (global){
  'use strict';
  
  const inherit = require('spotify-inheritance/inherit');
  const EventEmitter = require('spotify-eventemitter');
  const AppState = require('./app-state');
  
  /**
   * Class containing methods for navigating.
   *
   * @constructor
   */
  function Navigator() {
    this._global = global.top || global;
  
    EventEmitter.call(this);
  
    /**
     * Locally bound handler function for message event on global.
     *
     * @type {Function}
     */
    this._messageHandler = this._messageHandler.bind(this);
  }
  
  inherit(Navigator, EventEmitter);
  
  /**
   * Maps constants to string values of supported events.
   */
  Navigator.prototype.EVENTS = Navigator.EVENTS = {
    REQUEST_STATE: 'navigation_request_state'
  };
  
  /**
   * Maps constants to string values of supported methods.
   */
  Navigator.prototype.METHOD = Navigator.METHOD = {
    OPEN: 'open',
    TOGGLE: 'toggle'
  };
  
  /**
   * Identify posted message and trigger relevant callbacks
   * based on the message contents.
   *
   * @param {Object} message Message object caused by postMessage.
   */
  Navigator.prototype._messageHandler = function (message) {
    if (!message.data.type) {
      return;
    }
    const type = message.data.type;
    if (type === Navigator.EVENTS.REQUEST_STATE) {
      const appState = AppState.unserialize(message.data.state);
      const syncEvent = {
        state: appState,
        method: message.data.method
      };
      if (message.data.extra) {
        syncEvent.extra = JSON.parse(message.data.extra);
      }
      this.emitSync(type, syncEvent);
    }
  };
  
  /**
   * Start listening to messages.
   */
  Navigator.prototype.attachListener = function () {
    this._global.addEventListener('message', this._messageHandler);
  };
  
  /**
   * Stop listening to messages.
   */
  Navigator.prototype.detachListener = function () {
    this._global.removeEventListener('message', this._messageHandler);
  };
  
  /**
   * Short hand method for requesting open AppState with URI only.
   *
   * @param {string} uri URI to open.
   */
  Navigator.prototype.openURI = function (uri, options = {}) {
    this.requestOpenState(new AppState(uri), options);
  };
  
  /**
   * Short hand method for requesting toggle of AppState with URI only.
   *
   * @param {string} uri URI to open.
   */
  Navigator.prototype.toggleURI = function (uri) {
    this.requestToggleState(new AppState(uri));
  };
  
  /**
   * Send an open request message.
   *
   * @param {AppState} state AppState instance to base open action on.
   */
  Navigator.prototype.requestOpenState = function (state, options = {}) {
    this.requestState(state, this.METHOD.OPEN, options);
  };
  
  /**
   * Send a toggle request message.
   *
   * @param {AppState} state AppState instance to base open action on.
   */
  Navigator.prototype.requestToggleState = function (state) {
    this.requestState(state, this.METHOD.TOGGLE);
  };
  
  /**
   * Send an request for an app state.
   *
   * @param {AppState} state AppState object representing state to request.
   * @param {string} method Method to use.
   */
  Navigator.prototype.requestState = function (state, method, options = null) {
    if (!state) {
      throw new TypeError('AppState not set');
    }
    if (!method) {
      throw new TypeError('Method not set');
    }
  
    const message = {
      type: this.EVENTS.REQUEST_STATE,
      method: method,
      state: state.serialize()
    };
  
    if (options) {
      message.extra = JSON.stringify(options);
    }
  
    this._global.postMessage(message, '*');
  };
  
  module.exports = Navigator;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"./app-state":189,"spotify-eventemitter":548,"spotify-inheritance/inherit":551}],192:[function(require,module,exports){
  module.exports={
    "album": "album",
    "artist": "artist",
    "artistAbout": "artist/about",
    "artistAlbums": "artist/albums",
    "artistBio": "artist/bio",
    "artistConcerts": "artist/concerts",
    "artistGallery": "artist/gallery",
    "artistPlaylists": "artist/playlists",
    "artistRelated": "artist/related",
    "browse": "browse",
    "browseCharts": "browse/charts",
    "browseDiscover": "browse/discover",
    "browseGenres": "browse/genres",
    "browseNewReleases": "browse/newreleases",
    "browsePodcasts": "browse/podcasts",
    "browseVideos": "browse/videos",
    "charts": "charts",
    "chartsChart": "charts/chart",
    "chartsRegional": "charts/regional",
    "chartsSocial": "charts/social",
    "chartsViral": "charts/viral",
    "collection": "collection",
    "collectionAlbums": "collection/albums",
    "collectionAlbumsAlbum": "collection/albums/album",
    "collectionArtists": "collection/artists",
    "collectionArtistsArtist": "collection/artists/artist",
    "collectionPlaylists": "collection/playlists",
    "collectionRadio": "collection/radio",
    "collectionRecentlyPlayed": "collection/recently-played",
    "collectionShows": "collection/shows",
    "collectionSongs": "collection/songs",
    "concert": "concert",
    "concerts": "concerts",
    "concertsCitysearch": "concerts/citysearch",
    "concertsConcert": "concerts/concert",
    "debug": "debug",
    "lyrics": "lyrics",
    "home": "home",
    "moments": "moments",
    "momentsCategory": "moments/category",
    "music": "music",
    "musicCategory": "music/category",
    "now": "now",
    "nowplaying": "nowplaying",
    "nowplayingDevicepicker": "connect/devicepicker",
    "nowplayingHistory": "nowplaying/history",
    "nowplayingQueue": "nowplaying/queue",
    "party": "party",
    "partyAddFriends": "party/add-friends",
    "partyEnableBluetooth": "party/enable-bluetooth",
    "partyEnableNearby": "party/enable-nearby",
    "partyPreset": "party/preset",
    "partyQueue": "party/queue",
    "playlist": "playlist",
    "playlistFolder": "playlist/folder",
    "profile": "profile",
    "profileActivity": "profile/activity",
    "profileArtists": "profile/artists",
    "profileFollowers": "profile/followers",
    "profileFollowing": "profile/following",
    "profilePlaylists": "profile/playlists",
    "radio": "radio",
    "radioDailyMixes": "radio/daily-mixes",
    "radioStation": "radio/station",
    "running": "running",
    "runningCategory": "running/category",
    "runningSetup": "running/setup",
    "search": "search",
    "searchAlbums": "search/albums",
    "searchArtists": "search/artists",
    "searchGenres": "search/genres",
    "searchPlaylists": "search/playlists",
    "searchProfiles": "search/profiles",
    "searchRadio": "search/radio",
    "searchShows": "search/shows",
    "searchSongs": "search/songs",
    "searchVideos": "search/videos",
    "settings": "settings",
    "show": "show",
    "shows": "shows",
    "showsAudio": "shows/audio",
    "showsCategory": "shows/category",
    "showsNetwork": "shows/network",
    "showsVideo": "shows/video",
    "unknown": "unknown",
    "unknownUncovered": "unknown/uncovered"
  }
  
  },{}],193:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.getPageIdFromUri = getPageIdFromUri;
  exports.getPageIdFromAppArguments = getPageIdFromAppArguments;
  
  var _pageIdentifiers = require('./page-identifiers.json');
  
  var _pageIdentifiers2 = _interopRequireDefault(_pageIdentifiers);
  
  var _spotifyLiburi = require('spotify-liburi');
  
  var _spotifyLiburi2 = _interopRequireDefault(_spotifyLiburi);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  const extractArg = argNumber => args => args.length > argNumber && args[argNumber] || null;
  const extractHubArg = uri => {
    if (extractArg(0)(uri) === 'charts') {
      // For charts uris: spotify:app:hub:*charts*:*viral*
      return `charts-${extractArg(1)(uri)}`;
    }
  
    // Everything else can be treated as genre
    return 'genre';
  };
  
  /**
   * Maps the name of the app with the name of the identifier that has to be used
   * for logging the page view.
   */
  const PAGE_LOGGER_MAP = {
    'album': 'album',
    'app-manager': 'debug',
    'artist-concerts': 'concerts',
    'artist-creator-about': 'artistAbout',
    'artist-overview': 'artist',
    'artist-related': 'artistRelated',
    'artist': 'artist',
    'boilerplate': 'debug',
    'browse-charts': 'charts',
    'browse-discover': 'browseDiscover',
    'browse-genres': 'browseGenres',
    'browse-home': 'browse',
    'browse-releases': 'browseNewReleases',
    'browse-podcasts': 'browsePodcasts',
    'browse-videos': 'browseVideos',
    'browse': 'browse',
    'chart': 'chartsChart',
    'charts': 'charts',
    'collection-album': 'collectionAlbumsAlbum',
    'collection-albums': 'collectionAlbums',
    'collection-artist': 'collectionArtistsArtist',
    'collection-artists': 'collectionArtists',
    'collection-podcasts': 'collectionShows',
    'collection-songs': 'collectionSongs',
    'collection-videos': 'collectionShows',
    'collection': 'collection',
    'concert': 'concert',
    'concerts': 'concerts',
    'daily-mix-hub': 'radioDailyMixes',
    'diag': 'debug',
    'discover': 'browseDiscover',
    'friends-mix': 'friendsMix',
    'genre': 'browseGenres',
    'glue-header-demo': 'debug',
    'glue-reference': 'debug',
    'glue-grid-demo': 'debug',
    'home': 'home',
    'hub-charts-regional': 'chartsRegional',
    'hub-charts-viral': 'chartsViral',
    'hub-genre': 'browseGenres',
    'lyrics': 'lyrics',
    'playlist': 'playlist',
    'playlist-folder': 'playlistFolder',
    'profile-followers': 'profileFollowers',
    'profile-following': 'profileFollowing',
    'profile-overview': 'profile',
    'profile-public-playlists': 'profilePlaylists',
    'profile-recently-played-artists': 'profileArtists',
    'profile': 'profile',
    'radio-hub': 'radio',
    'radio': 'radio',
    'recently-played': 'collectionRecentlyPlayed',
    'search-albums': 'searchAlbums',
    'search-artists': 'searchArtists',
    'search-genres': 'searchGenres',
    'search-playlists': 'searchPlaylists',
    'search-profiles': 'searchProfiles',
    'search-tracks': 'searchSongs',
    'search-shows': 'searchShows',
    'search': 'search',
    'settings': 'settings',
    'show': 'show',
    'station': 'radioStation',
    'stations': 'collectionRadio',
    'video-debug': 'debug',
    'queue-history': 'nowplayingHistory',
    'queue-queue': 'nowplayingQueue',
    'queue': 'nowplayingQueue'
  };
  
  const EXTRACT_SECTION = {
    // Browse section is the first app argument (i.e.: spotify:app:browse:*releases*)
    browse: extractArg(0),
    // Artist and profile have the argument after the id (i.e.: spotify:user:spotify:*followers*)
    artist: extractArg(1),
    profile: extractArg(1),
    // Queue (spotify:app:queue and spotify:app:queue:history)
    queue: extractArg(0),
    // Search section is the argument after the search query (spotify:app:search:<query>:*albums*)
    // or for search (spotify:app:search:<query>:*albums*)
    search: extractArg(1),
    // Hub (spotify:app:hub:*charts*:*viral* or spotify:app:hub:*special*:*thebeatles*)
    hub: extractHubArg,
    // Collection (spotify:app:collection for albums and artists)
    collection: extractArg(0)
  };
  
  function getPageIdFromUri(appId, inputUri) {
    if (appId in EXTRACT_SECTION) {
      const uri = _spotifyLiburi2.default.from(inputUri);
      const section = EXTRACT_SECTION[appId](uri.args);
      return getPageIdFromAppIdAndSection(appId, section);
    }
    return getPageIdFromAppIdAndSection(appId);
  }
  
  function getPageIdFromAppArguments(appId, appArguments) {
    if (appId in EXTRACT_SECTION) {
      const section = EXTRACT_SECTION[appId](appArguments);
      return getPageIdFromAppIdAndSection(appId, section);
    }
    return getPageIdFromAppIdAndSection(appId);
  }
  
  function getPageIdFromAppIdAndSection(inputAppId, section) {
    let appId = inputAppId;
    if (section) {
      // The appId of an app with sections should be APP-SECTION,
      // such as 'browse-genres'
      appId = `${appId}-${section}`;
    }
    const normalizedAppId = PAGE_LOGGER_MAP[appId];
    return _pageIdentifiers2.default[normalizedAppId] || _pageIdentifiers2.default.unknownUncovered;
  }
  
  },{"./page-identifiers.json":192,"spotify-liburi":553}],194:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.splitFeedbackUri = exports.makeFeedbackUri = undefined;
  exports.fetchMadeFors = fetchMadeFors;
  exports.fetchMadeFor = fetchMadeFor;
  exports.buildMadeFor = buildMadeFor;
  exports.originalPlaylistUri = originalPlaylistUri;
  exports.allowsFeedback = allowsFeedback;
  exports.getFeedback = getFeedback;
  
  var _spotifyLiburi = require('spotify-liburi');
  
  var _spotifyLiburi2 = _interopRequireDefault(_spotifyLiburi);
  
  var _bridge = require('../bridge');
  
  var _bridge2 = _interopRequireDefault(_bridge);
  
  var _liveWrappedUri = require('../live-wrapped-uri');
  
  var _liveWrappedUri2 = _interopRequireDefault(_liveWrappedUri);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  /**
   * Fetch madeFor objects for an array of formatListAttributes
   *
   * @param  {Array} formatLists        Array of `formatListAttributes`
   * @param  {String} viewerUsername
   * @return {Promise}                  The result of Promise.all resolves
   *                                    to a list of `madeFor` objects
   */
  function fetchMadeFors(formatLists, viewerUsername) {
    const promiseList = formatLists.map(formatList => fetchMadeFor(formatList, viewerUsername));
    return Promise.all(promiseList);
  }
  
  /**
   * Fetch madeFor object based on a playlist's formatListAttributes
   * @param  {Object} formatListAttributes
   * @param  {String} viewerUsername
   * @return {Promise}                      Promise that resolves
   *                                        to a madeFor object
   */
  function fetchMadeFor(formatListAttributes, viewerUsername) {
    if (!formatListAttributes) {
      return Promise.resolve(null);
    }
  
    const username = formatListAttributes['madeFor.username'];
    if (!username) {
      return Promise.resolve(null);
    }
  
    let name = formatListAttributes['madeFor.name'];
    if (name) {
      return Promise.resolve(buildMadeFor(name, username, viewerUsername));
    }
  
    // FormatList does not yet return a name when current user views their own.
    // It may someday. In the meantime, we fetch the name via a Bridge call.
    return new Promise(resolve => {
      const userUri = _spotifyLiburi2.default.profileURI(username).toURI();
      _bridge2.default.request('user_metadata', [userUri], (bridgeError, userMetadata) => {
        if (bridgeError) {
          name = username;
        } else {
          name = userMetadata.name || username;
        }
        resolve(buildMadeFor(name, username, viewerUsername));
      });
    });
  }
  
  /**
   * Returns an object with data for templates
   *
   * @param  {String} fullName          Playlist was made for a user
   *                                    with this name (fallsback to
   *                                    username if they have no name)
   * @param  {String} username          Playlist was made for this
   *                                    username
   * @param  {String} viewerUsername    The username who is viewing
   *                                    this playlist
   * @return {Object}
   */
  function buildMadeFor(name, username, viewerUsername) {
    const isCurrentUser = username === viewerUsername;
    const shortName = name.split(' ').shift();
  
    // We try to avoid rendering names and usernames that contain only numbers.
    // If that's the case and we can show "You", we will do it.
    const numbers = /^\d+$/;
    const renderAsYou = isCurrentUser && username === name && numbers.test(username);
  
    return {
      isCurrentUser,
      name,
      shortName,
      username,
      renderAsYou,
      uri: _spotifyLiburi2.default.profileURI(username).toURI()
    };
  }
  
  /**
   * Turns a filterlist URI into a regular playlist URI.
   */
  function originalPlaylistUri(playlistUri) {
    return _liveWrappedUri2.default.getOriginUri(playlistUri) || playlistUri;
  }
  
  function allowsFeedback(formatListAttributes) {
    return !!formatListAttributes && formatListAttributes['like-feedback-available'] === '1';
  }
  
  /**
   * Parse track-level formatListAttributes related to feedback
   * into one value: 'up', 'down' or null
   */
  function getFeedback(formatListAttributes) {
    if (!formatListAttributes) {
      return null;
    } else if (formatListAttributes['like-feedback-selected'] === '1') {
      return 'up';
    } else if (formatListAttributes['dislike-feedback-selected'] === '1') {
      return 'down';
    }
    return null;
  }
  
  const makeFeedbackUri = exports.makeFeedbackUri = (contextUri, trackUri) => `spotify:internal:${contextUri}:feedback:${trackUri}`;
  const splitFeedbackUri = exports.splitFeedbackUri = feedbackUri => feedbackUri.split('spotify:internal:').pop().split(':feedback:');
  
  },{"../bridge":53,"../live-wrapped-uri":171,"spotify-liburi":553}],195:[function(require,module,exports){
  (function (global){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.formatListUtils = exports.getAvailablePlaylistName = exports.playlistRequest = exports.fetchIsPlaylistOwnedByCurrentUser = exports.fetchPlaylistV1UriFromPlaylist = exports.subscribeToMetadata = exports.fetchPlaylistMetadata = exports.offlineSyncPlaylist = exports.followPlaylist = exports.removePlaylistFolderRecursively = exports.removePlaylist = exports.setPlaylistName = exports.setPlaylistPublished = exports.setPlaylistCollaborative = exports.createNewPlaylistAfter = exports.createNewPlaylistBefore = exports.createNewPlaylist = exports.getCollaborativeStatus = exports.addTracksToPlaylist = exports.OfflineStatus = undefined;
  exports.addAlbumToPlaylist = addAlbumToPlaylist;
  exports.addStationToPlaylist = addStationToPlaylist;
  exports.addStuffToPlaylist = addStuffToPlaylist;
  exports.getRefreshedPlaylistData = getRefreshedPlaylistData;
  exports.createRefreshedPlaylist = createRefreshedPlaylist;
  exports.shouldPlaylistBeOffline = shouldPlaylistBeOffline;
  exports.getPlaylist2ShowsCell = getPlaylist2ShowsCell;
  exports.isPlaylist2Shows = isPlaylist2Shows;
  exports.resyncPlaylist = resyncPlaylist;
  
  var _spotifyLiburi = require('spotify-liburi');
  
  var _spotifyLiburi2 = _interopRequireDefault(_spotifyLiburi);
  
  var _bridge = require('../../libs/bridge');
  
  var _bridge2 = _interopRequireDefault(_bridge);
  
  var _live = require('../../libs/live');
  
  var _live2 = _interopRequireDefault(_live);
  
  var _playlistActions = require('./playlist-actions');
  
  var _playlistData = require('./playlist-data');
  
  var _offlineStatus = require('./offline-status');
  
  var OfflineStatus = _interopRequireWildcard(_offlineStatus);
  
  var _formatLists = require('./format-lists');
  
  var formatListUtils = _interopRequireWildcard(_formatLists);
  
  function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  exports.OfflineStatus = OfflineStatus;
  exports.addTracksToPlaylist = _playlistActions.addTracksToPlaylist;
  exports.getCollaborativeStatus = _playlistData.getCollaborativeStatus;
  exports.createNewPlaylist = _playlistActions.createNewPlaylist;
  exports.createNewPlaylistBefore = _playlistActions.createNewPlaylistBefore;
  exports.createNewPlaylistAfter = _playlistActions.createNewPlaylistAfter;
  exports.setPlaylistCollaborative = _playlistActions.setPlaylistCollaborative;
  exports.setPlaylistPublished = _playlistActions.setPlaylistPublished;
  exports.setPlaylistName = _playlistActions.setPlaylistName;
  exports.removePlaylist = _playlistActions.removePlaylist;
  exports.removePlaylistFolderRecursively = _playlistActions.removePlaylistFolderRecursively;
  exports.followPlaylist = _playlistActions.followPlaylist;
  exports.offlineSyncPlaylist = _playlistActions.offlineSyncPlaylist;
  exports.fetchPlaylistMetadata = _playlistData.fetchPlaylistMetadata;
  exports.subscribeToMetadata = _playlistData.subscribeToMetadata;
  exports.fetchPlaylistV1UriFromPlaylist = _playlistData.fetchPlaylistV1UriFromPlaylist;
  exports.fetchIsPlaylistOwnedByCurrentUser = _playlistData.fetchIsPlaylistOwnedByCurrentUser;
  exports.playlistRequest = _playlistData.playlistRequest;
  exports.getAvailablePlaylistName = _playlistData.getAvailablePlaylistName;
  exports.formatListUtils = formatListUtils;
  function addAlbumToPlaylist(playlistUri, albumUri, cb) {
    const args = [albumUri.toString(), 0, -1];
  
    return _bridge2.default.request('album_tracks_snapshot', args, (err, resp) => {
      if (err) {
        cb(err);
        return;
      }
      (0, _playlistActions.addTracksToPlaylist)(playlistUri, resp.array, cb);
    });
  }
  
  function addStationToPlaylist(playlistUri, stationURI, cb) {
    (0, _live2.default)(stationURI.toString()).query('rows(track(uri))', (err, data) => {
      if (err) {
        cb(err);
        return;
      }
      const trackURIs = data.rows.map(radioTrack => radioTrack.track.uri);
      (0, _playlistActions.addTracksToPlaylist)(playlistUri, trackURIs, cb);
    });
  }
  
  function addStuffToPlaylist(playlistUri, uris, cb) {
    if (uris[0].type === _spotifyLiburi2.default.Type.ALBUM) {
      addAlbumToPlaylist(playlistUri, uris[0], cb);
      if (uris.length > 1) {
        console.warn('Passed more than a single album to addStuffToPlaylist(). Adding only one.');
      }
    } else if (uris[0].type === _spotifyLiburi2.default.Type.STATION) {
      addStationToPlaylist(playlistUri, uris[0], cb);
    } else {
      const urisStr = uris.map(uri => uri.toString());
      (0, _playlistActions.addTracksToPlaylist)(playlistUri, urisStr, cb);
    }
  }
  
  function fetchPlaylistName(playlistUri, cb) {
    const policy = {
      name: true
    };
  
    (0, _playlistData.fetchPlaylistMetadata)(playlistUri, policy, (error, data) => {
      if (data && data.name) {
        cb(error, data.name);
        return;
      }
      cb(error);
    });
  }
  
  function getRefreshedPlaylistData(playlistUri, cb) {
    fetchPlaylistName(playlistUri, async (nameError, oldName) => {
      if (nameError) {
        cb(nameError);
        return;
      }
  
      const playlistV1Uri = await (0, _playlistData.fetchPlaylistV1UriFromPlaylist)(playlistUri.toString());
      const name = (0, _playlistActions.refreshedPlaylistName)(oldName);
  
      _bridge2.default.cosmosJSON({
        method: 'POST',
        uri: 'hm://playlistfreshener/freshen',
        body: {
          playlistURI: playlistV1Uri,
          type: 'W2V_freshener'
        }
      }, (error, body) => {
        if (error) {
          cb(error);
          return;
        }
  
        const tracks = body.recommendedTracks.map(r => `spotify:track:${r.id}`);
  
        cb(null, {
          name,
          tracks
        });
      });
    });
  }
  
  function createRefreshedPlaylist(playlistUri, cb) {
    getRefreshedPlaylistData(playlistUri, (error, { name, tracks } = {}) => {
      if (error) {
        cb(error);
        return;
      }
  
      (0, _playlistActions.createNewPlaylist)(name, (createNewPlaylistError, newPlaylistUri) => {
        if (createNewPlaylistError) {
          cb(createNewPlaylistError);
          return;
        }
  
        (0, _playlistActions.addTracksToPlaylist)(newPlaylistUri, tracks, addTracksToPlaylistError => {
          cb(addTracksToPlaylistError, newPlaylistUri);
        });
      });
    });
  }
  
  function shouldPlaylistBeOffline(uri, cb) {
    const policy = {
      offline: true
    };
  
    (0, _playlistData.fetchPlaylistMetadata)(uri, policy, (error, data) => {
      if (data && data.offline) {
        const shouldBeOffline = OfflineStatus.shouldBeOffline(data.offline);
        cb(error, shouldBeOffline);
        return;
      }
      cb(error, data);
    });
  }
  
  // A synchronous way to "fetch" the playlist2shows ABBA flag.
  function getPlaylist2ShowsCell() {
    // initialState.playlist2ShowsCell is initialized on
    // apps/script/zlink/pre-init.js.
    const initialState = global.initialState || global.top && global.top.initialState || {};
    return initialState.playlist2ShowsCell || null;
  }
  
  function isPlaylist2Shows(formatListType) {
    const isCellEnabled = getPlaylist2ShowsCell() === 'Enabled';
    if (!isCellEnabled) {
      return false;
    }
  
    const validFormatListTypes = ['format-shows', 'format-shows-shuffle'];
  
    return validFormatListTypes.includes(formatListType);
  }
  
  function resyncPlaylist(uri, callback) {
    const opts = {
      method: 'POST',
      uri: `sp://core-playlist/v1/playlist/${uri}/resync`
    };
  
    return _bridge2.default.cosmos(opts, (err, body) => {
      if (callback) {
        if (err) {
          callback(err);
        } else if (body.status !== 200 && body.status !== 204) {
          const e = new Error(`Cosmos Request Error. Status: ${body.status}`);
          e.status = body.status;
          callback(e);
        } else {
          callback(null, body);
        }
      }
    });
  }
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../libs/bridge":53,"../../libs/live":172,"./format-lists":194,"./offline-status":196,"./playlist-actions":197,"./playlist-data":198,"spotify-liburi":553}],196:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.YES = exports.DOWNLOADING = exports.WAITING_SYNC_NOT_ALLOWED = exports.WAITING_NO_CONNECTION = exports.WAITING_OFFLINE_MODE = exports.WAITING = exports.NO = undefined;
  exports.isDownloading = isDownloading;
  exports.shouldBeOffline = shouldBeOffline;
  exports.progressFromStatus = progressFromStatus;
  exports.getLabelForStatus = getLabelForStatus;
  exports.sendUserMessageForStatus = sendUserMessageForStatus;
  
  var _bridge = require('../../libs/bridge');
  
  var _bridge2 = _interopRequireDefault(_bridge);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  const NO = exports.NO = 'no';
  const WAITING = exports.WAITING = 'waiting';
  const WAITING_OFFLINE_MODE = exports.WAITING_OFFLINE_MODE = 'waitingOfflineMode';
  const WAITING_NO_CONNECTION = exports.WAITING_NO_CONNECTION = 'waitingNoConnection';
  const WAITING_SYNC_NOT_ALLOWED = exports.WAITING_SYNC_NOT_ALLOWED = 'waitingSyncNotAllowed';
  const DOWNLOADING = exports.DOWNLOADING = 'downloading';
  const YES = exports.YES = 'yes';
  
  function isDownloading(offlineStatus) {
    return offlineStatus === DOWNLOADING;
  }
  
  function shouldBeOffline(offlineStatus) {
    switch (offlineStatus) {
      case WAITING:
      case WAITING_OFFLINE_MODE:
      case WAITING_NO_CONNECTION:
      case DOWNLOADING:
      case WAITING_SYNC_NOT_ALLOWED:
      case YES:
        return true;
      case NO:
      default:
        return false;
    }
  }
  
  // Returns 0 or 1 if able to derive
  // progress from the offlineStatus,
  // otherwise will return undefined.
  function progressFromStatus(offlineStatus) {
    switch (offlineStatus) {
      case WAITING:
      case WAITING_OFFLINE_MODE:
      case WAITING_NO_CONNECTION:
      case WAITING_SYNC_NOT_ALLOWED:
      case NO:
        return 0;
      case YES:
        return 1;
      case DOWNLOADING:
      default:
        return undefined;
    }
  }
  
  function getLabelForStatus(offlineStatus, isFormatShows) {
    switch (offlineStatus) {
      case DOWNLOADING:
        return 'Downloading';
      case WAITING_OFFLINE_MODE:
      case WAITING_NO_CONNECTION:
      case WAITING_SYNC_NOT_ALLOWED:
        return 'WaitingForConnection';
      case WAITING:
        return 'WaitingForDownload';
      case YES:
        return isFormatShows ? 'DownloadedSongs' : 'Downloaded';
      case NO:
      default:
        return isFormatShows ? 'DownloadSongs' : 'Download';
    }
  }
  
  function sendUserMessageForStatus(offlineStatus) {
    switch (offlineStatus) {
      case WAITING_OFFLINE_MODE:
        // Tell the user to turn off offline mode
        _bridge2.default.cosmosJSON({
          method: 'POST',
          uri: 'sp://messages/v1/container/user-message',
          body: {
            id: 'cant-offline-sync-playlist-in-offline-mode'
          }
        });
        return true;
      default:
        return false;
    }
  }
  
  },{"../../libs/bridge":53}],197:[function(require,module,exports){
  (function (global){
  // Playlist Actions: functions that mutate playlists in some way
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.DUPLICATE_ACTIONS = undefined;
  exports.addTracksToPlaylist = addTracksToPlaylist;
  exports.setPlaylistPublished = setPlaylistPublished;
  exports.createNewPlaylist = createNewPlaylist;
  exports.createNewPlaylistBefore = createNewPlaylistBefore;
  exports.createNewPlaylistAfter = createNewPlaylistAfter;
  exports.setPlaylistName = setPlaylistName;
  exports.setPlaylistCollaborative = setPlaylistCollaborative;
  exports.followPlaylist = followPlaylist;
  exports.removePlaylist = removePlaylist;
  exports.removePlaylistFolderRecursively = removePlaylistFolderRecursively;
  exports.refreshedPlaylistName = refreshedPlaylistName;
  exports.offlineSyncPlaylist = offlineSyncPlaylist;
  
  var _spotifyCosmosApi = require('spotify-cosmos-api');
  
  var _spotifyCosmosApi2 = _interopRequireDefault(_spotifyCosmosApi);
  
  var _live = require('../../libs/live');
  
  var _live2 = _interopRequireDefault(_live);
  
  var _bridge = require('../../libs/bridge');
  
  var _bridge2 = _interopRequireDefault(_bridge);
  
  var _playlistData = require('./playlist-data');
  
  var _surroundMessages = require('../../libs/surround-messages');
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  const DUPLICATE_ACTIONS = exports.DUPLICATE_ACTIONS = {
    ADD_ALL: 'add_all',
    ADD_UNIQUE: 'add_unique',
    CANCEL: 'cancel'
  };
  
  function getDuplicates(playlistURI, trackURIs, callback) {
    _spotifyCosmosApi2.default.resolver.post({
      url: `sp://core-playlist/v1/playlist/${encodeURIComponent(playlistURI)}/contains`,
      body: {
        items: trackURIs
      }
    }, function (error, response) {
      if (error) {
        callback([]);
        return;
      }
  
      const body = response.getJSONBody();
      const foundByIndex = body.found;
  
      const duplicates = [];
      const uniques = [];
  
      trackURIs.forEach((uri, index) => {
        if (foundByIndex[index]) {
          duplicates.push(trackURIs[index]);
        } else {
          uniques.push(trackURIs[index]);
        }
      });
  
      callback(duplicates, uniques);
    });
  }
  
  function maybeFilterDuplicates(playlistURI, trackURIs, callback) {
    getDuplicates(playlistURI, trackURIs, function (duplicates, uniques) {
      if (!duplicates.length) {
        callback(trackURIs);
        return;
      }
  
      _spotifyCosmosApi2.default.resolver.post({
        url: 'sp://messages/v1/avoidduplicates',
        body: {
          action: 'confirm',
          source: global.__spotify.app_uri,
          playlistUri: playlistURI,
          numItems: trackURIs.length,
          numDuplicates: duplicates.length
        }
      }, function (postError) {
        if (postError) {
          callback(trackURIs);
          return;
        }
  
        // Subscribe for the dialog response
        const subscription = _spotifyCosmosApi2.default.resolver.subscribe({
          url: 'sp://messages/v1/avoidduplicates'
        }, function (error, response) {
          subscription.cancel();
          if (error) {
            callback(trackURIs);
            return;
          }
          switch (response.getJSONBody().action) {
            case DUPLICATE_ACTIONS.ADD_ALL:
              callback(trackURIs);
              break;
  
            case DUPLICATE_ACTIONS.ADD_UNIQUE:
              callback(uniques);
              break;
  
            case DUPLICATE_ACTIONS.CANCEL:
              callback([]);
              break;
  
            default:
              throw new Error('Invalid DUPLICATE_ACTION.');
          }
        });
      });
    });
  }
  
  function addTracksToPlaylist(playlistURI, trackURIs, cb) {
    maybeFilterDuplicates(playlistURI, trackURIs, function (filteredTrackURIs) {
      if (!filteredTrackURIs.length) {
        cb();
        return;
      }
  
      _spotifyCosmosApi2.default.resolver.post({
        url: `sp://core-playlist/v1/playlist/${encodeURIComponent(playlistURI)}`,
        body: {
          operation: 'add',
          uris: filteredTrackURIs,
          after: 'end'
        }
      }, function (error, response) {
        // TODO(jaco): Move over to status code checking when CORE-1638 is done.
        const trackLimitExceeded = error && response && response.getJSONBody() && /exceed the maximum playlist size/i.test(response.getJSONBody().error);
        if (trackLimitExceeded) {
          _spotifyCosmosApi2.default.resolver.post({
            url: 'sp://messages/v1/container/user-message',
            body: { id: 'playlist-limit-exceeded' }
          });
        }
  
        if (error) {
          cb(error);
          return;
        }
        (0, _surroundMessages.sendQBEAction)(_surroundMessages.QBE_ADD_TO_PLAYLIST);
        cb();
      });
    });
  }
  
  function setPlaylistPublished(uriOrRowId, published, cb) {
    _bridge2.default.cosmosJSON({
      method: 'POST',
      uri: 'sp://core-playlist/v1/rootlist',
      body: {
        operation: 'set',
        attributes: { published },
        rows: [uriOrRowId.toString()]
      }
    }, cb);
  }
  
  function createNewPlaylist(name, cb) {
    _bridge2.default.cosmosJSON({
      method: 'POST',
      uri: 'sp://core-playlist/v1/rootlist',
      body: {
        operation: 'create',
        playlist: true,
        before: 'start',
        name
      }
    }, (error, body) => cb(error, body && body.uri));
  }
  
  function createNewPlaylistBefore(name, before, cb) {
    _bridge2.default.cosmosJSON({
      method: 'POST',
      uri: 'sp://core-playlist/v1/rootlist',
      body: {
        operation: 'create',
        playlist: true,
        before,
        name
      }
    }, (error, body) => cb(error, body && body.uri));
  }
  
  function createNewPlaylistAfter(name, after, cb) {
    _bridge2.default.cosmosJSON({
      method: 'POST',
      uri: 'sp://core-playlist/v1/rootlist',
      body: {
        operation: 'create',
        playlist: true,
        after,
        name
      }
    }, (error, body) => cb(error, body && body.uri));
  }
  
  function setPlaylistName(uri, name, cb) {
    _bridge2.default.cosmosJSON({
      method: 'POST',
      uri: `sp://core-playlist/v1/playlist/${encodeURIComponent(uri)}`,
      body: {
        operation: 'set',
        attributes: { name }
      }
    }, (error, body) => {
      if (!error) {
        (0, _live2.default)(uri).update({ name });
      }
      cb(error, body);
    });
  }
  
  function setPlaylistCollaborative(uri, collaborative, cb) {
    _bridge2.default.cosmosJSON({
      method: 'POST',
      uri: `sp://core-playlist/v1/playlist/${encodeURIComponent(uri)}`,
      body: {
        operation: 'set',
        attributes: { collaborative }
      }
    }, (setCollaborativeError, body) => {
      if (setCollaborativeError) {
        cb(setCollaborativeError, body);
        return;
      }
  
      if (collaborative) {
        // when we set a playlist as collaborative, check if
        // it's public and make it private
        (0, _playlistData.getCollaborativeStatus)(uri, (getCollaborativeError, data) => {
          if (getCollaborativeError || data.published === null) {
            cb(getCollaborativeError || new Error('`published` property unexpectedly missing'), body);
            return;
          }
  
          if (data.published) {
            exports.setPlaylistPublished(uri, false, cb);
            return;
          }
          cb(null, body);
        });
      } else {
        cb(null, body);
      }
    });
  }
  
  function broadcastFollowState(uri, isFollowing) {
    _bridge2.default.cosmosJSON({
      method: 'POST',
      uri: 'sp://messages/v1/followstate',
      body: {
        uri: uri.toString(),
        isFollowing
      }
    });
  }
  
  function followPlaylist(uri, cb) {
    _bridge2.default.cosmosJSON({
      method: 'POST',
      uri: 'sp://core-playlist/v1/rootlist',
      body: {
        operation: 'add',
        before: 'start',
        uris: [uri.toString()]
      }
    }, (error, body) => {
      if (!error) broadcastFollowState(uri.toString(), true);
      cb(error, body);
    });
  }
  
  function removePlaylist(uri, cb) {
    _bridge2.default.cosmosJSON({
      method: 'POST',
      uri: 'sp://core-playlist/v1/rootlist',
      body: {
        operation: 'remove',
        rows: [uri.toString()]
      }
    }, (error, body) => {
      if (!error) broadcastFollowState(uri.toString(), false);
      cb(error, body);
    });
  }
  
  function removePlaylistFolderRecursively(uri, cb) {
    _bridge2.default.cosmosJSON({
      method: 'POST',
      uri: 'sp://core-playlist/v1/rootlist',
      body: {
        operation: 'remove',
        contents: true,
        rows: [uri.toString()]
      }
    }, cb);
  }
  
  function refreshedPlaylistName(oldName) {
    if (!oldName) {
      // TODO(jaco): Should be localised, but since the name is still very much up
      // in the air and this will only go out to employees, we don't want to send
      // it off for translation yet since it will likely change a couple of times
      // before we're happy with it.
      return 'Similar Playlist';
    }
  
    const nextInt = function (intStr) {
      return parseInt(intStr, 10) + 1;
    };
  
    // Parenthesized number suffix: "Playlist (2)"
    const matches = oldName.match(new RegExp('(.*)\\((\\d+)\\)$'));
    if (matches) {
      return `${matches[1].trim()} (${nextInt(matches[2])})`;
    }
  
    return `${oldName} (2)`;
  }
  
  function offlineSyncPlaylist(uri, shouldBeOffline, cb) {
    _bridge2.default.cosmos({
      method: shouldBeOffline ? 'POST' : 'DELETE',
      uri: `sp://offline/v1/resources?uri=${uri}`
    }, cb);
  }
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../libs/bridge":53,"../../libs/live":172,"../../libs/surround-messages":315,"./playlist-data":198,"spotify-cosmos-api":543}],198:[function(require,module,exports){
  (function (global){
  // Playlist Data: playlist data fetching and data subscription.
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  
  var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
  
  exports.fetchPlaylistMetadata = fetchPlaylistMetadata;
  exports.subscribeToMetadata = subscribeToMetadata;
  exports.getCollaborativeStatus = getCollaborativeStatus;
  exports.playlistFiltersToReturn = playlistFiltersToReturn;
  exports.replaceFiltersToReturn = replaceFiltersToReturn;
  exports.fetchPlaylistOwner = fetchPlaylistOwner;
  exports.fetchIsPlaylistOwnedByCurrentUser = fetchIsPlaylistOwnedByCurrentUser;
  exports.fetchPlaylistV1UriFromPlaylist = fetchPlaylistV1UriFromPlaylist;
  exports.getCorePlaylistUrl = getCorePlaylistUrl;
  exports.playlistRequest = playlistRequest;
  exports.getAvailablePlaylistName = getAvailablePlaylistName;
  
  var _spotifyLiburi = require('spotify-liburi');
  
  var _spotifyLiburi2 = _interopRequireDefault(_spotifyLiburi);
  
  var _bridge = require('../../libs/bridge');
  
  var _bridge2 = _interopRequireDefault(_bridge);
  
  var _liveWrappedUri = require('../../libs/live-wrapped-uri');
  
  var _liveWrappedUri2 = _interopRequireDefault(_liveWrappedUri);
  
  var _coreSortParams = require('../../libs/core-sort-params');
  
  var _coreSortParams2 = _interopRequireDefault(_coreSortParams);
  
  var _abba = require('../../libs/abba');
  
  var _abba2 = _interopRequireDefault(_abba);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  /**
   * Possible policy fields (may not be an exhaustive list):
   *
   * {
   *   link: true,
   *   loaded: true,
   *   published: true,
   *   browsableOffline: true,
   *   totalLength: true,
   *   name: true,
   *   picture: true,
   *   pictureFromAnnotate: true,
   *   canReportAnnotationAbuse: true,
   *   description: true,
   *   descriptionFromAnnotate: true,
   *   collaborative: true,
   *   formatListAttributes: true,
   *   length: true,
   *   duration: true,
   *   lastModification: true,
   *   allows: {
   *     insert: true,
   *     remove: true
   *   },
   *   followed: true,
   *   offline: true,
   *   followers: true,
   *   owner: {
   *     link: true,
   *     username: true,
   *     name: true
   *   },
   *   ownedBySelf: true
   * }
   */
  function fetchPlaylistMetadata(playlistUri, policy, cb) {
    const playlistUriWithoutQueryParams = _spotifyLiburi2.default.from(playlistUri).toURI();
    const encodedPlaylistUri = encodeURIComponent(playlistUriWithoutQueryParams);
  
    _bridge2.default.cosmosJSON({
      method: 'GET',
      uri: `sp://core-playlist/v1/playlist/${encodedPlaylistUri}/metadata`,
      body: {
        policy: policy
      }
    }, (error, { error: responseError = null, metadata } = {}) => cb(error || responseError, metadata));
  }
  
  function subscribeToMetadata(playlistUri, policy, cb) {
    const playlistUriWithoutQueryParams = _spotifyLiburi2.default.from(playlistUri).toURI();
    const encodedPlaylistUri = encodeURIComponent(playlistUriWithoutQueryParams);
  
    return _bridge2.default.cosmosJSON({
      method: 'SUB',
      uri: `sp://core-playlist/v1/playlist/${encodedPlaylistUri}/metadata`,
      body: { policy: policy }
    }, (subError, body) => cb(subError, body && body.metadata));
  }
  
  function getCollaborativeStatus(uri, cb) {
    const policy = {
      collaborative: true,
      published: true
    };
    fetchPlaylistMetadata(uri.toString(), policy, cb);
  }
  
  function playlistFiltersToReturn(returnUnplayableTracks, returnPremiumOnlyTracks) {
    if (returnUnplayableTracks) {
      if (returnPremiumOnlyTracks) {
        // Return all the tracks. No filters. No restrictions!
        return [];
      }
      // Return unplayable tracks without the premium-only tracks.
      // Equivalent to: return all the tracks, except premium-only tracks.
      // Equivalent to: don't return premium-only tracks.
      return ['isPremiumOnly eq false'];
    }
  
    // returnUnplayableTracks=false
  
    if (returnPremiumOnlyTracks) {
      // Don't return "regular unplayable" tracks, but return premium-only tracks.
      // A track can be returned if it used to be defined as playable or if it's
      // premium-only.
      const availabilityFlags = 'available|cappingReached|' + 'inPremium'; // regular (pre-premiere) definition of playable // available (playable) only in Premium
  
      // Availability flags are based on the ResourceAvailabilityType enum defined
      // in core (spotify/metadata/cpp/include/spotify/metadata/resource_availability.h).
      return [`availability in ${availabilityFlags}`];
    }
    // Don't return regular unplayable tracks and don't return premium-only
    // tracks.
    // Equivalent to: don't return any non-playable track.
    return ['playable eq true'];
  }
  
  function replaceFiltersToReturn(filters, returnUnplayableTracks, returnPremiumOnlyTracks) {
    const newFilters = filters.filter(filter => {
      return !filter.startsWith('playable') && !filter.startsWith('isPremiumOnly') && !filter.startsWith('availability');
    });
    const filtersToAdd = playlistFiltersToReturn(returnUnplayableTracks, returnPremiumOnlyTracks);
    return newFilters.concat(filtersToAdd);
  }
  
  function isLocalFilesUri(uri) {
    return (/:internal:local-files.*/.test(uri)
    );
  }
  
  async function fetchPlaylistOwner(playlistUri) {
    const parsedUri = _spotifyLiburi2.default.from(playlistUri);
    if (parsedUri.type === _spotifyLiburi2.default.Type.PLAYLIST) {
      return parsedUri.username;
    } else if (parsedUri.type === _spotifyLiburi2.default.Type.PLAYLIST_V2) {
      return await new Promise((resolve, reject) => fetchPlaylistMetadata(playlistUri, { owner: { username: true } }, (error, { owner: { username: fetchedUsername } = {} } = {}) => {
        if (error) reject(error);else if (!fetchedUsername) {
          reject(new Error(`No owner username found for ${playlistUri}`));
        } else resolve(fetchedUsername);
      }));
    }
    throw new Error(`The uri ${playlistUri} is not any kind of playlist uri`);
  }
  
  async function fetchIsPlaylistOwnedByCurrentUser(playlistUri) {
    return (await fetchPlaylistOwner(playlistUri)) === global.__spotify.username;
  }
  
  async function fetchPlaylistV1UriFromPlaylist(uri) {
    const { type, id } = _spotifyLiburi2.default.from(uri);
    if (type === _spotifyLiburi2.default.Type.PLAYLIST_V2) {
      const username = await exports.fetchPlaylistOwner(uri);
      return _spotifyLiburi2.default.playlistURI(username, id).toURI();
    } else if (type === _spotifyLiburi2.default.Type.PLAYLIST) {
      return uri;
    }
    throw new Error(`The uri ${uri} is not any kind of playlist uri`);
  }
  
  function getCorePlaylistUrl(uri, { type, start, length, includeEpisodes, version = 'v1' } = {}, isOwnPlaylist, windowedPlaylistShowTracklistCell) {
    const { originUri = uri, sort, filter } = _liveWrappedUri2.default.parse(uri) || {};
    let filters = filter ? filter.query.split(',') : [];
  
    if (!isLocalFilesUri(uri)) {
      // Project Premiere "redefined" what is considered 'playable' in the context
      // of the "Show Unavailable(Unplayable) Tracks in Playlists" setting.
      // We do the proper translation to core URIs here assuming this filter ended
      // up here due to the user setting.
      const returnUnplayableTracks = !filters.includes('playable eq true');
  
      let returnPremiumOnlyTracks;
      if (isOwnPlaylist) {
        returnPremiumOnlyTracks = true;
      } else if (windowedPlaylistShowTracklistCell === 'Enabled') {
        returnPremiumOnlyTracks = returnUnplayableTracks;
      } else {
        returnPremiumOnlyTracks = false;
      }
  
      filters = replaceFiltersToReturn(filters, returnUnplayableTracks, returnPremiumOnlyTracks);
    }
  
    const originUriWithoutQueryParams = _spotifyLiburi2.default.from(originUri).toURI();
  
    let url = isLocalFilesUri(uri) ? 'sp://local-files/v2' : `sp://core-playlist/${version}/playlist/${encodeURIComponent(originUriWithoutQueryParams)}`;
  
    if (type) {
      url += `/${isLocalFilesUri(uri) && type === 'rows' ? 'tracks' : type}`;
    }
  
    const query = [];
    if (sort && type !== 'metadata') {
      const direction = sort.direction === 'desc' ? 'DESC' : 'ASC';
      query.push(`sort=${encodeURIComponent((0, _coreSortParams2.default)(sort.query, direction))}`);
    }
    if (typeof start === 'number') {
      query.push(`start=${start}`);
    }
    if (typeof length === 'number') {
      query.push(`length=${length}`);
    }
    if (includeEpisodes === true) {
      query.push('includeEpisodes=true');
    }
  
    if (filters.length) {
      query.push(`filter=${filters.map(encodeURIComponent).join(',')}`);
    }
    if (type === 'play') {
      // Include a context URI that is then reflected as the context in the player state
      query.push(`uri=${encodeURIComponent(uri)}`);
    }
    if (query.length) {
      // TODO(felipec): remove the extra & and fix tests
      url += `?${query.join('&')}`;
    }
    return url;
  }
  
  function playlistRequest(uri, {
    method = 'GET',
    policy = {},
    body = {},
    type,
    start,
    length,
    includeEpisodes,
    version
  } = {}, callback) {
    const { originUri = uri } = _liveWrappedUri2.default.parse(uri) || {};
    let cancelFunction;
  
    const isOwnPlaylistPromise = isLocalFilesUri(uri) ? Promise.resolve(null) : exports.fetchIsPlaylistOwnedByCurrentUser(originUri);
  
    Promise.all([isOwnPlaylistPromise, _abba2.default.fetch('windowed-playlist-show-tracklist')]).then(([isOwnPlaylist, windowedPlaylistShowTracklistCell]) => {
      let lastResponseBodyString;
      cancelFunction = _bridge2.default.cosmos({
        method,
        uri: exports.getCorePlaylistUrl(uri, { type, start, length, includeEpisodes, version }, isOwnPlaylist, windowedPlaylistShowTracklistCell),
        body: JSON.stringify(_extends({}, body, {
          policy: _extends({}, body.policy || {}, policy)
        }))
      }, (error, response) => {
        if (error) {
          callback(error, response);
        } else if (response && !response.body && response.status >= 400) {
          callback(new Error(`${response.uri} responded with status ${response.status}`), response);
        } else {
          try {
            // Unfortunately core-playlist may pub even if nothing has changed,
            // and since some callers (eg the playlist app) runs pretty heavy
            // callbacks, we'll make sure to only ever call them when something
            // has actually changed.
            if (method === 'SUB') {
              if (response.body === lastResponseBodyString) return;
              lastResponseBodyString = response.body;
            }
            callback(null, JSON.parse(response.body));
          } catch (parseError) {
            parseError.message = `Failed to parse cosmos response: ${parseError.message}`;
            callback(parseError, response);
          }
        }
      });
    }).catch(callback);
  
    return {
      cancel() {
        if (cancelFunction) cancelFunction();
      }
    };
  }
  
  function getAvailablePlaylistName(candidateName, callback) {
    _bridge2.default.cosmosJSON({
      method: 'GET',
      uri: `sp://core-playlist/v1/rootlist?filter=${encodeURIComponent(`name startsWith ${encodeURIComponent(candidateName)}`)}`,
      body: {
        policy: {
          playlist: {
            name: true
          }
        }
      }
    }, (error, body) => {
      if (error) {
        callback(error);
        return;
      }
      const highestSuffix = body.rows.map(({ name }) => name.substr(candidateName.length).trim() || '1').filter(suffix => /\d+/.test(suffix)).map(Number).sort((a, b) => a - b).pop();
  
      const availablePlaylistName = `${candidateName}${highestSuffix ? ` ${highestSuffix + 1}` : ''}`;
      callback(null, availablePlaylistName);
    });
  }
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../libs/abba":51,"../../libs/bridge":53,"../../libs/core-sort-params":57,"../../libs/live-wrapped-uri":171,"spotify-liburi":553}],199:[function(require,module,exports){
  'use strict';
  
  /**
   * Module dependencies
   */
  
  const extend = require('extend');
  const bridge = require('../bridge');
  const batch = require('spotify-batch');
  
  /**
   * Bridge methods
   */
  
  const PREF_ALL = 'preferences_all';
  const PREF_GET = 'preferences_get';
  const PREF_SET = 'preferences_set';
  const PREF_SUB = 'preferences_event_wait';
  
  /**
   * Converts a string with dot notation into
   * a nested object where the last key is
   * set to value
   *
   *  eg: f('foo.bar', 123) = {foo: {bar: 123}}
   *
   * @api private
   * @param {String} string
   * @param {Mixed} value
   * @param {String} delimiter - optional
   */
  
  function transformStringIntoObjectAndSetValue(string, value, delimiter) {
    const object = {};
    string.split(delimiter || '.').reduce(function (o, c, i, a) {
      if (i < a.length - 1) {
        o[c] = {};
      } else {
        o[c] = value;
      }
  
      return o[c];
    }, object);
  
    return object;
  }
  
  /**
   * Converts an object with nest objects into a flat object
   *
   *  eg: f({foo: {bar: 123}}) = {'foo.bar': 123}
   *
   * @api private
   * @param {String} string
   * @param {String} delimiter - optional
   */
  
  function transformObjectIntoFlatObject(object, delimiter) {
    const flattened = {};
    const newDelimiter = delimiter || '.';
    function traverse(o, key) {
      let tmp = null;
      for (const p in o) {
        if (typeof o[p] === 'object') {
          const newKey = key ? [key, p].join(newDelimiter) : p;
          traverse(o[p], newKey);
        } else {
          tmp = key ? [key, p].join(newDelimiter) : p;
          flattened[tmp] = o[p];
        }
      }
    }
  
    traverse(object);
    return flattened;
  }
  
  /**
   * Normalizes data from bridge responses
   *
   * @api private
   * @param {String} key
   * @param {Object} data
   * @param {Boolean} preserve - optional (Default: false)
   */
  
  function normalizeData(key, data, preserve) {
    const out = {};
    const regex = key === '*' ? null : RegExp(`^${key}`);
    let targets = null;
  
    if (typeof data !== 'object') {
      return data;
    }
  
    targets = Object.keys(data).map(function (innerkey) {
      if (regex === null || regex.test(innerkey)) {
        return [innerkey, data[innerkey]];
      }
      return false;
    }).filter(Boolean).reduce(function (t, c) {
      t[c[0]] = c[1];
      return t;
    }, {});
  
    Object.keys(targets).forEach(function (innerkey) {
      // extend output object with transformed properties
      extend(true, out, transformStringIntoObjectAndSetValue(innerkey, targets[innerkey]));
    });
  
    if (key === '*' || preserve === true) {
      return out;
    }
    return key.split('.').reduce(function (o, k) {
      return o[k];
    }, out);
  }
  
  /**
   * Subscribes to a key.
   *
   * @api private
   * @param {Function} rpc The RPC to use.
   * @param {String} key The key (including path).
   * @param {*} lastValue The last value of the key (used for comparing).
   * @param {Function} fn Subscribe handler.
   * @param {Object=} sub Subscription object, optional.
   */
  
  function subscribeToKey(rpc, key, lastValue, fn, sub) {
    const newSub = sub || {
      isCancelled: false,
      cancel: function () {
        this.isCancelled = true;
      }
    };
  
    rpc(PREF_SUB, [], function (err, res) {
      if (newSub.isCancelled) return;
  
      let didValueChange = false;
  
      let newLastValue = lastValue;
      if (res) {
        const value = normalizeData(key, res);
        if (value !== newLastValue) {
          newLastValue = value;
          didValueChange = true;
        }
      }
  
      // Resubscribe since one subscription is only for one response
      subscribeToKey(rpc, key, newLastValue, fn, newSub);
  
      if (didValueChange) {
        fn(null, newLastValue);
      }
    });
  
    return newSub;
  }
  
  /**
   * `Preferences' constructor
   *
   * @api public
   * @param {String} path - optional (Default: '*')
   * @param {Function} rpc - optional
   */
  
  module.exports = Preferences;
  function Preferences(path, rpc) {
    if (!(this instanceof Preferences)) {
      return new Preferences(path, rpc);
    }
  
    this.path = path || '*';
    this.rpc = rpc || bridge.request.bind(bridge);
  }
  
  /**
   * Retrieves all preferences at path.
   * If path is '*' then all preferences
   * are retrieved.
   *
   * @api public
   * @param {Function} fn
   */
  
  Preferences.prototype.all = function (fn) {
    const path = this.path;
  
    this.rpc(PREF_ALL, [], function (err, res) {
      if (err) {
        fn(err);
      } else if (path === '*') {
        fn(null, res);
      } else {
        fn(null, normalizeData(path, res, true));
      }
    });
  
    return this;
  };
  
  /**
   * Sets a preference at key at path
   * with value
   *
   * @api public
   * @param {String} key
   * @param {Mixed} value
   * @param {Function} fn
   */
  
  Preferences.prototype.set = function (key, value, fn) {
    const path = this.path;
    let map = null;
    let jobs = null;
  
    // handle batch jobs to extend bridge preferences
    if (typeof key === 'object') {
      const newFn = value;
  
      map = transformObjectIntoFlatObject(key);
      jobs = batch().concurrency(1);
      Object.keys(map).forEach(k => {
        jobs.push(next => {
          this.set(k, map[k], next);
        });
      });
  
      jobs.run(newFn);
    } else {
      const newKey = path === '*' ? key : [path, key].join('.');
      this.rpc(PREF_SET, [newKey, value], fn);
    }
    return this;
  };
  
  /**
   * Sets a preference bitflag at path, based on the inputs.
   *
   * @api public
   * @param {String} key
   * @param {Array} flagBoolPairs
   * @param {Function} fn
   */
  
  Preferences.prototype.setAtomicFlags = function (key, flagBoolPairs, fn) {
    this.get(key, (err, value) => {
      if (err) {
        fn(err);
      } else {
        let newValue = value;
        flagBoolPairs.forEach(function (pair) {
          const flag = pair[0];
          const bool = pair[1];
          if (bool === undefined || bool === null) return;
          if (bool) newValue |= flag;else newValue &= ~flag;
        });
  
        this.set(key, newValue, fn);
      }
    });
  };
  
  /**
   * Gets a preference at key at path
   *
   * @api public
   * @param {String} key
   * @param {Function} fn
   */
  
  Preferences.prototype.get = function (key, fn) {
    const newKey = this.path === '*' ? key : [this.path, key].join('.');
  
    // try to get property first
    this.rpc(PREF_GET, [newKey], (err, res) => {
      const value = res ? normalizeData(newKey, res) : null;
      if (value === null) {
        this.rpc(PREF_ALL, [], function (innererr, innerres) {
          if (innererr) {
            fn(innererr);
          } else {
            fn(null, normalizeData(newKey, innerres));
          }
        });
      } else {
        fn(null, normalizeData(newKey, res));
      }
    });
    return this;
  };
  
  /**
   * Subscribes to a preference at key at path
   *
   * @api public
   * @param {String} key
   * @param {Function} fn
   */
  
  Preferences.prototype.subscribe = function (key, fn) {
    let sub = null;
    let cancelled = false;
  
    // Get initial value and then subscribe for changes
    this.get(key, (err, res) => {
      if (cancelled) return;
      const keyWithPath = this.path === '*' ? key : [this.path, key].join('.');
      sub = subscribeToKey(this.rpc, keyWithPath, res, fn);
  
      // Respond to the GET (initial value for SUB)
      fn(err, res);
    });
  
    return {
      cancel: function () {
        cancelled = true;
        if (sub) {
          sub.cancel();
        }
      }
    };
  };
  
  },{"../bridge":53,"extend":332,"spotify-batch":537}],200:[function(require,module,exports){
  (function (process,global){
  /*
  defer
  */'use strict';
  
  const now = require('mout/time/now');
  const forEach = require('mout/array/forEach');
  const indexOf = require('mout/array/indexOf');
  
  const callbacks = {
    timeout: {},
    frame: [],
    immediate: []
  };
  
  const iterate = function (collection) {
    const time = now();
  
    forEach(collection.splice(0), function (entry) {
      entry.callback.call(entry.context, time);
    });
  };
  
  const push = function (collection, callback, context, defer) {
    const iterator = function () {
      iterate(collection);
    };
  
    if (!collection.length) defer(iterator);
  
    const entry = {
      callback: callback,
      context: context
    };
  
    collection.push(entry);
  
    return function () {
      const io = indexOf(collection, entry);
      if (io > -1) collection.splice(io, 1);
    };
  };
  
  const defer = {};
  
  if (global.process && process.nextTick) {
    defer.immediate = function (callback, context) {
      return push(callbacks.immediate, callback, context, process.nextTick);
    };
  } else if (global.setImmediate) {
    defer.immediate = function (callback, context) {
      return push(callbacks.immediate, callback, context, setImmediate);
    };
  } else if (global.postMessage && global.addEventListener) {
    addEventListener('message', function (event) {
      if (event.source === global && event.data === '@deferred') {
        event.stopPropagation();
        iterate(callbacks.immediate);
      }
    }, true);
  
    defer.immediate = function (callback, context) {
      return push(callbacks.immediate, callback, context, function () {
        postMessage('@deferred', '*');
      });
    };
  } else {
    defer.immediate = function (callback, context) {
      return push(callbacks.immediate, callback, context, function (iterator) {
        setTimeout(iterator, 0);
      });
    };
  }
  
  let clear;
  
  defer.timeout = function (callback, ms, context) {
    const ct = callbacks.timeout;
  
    if (!clear) {
      clear = defer.immediate(function () {
        clear = null;
        callbacks.timeout = {};
      });
    }
  
    return push(ct[ms] || (ct[ms] = []), callback, context, function (iterator) {
      setTimeout(iterator, ms);
    });
  };
  
  module.exports = defer;
  
  }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"_process":481,"mout/array/forEach":439,"mout/array/indexOf":440,"mout/time/now":478}],201:[function(require,module,exports){
  /*
  Emitter
  */'use strict';
  
  const indexOf = require('mout/array/indexOf');
  const forEach = require('mout/array/forEach');
  
  const prime = require('./index');
  const defer = require('./defer');
  
  const slice = Array.prototype.slice;
  
  let resetCount = 0;
  
  const Emitter = prime({
    constructor: function (stoppable) {
      this._stoppable = stoppable;
    },
  
    on: function (event, fn) {
      const listeners = this._listeners || (this._listeners = {});
      const events = listeners[event] || (listeners[event] = []);
  
      if (indexOf(events, fn) === -1) {
        events.push(fn);
      }
  
      return this;
    },
  
    off: function (event, fn) {
      const listeners = this._listeners;
      if (listeners) {
        const events = listeners[event];
        if (events) {
          const io = indexOf(events, fn);
          if (io > -1) events.splice(io, 1);
          if (!events.length) delete listeners[event];
          for (const l in listeners) {
            if (!listeners.hasOwnProperty(l)) continue;
            return this;
          }
          delete this._listeners;
        }
      }
      return this;
    },
  
    emit: function (event) {
      const args = slice.call(arguments, 1);
  
      const emit = () => {
        const listeners = this._listeners;
        if (listeners) {
          const events = listeners[event];
          if (events) {
            forEach(events.slice(0), innerEvent => {
              const result = innerEvent.apply(this, args);
              if (this._stoppable) return result;
              return undefined;
            });
          }
        }
        if (this._promiseWaiters && this._promiseWaiters[event]) {
          const execute = this._promiseWaiters[event].slice();
          this._promiseWaiters[event] = [];
          execute.forEach(waiter => waiter(...args));
        }
      };
  
      if (args[args.length - 1] === Emitter.EMIT_SYNC) {
        args.pop();
        emit();
      } else {
        // Defer, and check that the emitter wasn't reset() in the same event loop iteration
        const currentReset = resetCount;
        defer.immediate(() => {
          if (currentReset === resetCount) {
            emit();
          }
        });
      }
  
      return this;
    },
  
    reset: function () {
      resetCount++;
      this._listeners = {};
    },
  
    whenNextEvent: function (event) {
      return new Promise(resolve => {
        this._promiseWaiters = this._promiseWaiters || {};
        this._promiseWaiters[event] = [...(this._promiseWaiters[event] || []), resolve];
      });
    }
  });
  
  Emitter.EMIT_SYNC = {};
  
  module.exports = Emitter;
  
  },{"./defer":200,"./index":202,"mout/array/forEach":439,"mout/array/indexOf":440}],202:[function(require,module,exports){
  /*
  prime
   - prototypal inheritance
  */'use strict';
  
  const hasOwn = require('mout/object/hasOwn');
  const mixIn = require('mout/object/mixIn');
  const create = require('mout/lang/createObject');
  const kindOf = require('mout/lang/kindOf');
  
  let hasDescriptors = true;
  
  try {
    Object.defineProperty({}, '~', {});
    Object.getOwnPropertyDescriptor({}, '~');
  } catch (e) {
    hasDescriptors = false;
  }
  
  // we only need to be able to implement "toString" and "valueOf" in IE < 9
  const hasEnumBug = !{ valueOf: 0 }.propertyIsEnumerable('valueOf');
  const buggy = ['toString', 'valueOf'];
  
  const verbs = /^constructor|inherits|mixin$/;
  
  const implement = function (proto) {
    const prototype = this.prototype;
  
    for (const key in proto) {
      if (key.match(verbs)) continue;
      if (hasDescriptors) {
        const descriptor = Object.getOwnPropertyDescriptor(proto, key);
        if (descriptor) {
          Object.defineProperty(prototype, key, descriptor);
          continue;
        }
      }
      prototype[key] = proto[key];
    }
  
    if (hasEnumBug) {
      buggy.forEach(innerKey => {
        const value = proto[innerKey];
        if (value !== Object.prototype[innerKey]) prototype[innerKey] = value;
      });
    }
  
    return this;
  };
  
  const prime = function (proto) {
    let newProto = proto;
    if (kindOf(newProto) === 'Function') {
      newProto = { constructor: proto };
    }
  
    const superprime = newProto.inherits;
  
    // if our nice proto object has no own constructor property
    // then we proceed using a ghosting constructor that all it does is
    // call the parent's constructor if it has a superprime, else an empty constructor
    // newProto.constructor becomes the effective constructor
  
    let constructor;
  
    if (hasOwn(newProto, 'constructor')) {
      constructor = newProto.constructor;
    } else {
      if (superprime) {
        constructor = function () {
          return superprime.apply(this, arguments);
        };
      } else {
        constructor = function () {};
      }
    }
  
    if (superprime) {
      mixIn(constructor, superprime);
  
      const superproto = superprime.prototype;
      // inherit from superprime
      const cproto = constructor.prototype = create(superproto);
  
      // setting constructor.parent to superprime.prototype
      // because it's the shortest possible absolute reference
      constructor.parent = superproto;
      cproto.constructor = constructor;
    }
  
    if (!constructor.implement) constructor.implement = implement;
  
    let mixins = newProto.mixin;
    if (mixins) {
      if (kindOf(mixins) !== 'Array') mixins = [mixins];
      for (let i = 0; i < mixins.length; i++) {
        constructor.implement(create(mixins[i].prototype));
      }
    }
  
    // implement newProto and return constructor
    return constructor.implement(newProto);
  };
  
  module.exports = prime;
  
  },{"mout/lang/createObject":451,"mout/lang/kindOf":461,"mout/object/hasOwn":469,"mout/object/mixIn":471}],203:[function(require,module,exports){
  /*
  Map
  */
  'use strict';
  
  const indexOf = require('mout/array/indexOf');
  
  const prime = require('./index');
  
  const Map = prime({
    constructor: function Map() {
      this.length = 0;
      this._values = [];
      this._keys = [];
    },
  
    set: function (key, value) {
      const index = indexOf(this._keys, key);
  
      if (index === -1) {
        this._keys.push(key);
        this._values.push(value);
        this.length++;
      } else {
        this._values[index] = value;
      }
  
      return this;
    },
  
    get: function (key) {
      const index = indexOf(this._keys, key);
      return index === -1 ? null : this._values[index];
    },
  
    count: function () {
      return this.length;
    },
  
    forEach: function (method, context) {
      for (let i = 0, l = this.length; i < l; i++) {
        if (method.call(context, this._values[i], this._keys[i], this) === false) {
          break;
        }
      }
      return this;
    },
  
    map: function (method, context) {
      const results = new Map();
      this.forEach(function (value, key) {
        results.set(key, method.call(context, value, key, this));
      }, this);
      return results;
    },
  
    filter: function (method, context) {
      const results = new Map();
      this.forEach(function (value, key) {
        if (method.call(context, value, key, this)) results.set(key, value);
      }, this);
      return results;
    },
  
    every: function (method, context) {
      let every = true;
      this.forEach(function (value, key) {
        if (!method.call(context, value, key, this)) {
          every = false;
        }
      }, this);
      return every;
    },
  
    some: function (method, context) {
      let some = false;
      this.forEach(function (value, key) {
        if (method.call(context, value, key, this)) {
          some = true;
        }
      }, this);
      return some;
    },
  
    indexOf: function (value) {
      const index = indexOf(this._values, value);
      return index > -1 ? this._keys[index] : null;
    },
  
    remove: function (value) {
      const index = indexOf(this._values, value);
  
      if (index !== -1) {
        this._values.splice(index, 1);
        this.length--;
        return this._keys.splice(index, 1)[0];
      }
  
      return null;
    },
  
    unset: function (key) {
      const index = indexOf(this._keys, key);
  
      if (index !== -1) {
        this._keys.splice(index, 1);
        this.length--;
        return this._values.splice(index, 1)[0];
      }
  
      return null;
    },
  
    keys: function () {
      return this._keys.slice();
    },
  
    values: function () {
      return this._values.slice();
    }
  });
  
  const map = function () {
    return new Map();
  };
  
  map.prototype = Map.prototype;
  
  module.exports = map;
  
  },{"./index":202,"mout/array/indexOf":440}],204:[function(require,module,exports){
  /**
   * @module range2
   */
  'use strict';
  
  const prime = require('../../libs/prime');
  
  const map = require('mout/array/map');
  
  const push_ = Array.prototype.push;
  const slice_ = Array.prototype.slice;
  
  const Range = prime(
  /** @lends Range.prototype */{
    /**
     * The representation of a range
     * @constructs
     * @param {Number} start The start of the range.
     * @param {Number} end The end of the range.
     */
    constructor: function Range(start, end) {
      this.update(start, end);
    },
  
    /** Update a range. */
    update: function (start, end) {
      if (start !== null) this.start = start;
      if (end !== null) this.end = end;
      if (this.start === null || this.end === null || this.start > this.end) {
        throw new Error(`invalid range: [${this.start}, ${this.end}]`);
      }
      this.length = this.end - this.start;
      return this;
    },
  
    /** Copy a range. */
    copy: function () {
      return new Range(this.start, this.end);
    },
  
    /** Range is above range. */
    above: function (range) {
      if (!range) return false;
      return this.start >= range.end;
    },
  
    /** Range is below range. */
    below: function (range) {
      if (!range) return false;
      return this.end <= range.start;
    },
  
    adjacent: function (range) {
      if (!range) return false;
      return this.end === range.start || this.start === range.end;
    },
  
    /** Range intersects range. */
    intersects: function (range) {
      if (!range) return false;
      return !this.above(range) && !this.below(range);
    },
  
    /** Range contains range. */
    contains: function (range) {
      if (!range) return false;
      return this.start <= range.start && this.end >= range.end;
    },
  
    /** Range is contained by ranges. */
    contained: function (range) {
      let ranges;
      if (range instanceof Range) ranges = arguments;else ranges = range;
      if (!ranges) return false;
  
      for (let i = 0; ranges[i] !== undefined; i++) {
        const r = ranges[i];
        if (r.start <= this.start && r.end >= this.end) {
          return true;
        }
      }
      return false;
    },
  
    /** Range fits two ranges. */
    fits: function (prev, next) {
      if (!prev && !next) return true;
      if (!prev) return this.end <= next.start;
      if (!next) return this.start >= prev.end;
      return this.start >= prev.end && this.end <= next.start;
    },
  
    // get the section of this range contained between two ranges
    // [0, 20] » [0, 8], [16, 20] » [8, 16]
  
    /** portion of a range between two ranges. */
    between: function (prev, next) {
      if (!prev && !next) return this.copy();
      if (!prev) {
        return this.start >= next.start ? null : new Range(this.start, Math.min(this.end, next.start));
      }
      if (!next) {
        return this.end <= prev.end ? null : new Range(Math.max(prev.end, this.start), this.end);
      }
  
      return this.end > prev.end && this.start < next.start ? new Range(Math.max(prev.end, this.start), Math.min(next.start, this.end)) : null;
    },
  
    // [0, 8] » [0, 2], [4, 5] = [0, 2], [4, 5]
    // [3, 6] » [5, 7] = [5, 6]
    // [6, 9] » [5, 7] = [6, 7]
  
    /** intersection of a range with many ranges */
    intersection: function (range) {
      const intersected = [];
      let ranges;
      if (range instanceof Range) ranges = arguments;else ranges = range;
      for (let k = 0; k < ranges.length; k++) {
        const r = ranges[k];
        if (this.below(r)) break;
  
        if (r.intersects(this)) {
          intersected.push(new Range(Math.max(this.start, r.start), Math.min(this.end, r.end)));
        }
      }
      return intersected;
    },
  
    // subtract ranges from this range
    // [0, 8] » [0, 2], [4, 5] = [2, 4], [5, 8]
  
    /** subtract many ranges from the range. */
    subtract: function (range) {
      const subtracted = [];
      let ranges;
      if (range instanceof Range) ranges = arguments;else ranges = range;
      for (let k = -1; k < ranges.length; k++) {
        const prev = ranges[k];
        const next = ranges[k + 1];
        const between = this.between(prev, next);
        if (between) subtracted.push(between);
      }
      return subtracted;
    },
  
    // extract this range from ranges
    // [0, 8] » [0, 2], [5, 8] = []
    // [8, 20] » [0, 2], [5, 8] = [0, 2], [5, 8]
    // [0, 10] » [5, 10], [15, 20] = [5, 10]
    // [10, 20] » [5, 80], [90, 100] = [5, 70] [80, 90]
    // [10, 20] » [0, 10], [20, 30] = [0, 20]
  
    /** extract the range from many ranges. */
    extract: function (range) {
      const ranges = range instanceof Range ? slice_.call(arguments) : range.slice();
  
      for (let k = 0; k < ranges.length; k++) {
        const prev = ranges[k - 1];
        const next = ranges[k];
  
        let newRange = null;
  
        if (this.below(next)) {
          newRange = new Range(next.start - this.length, next.end - this.length);
        } else if (this.intersects(next)) {
          const subtracted = next.subtract(this);
          if (subtracted.length === 2) {
            // is contained
            newRange = new Range(subtracted[0].start, subtracted[1].end - this.length);
          } else if (subtracted.length === 1) {
            if (next.end > this.end) {
              // second segment kept
              newRange = new Range(subtracted[0].start - this.length, subtracted[0].end - this.length);
            } else if (this.start > next.start) {
              // first segment kept
              newRange = new Range(subtracted[0].start, subtracted[0].end);
            }
          } else {
            // gets eaten
            ranges.splice(k--, 1); // decrease k
          }
        } else {
          ranges.splice(k, 1, next.copy());
        }
  
        if (newRange) {
          if (prev && prev.end === newRange.start) {
            // touches previous
            ranges.splice(k-- - 1, 2, new Range(prev.start, newRange.end)); // decrease k due to splicing
          } else {
            ranges.splice(k, 1, newRange);
          }
        }
      }
  
      return ranges;
    },
  
    // [2,3] » [0,1], [2,3] = [0,1], [2,4]
    // [1,2] » [0,6] = [0,7]
    // [0,1] » [0,1], [5,6] = [0,2], [6,7]
    // [4,6] » [0,1], [5,6] = [0,1], [4,6], [7,8]
  
    // TODO: this can be slightly better. merging can be done in one loop.
  
    /** insert the range in many ranges. */
    insert: function (range) {
      const ranges = range instanceof Range ? slice_.call(arguments) : range.slice();
  
      for (let k = 0; k < ranges.length; k++) {
        const next = ranges[k];
  
        if (this.start >= next.end) {
          ranges.splice(k, 1, next.copy());
        } else if (this.start > next.start && this.start < next.end) {
          ranges.splice(k, 1, new Range(next.start, this.start), new Range(this.start, next.end));
        } else {
          ranges.splice(k, 1, new Range(next.start + this.length, next.end + this.length));
        }
      }
  
      return this.merge(ranges);
    },
  
    // merge this range in ranges
    // [0, 8] » [0, 2], [5, 8] = [0, 8]
    // [8, 20] » [0, 2], [5, 8] = [0, 2], [5, 20]
    // [0, 10] » [5, 10], [15, 20] = [0, 10], [15, 20]
  
    /** merge many ranges to the range. */
    merge: function (range) {
      let ranges;
      if (range instanceof Range) ranges = arguments;else ranges = range;
  
      ranges = map(ranges, function (r) {
        return r.copy();
      });
  
      if (!ranges.length) return [this.copy()];
  
      let k;
      let l;
  
      for (k = -1, l = ranges.length; k < l; k++) {
        const prev = ranges[k];
        const next = ranges[k + 1];
        const between = this.between(prev, next);
  
        if (between) {
          if (!prev && next) {
            if (between.end === next.start) {
              next.update(between.start, next.end);
            } else {
              k++; // increase k since we had a push
              ranges.unshift(between);
            }
          } else if (prev && next) {
            if (prev.end === between.start && between.end === next.start) {
              prev.update(prev.start, next.end);
              ranges.splice(k-- + 1, 1); // remove the next, decrease k since we had a splice
            } else if (prev.end === between.start) {
              prev.update(prev.start, between.end);
            } else if (between.end === next.start) {
              next.update(between.start, next.end);
            } else {
              ranges.splice(k + 1, 0, between);
            }
          } else if (prev && !next) {
            if (prev.end === between.start) {
              prev.update(prev.start, between.end);
            } else {
              k++; // increase k since we had a push
              ranges.push(between);
            }
          }
        }
      }
  
      return ranges;
    },
  
    /** Remove range from many ranges
     - The range(s) you pass is what you remove from */
    remove: function (range) {
      let ranges;
      if (range instanceof Range) ranges = arguments;else ranges = range;
      const result = [];
      for (let i = 0; i < ranges.length; i++) {
        const remaining = ranges[i].subtract(this);
        if (remaining.length) push_.apply(result, remaining);
      }
      return result;
    },
  
    /** Range to an array of indices */
    toIndices: function () {
      const indices = [];
      for (let i = this.start; i < this.end; i++) indices.push(i);
      return indices;
    },
  
    /** Range to a string */
    toString: function () {
      return `${[this.start, this.end]}`;
    }
  });
  
  /**
   * Range from a string
   * @memberof Range
   * @static
   * @param {String} string - The string.
   * @return {Range} The range.
   */
  Range.fromString = function (string) {
    const parts = string.split(',');
    return new Range(+parts[0], +parts[1]);
  };
  
  /**
   * Ranges from an array of indices
   * @memberof Range
   * @static
   * @param {Array} indices - An array of indices.
   * @return {Array} An array of Ranges.
   */
  Range.fromIndices = function (indices) {
    indices.sort(function (a, b) {
      return a > b ? 1 : -1;
    });
  
    const ranges = [];
    let rstart;
    let rend;
  
    for (let i = 0; i < indices.length; i++) {
      rstart = indices[i];
      rend = rstart;
      while (indices[i + 1] - indices[i] === 1) {
        rend = indices[i + 1]; // increment the index if the numbers sequential
        i++;
      }
      ranges.push(new Range(rstart, rend + 1));
    }
  
    return ranges;
  };
  
  /**
   * A function that returns a range.
   * @function
   * @see Range
   * @param {Number} start The start of the range.
   * @param {Number} end The end of the range.
   */
  module.exports = Range;
  
  },{"../../libs/prime":202,"mout/array/map":442}],206:[function(require,module,exports){
  /*
  attributes
  */
  'use strict';
  
  const $ = require('./base');
  
  const trim = require('mout/string/trim');
  const forEach = require('mout/array/forEach');
  const filter = require('mout/array/filter');
  const indexOf = require('mout/array/indexOf');
  
  // attributes
  
  $.implement({
    setAttribute: function (name, value) {
      return this.forEach(function (node) {
        node.setAttribute(name, value);
      });
    },
  
    getAttribute: function (name) {
      const attr = this[0].getAttributeNode(name);
      return attr && attr.specified ? attr.value : null;
    },
  
    hasAttribute: function (name) {
      const node = this[0];
      if (node.hasAttribute) return node.hasAttribute(name);
      const attr = node.getAttributeNode(name);
      return !!(attr && attr.specified);
    },
  
    removeAttribute: function (name) {
      return this.forEach(function (node) {
        const attr = node.getAttributeNode(name);
        if (attr) node.removeAttributeNode(attr);
      });
    }
  });
  
  const accessors = {};
  
  forEach(['type', 'value', 'name', 'href', 'title', 'id'], function (name) {
    accessors[name] = function (value) {
      return value !== undefined ? this.forEach(function (node) {
        node[name] = value;
      }) : this[0][name];
    };
  });
  
  // booleans
  
  forEach(['checked', 'disabled', 'selected'], function (name) {
    accessors[name] = function (value) {
      return value !== undefined ? this.forEach(function (node) {
        node[name] = !!value;
      }) : !!this[0][name];
    };
  });
  
  // className
  
  const classes = function (className) {
    const classNames = trim(className).replace(/\s+/g, ' ').split(' ');
    const uniques = {};
  
    return filter(classNames, function (innerClassName) {
      if (innerClassName !== '' && !uniques[innerClassName]) {
        return uniques[innerClassName] = innerClassName;
      }
      return false;
    }).sort();
  };
  
  accessors.className = function (className) {
    return className !== undefined ? this.forEach(function (node) {
      node.className = classes(className).join(' ');
    }) : classes(this[0].className).join(' ');
  };
  
  // attribute
  
  $.implement({
    attribute: function (name, value) {
      const accessor = accessors[name];
      let ret;
      if (accessor) ret = accessor.call(this, value);
      if (value) ret = this.setAttribute(name, value);
      if (value === null) ret = this.removeAttribute(name);
      if (value === undefined) ret = this.getAttribute(name);
  
      return ret;
    }
  });
  
  $.implement(accessors);
  
  // shortcuts
  
  $.implement({
    check: function () {
      return this.checked(true);
    },
  
    uncheck: function () {
      return this.checked(false);
    },
  
    disable: function () {
      return this.disabled(true);
    },
  
    enable: function () {
      return this.disabled(false);
    },
  
    select: function () {
      return this.selected(true);
    },
  
    deselect: function () {
      return this.selected(false);
    }
  });
  
  // classNames, has / add / remove Class
  
  $.implement({
    classNames: function () {
      return classes(this[0].className);
    },
  
    hasClass: function (className) {
      return indexOf(this.classNames(), className) > -1;
    },
  
    addClass: function (className) {
      return this.forEach(function (node) {
        const nodeClassName = node.className;
        const classNames = classes(`${nodeClassName} ${className}`).join(' ');
        if (nodeClassName !== classNames) node.className = classNames;
      });
    },
  
    removeClass: function (className) {
      return this.forEach(function (node) {
        const classNames = classes(node.className);
        forEach(classes(className), function (innerClassName) {
          const index = indexOf(classNames, innerClassName);
          if (index > -1) classNames.splice(index, 1);
        });
        node.className = classNames.join(' ');
      });
    },
  
    toggleClass: function (className, force) {
      const add = force !== undefined ? force : !this.hasClass(className);
      if (add) this.addClass(className);else this.removeClass(className);
      return !!add;
    }
  });
  
  // toString
  
  $.prototype.toString = function () {
    const tag = this.tag();
    const id = this.id();
    const innerClasses = this.classNames();
  
    let str = tag;
    if (id) str += `#${id}`;
    if (innerClasses.length) str += `.${innerClasses.join('.')}`;
    return str;
  };
  
  const textProperty = document.createElement('div').textContent === null ? 'innerText' : 'textContent';
  
  // tag, html, text, data
  
  $.implement({
    tag: function () {
      return this[0].tagName.toLowerCase();
    },
  
    html: function (html) {
      return html !== undefined ? this.forEach(function (node) {
        node.innerHTML = html;
      }) : this[0].innerHTML;
    },
  
    text: function (text) {
      return text !== undefined ? this.forEach(function (node) {
        node[textProperty] = text;
      }) : this[0][textProperty];
    },
  
    data: function (key, value) {
      switch (value) {
        case undefined:
          return this.getAttribute(`data-${key}`);
        case null:
          return this.removeAttribute(`data-${key}`);
        default:
          return this.setAttribute(`data-${key}`, value);
      }
    }
  });
  
  module.exports = $;
  
  },{"./base":207,"mout/array/filter":438,"mout/array/forEach":439,"mout/array/indexOf":440,"mout/string/trim":477}],207:[function(require,module,exports){
  (function (global){
  /*
  elements
  */'use strict';
  
  const prime = require('../../libs/prime');
  
  const forEach = require('mout/array/forEach');
  const map = require('mout/array/map');
  const filter = require('mout/array/filter');
  const every = require('mout/array/every');
  const some = require('mout/array/some');
  
  // uniqueID
  const doc = global.document || {};
  let index = 0;
  const __dc = doc.__counter;
  const counter = doc.__counter = (__dc ? parseInt(__dc, 36) + 1 : 0).toString(36);
  const key = `uid:${counter}`;
  
  const uniqueID = function (n) {
    if (n === window) return 'window';
    if (n === document) return 'document';
    if (n === document.documentElement) return 'html';
    return n[key] || (n[key] = (index++).toString(36));
  };
  
  const instances = new WeakMap();
  
  // elements prime
  
  const $ = prime({
    constructor: function $(n, context) {
      // TODO: Fix this to use dependency injection and/or actual classes
      // eslint-disable-next-line no-use-before-define
      if (!n) return this && this.constructor === $ ? new Elements() : null;
  
      let self;
      let uid;
  
      // eslint-disable-next-line no-use-before-define
      if (n.constructor !== Elements) {
        // eslint-disable-next-line no-use-before-define
        self = new Elements();
  
        if (typeof n === 'string') {
          if (!self.search) return null;
          self[self.length++] = context || document;
          return self.search(n);
        }
  
        if (n.nodeType || n === window) {
          self[self.length++] = n;
        } else if (n.length) {
          // this could be an array, or any object with a length attribute,
          // including another instance of elements from another interface.
  
          const uniques = {};
  
          for (let i = 0, l = n.length; i < l; i++) {
            // perform elements flattening
            const nodes = $(n[i], context);
            if (nodes && nodes.length) {
              for (let j = 0, k = nodes.length; j < k; j++) {
                const node = nodes[j];
                uid = uniqueID(node);
                if (!uniques[uid]) {
                  self[self.length++] = node;
                  uniques[uid] = true;
                }
              }
            }
          }
        }
      } else {
        self = n;
      }
  
      if (!self.length) return null;
  
      // when length is 1 always use the same elements instance
  
      if (self.length === 1) {
        const instance = instances.get(self[0]);
        if (instance) {
          return instance;
        }
        instances.set(self[0], self);
      }
  
      return self;
    }
  });
  
  const Elements = prime({
    inherits: $,
  
    constructor: function Elements() {
      this.length = 0;
    },
  
    unlink: function () {
      return this.map(function (node) {
        instances.delete(node);
        return node;
      });
    },
  
    // methods
  
    forEach: function (method, context) {
      forEach(this, method, context);
      return this;
    },
  
    map: function (method, context) {
      return map(this, method, context);
    },
  
    filter: function (method, context) {
      return filter(this, method, context);
    },
  
    every: function (method, context) {
      return every(this, method, context);
    },
  
    some: function (method, context) {
      return some(this, method, context);
    }
  });
  
  module.exports = $;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../libs/prime":202,"mout/array/every":437,"mout/array/filter":438,"mout/array/forEach":439,"mout/array/map":442,"mout/array/some":445}],208:[function(require,module,exports){
  /*
  delegation
  */'use strict';
  
  const Map = require('../../libs/prime/map');
  
  const $ = require('./events');
  require('./traversal');
  
  $.implement({
    delegate: function (event, selector, handle) {
      return this.forEach(function (node) {
        const self = $(node);
  
        const delegation = self._delegation || (self._delegation = {});
        const events = delegation[event] || (delegation[event] = {});
        const map = events[selector] || (events[selector] = new Map());
  
        if (map.get(handle)) return;
  
        const action = function (e) {
          const target = $(e.target || e.srcElement);
          const match = target.matches(selector) ? target : target.parent(selector);
  
          let res;
  
          if (match) res = handle.call(self, e, match);
  
          return res;
        };
  
        map.set(handle, action);
  
        self.on(event, action);
      });
    },
  
    undelegate: function (event, selector, handle) {
      return this.forEach(function (node) {
        const self = $(node);
        const delegation = self._delegation;
  
        if (!delegation) {
          return;
        }
  
        const events = delegation[event];
        if (!events) {
          return;
        }
  
        const map = events[selector];
        if (!map) {
          return;
        }
  
        const action = map.get(handle);
  
        if (action) {
          self.off(event, action);
          map.remove(action);
  
          // if there are no more handles in a given selector, delete it
          if (!map.count()) delete events[selector];
          // var evc = evd = 0, x
          let e1 = true;
          let e2 = true;
  
          for (const x in events) {
            if (!events.hasOwnProperty(x)) continue;
            e1 = false;
            break;
          }
          // if no more selectors in a given event type, delete it
          if (e1) delete delegation[event];
          for (const x in delegation) {
            if (!delegation.hasOwnProperty(x)) continue;
            e2 = false;
            break;
          }
          // if there are no more delegation events in the element, delete the _delegation object
          if (e2) delete self._delegation;
        }
      });
    }
  });
  
  module.exports = $;
  
  },{"../../libs/prime/map":203,"./events":209,"./traversal":212}],209:[function(require,module,exports){
  /*
  events
  */'use strict';
  
  const Emitter = require('../../libs/prime/emitter');
  
  const $ = require('./base');
  
  $.implement({
    on: function (event, handle, useCapture) {
      return this.forEach(function (node) {
        const self = $(node);
  
        const internalEvent = event + (useCapture ? ':capture' : '');
  
        Emitter.prototype.on.call(self, internalEvent, handle);
  
        const domListeners = self._domListeners || (self._domListeners = {});
        if (!domListeners[internalEvent]) {
          const listener = function (e) {
            Emitter.prototype.emit.call(self, internalEvent, e || window.event, Emitter.EMIT_SYNC);
          };
          node.addEventListener(event, listener, useCapture || false);
          domListeners[internalEvent] = listener;
        }
      });
    },
  
    off: function (event, handle, useCapture) {
      return this.forEach(function (node) {
        const self = $(node);
  
        const internalEvent = event + (useCapture ? ':capture' : '');
  
        const domListeners = self._domListeners;
        const listeners = self._listeners;
  
        if (domListeners && listeners) {
          const domEvent = domListeners[internalEvent];
  
          if (domEvent) {
            Emitter.prototype.off.call(self, internalEvent, handle);
  
            if (!self._listeners || !self._listeners[event]) {
              node.removeEventListener(event, domEvent, false);
              delete domListeners[event];
  
              for (const l in domListeners) {
                if (domListeners.hasOwnProperty(l)) {
                  return;
                }
              }
              delete self._domListeners;
            }
          }
        }
      });
    },
  
    emit: function () {
      const args = arguments;
      return this.forEach(function (node) {
        Emitter.prototype.emit.apply($(node), args);
      });
    }
  });
  
  module.exports = $;
  
  },{"../../libs/prime/emitter":201,"./base":207}],210:[function(require,module,exports){
  /*
  elements
  */'use strict';
  
  const $ = require('./base');
  require('./attributes');
  require('./events');
  require('./insertion');
  require('./traversal');
  require('./delegation');
  
  module.exports = $;
  
  },{"./attributes":206,"./base":207,"./delegation":208,"./events":209,"./insertion":211,"./traversal":212}],211:[function(require,module,exports){
  /*
  insertion
  */'use strict';
  
  const $ = require('./base');
  
  // base insertion
  
  $.implement({
    appendChild: function (child) {
      this[0].appendChild($(child)[0]);
      return this;
    },
  
    insertBefore: function (child, ref) {
      this[0].insertBefore($(child)[0], $(ref)[0]);
      return this;
    },
  
    removeChild: function (child) {
      this[0].removeChild($(child)[0]);
      return this;
    },
  
    replaceChild: function (child, ref) {
      this[0].replaceChild($(child)[0], $(ref)[0]);
      return this;
    }
  });
  
  // before, after, bottom, top
  
  $.implement({
    before: function (element) {
      const innerElement = $(element)[0];
      const parent = innerElement.parentNode;
      if (parent) {
        this.forEach(function (node) {
          parent.insertBefore(node, innerElement);
        });
      }
      return this;
    },
  
    after: function (element) {
      const innerElement = $(element)[0];
      const parent = innerElement.parentNode;
      if (parent) {
        this.forEach(function (node) {
          parent.insertBefore(node, innerElement.nextSibling);
        });
      }
      return this;
    },
  
    bottom: function (element) {
      const innerElement = $(element)[0];
      return this.forEach(function (node) {
        innerElement.appendChild(node);
      });
    },
  
    top: function (element) {
      const innerElement = $(element)[0];
      return this.forEach(function (node) {
        innerElement.insertBefore(node, innerElement.firstChild);
      });
    }
  });
  
  // insert, replace
  
  $.implement({
    insert: $.prototype.bottom,
  
    remove: function () {
      return this.forEach(function (node) {
        const parent = node.parentNode;
        if (parent) parent.removeChild(node);
      });
    },
  
    replace: function (element) {
      const innerElement = $(element)[0];
      innerElement.parentNode.replaceChild(this[0], innerElement);
      return this;
    }
  });
  
  module.exports = $;
  
  },{"./base":207}],212:[function(require,module,exports){
  /*
  traversal
  */
  'use strict';
  
  var _slick = require('slick');
  
  var _slick2 = _interopRequireDefault(_slick);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  const map = require('mout/array/map');
  
  const $ = require('./base');
  
  const gen = function (combinator, expression) {
    return map(_slick2.default.parse(expression || '*'), function (part) {
      return `${combinator} ${part}`;
    }).join(', ');
  };
  
  const push_ = Array.prototype.push;
  
  $.implement({
    search: function (expression) {
      if (this.length === 1) return $(_slick2.default.search(expression, this[0], new $()));
  
      let buffer = [];
      for (let i = 0; i < this.length; i++) {
        const node = this[i];
        push_.apply(buffer, _slick2.default.search(expression, node));
      }
      buffer = $(buffer);
      return buffer && buffer.sort();
    },
  
    find: function (expression) {
      if (this.length === 1) return $(_slick2.default.find(expression, this[0]));
  
      for (let i = 0; i < this.length; i++) {
        const node = this[i];
        const found = _slick2.default.find(expression, node);
        if (found) return $(found);
      }
  
      return null;
    },
  
    sort: function () {
      return _slick2.default.sort(this);
    },
  
    matches: function (expression) {
      return _slick2.default.matches(this[0], expression);
    },
  
    contains: function (node) {
      return _slick2.default.contains(this[0], node);
    },
  
    nextSiblings: function (expression) {
      return this.search(gen('~', expression));
    },
  
    nextSibling: function (expression) {
      return this.find(gen('+', expression));
    },
  
    previousSiblings: function (expression) {
      return this.search(gen('!~', expression));
    },
  
    previousSibling: function (expression) {
      return this.find(gen('!+', expression));
    },
  
    children: function (expression) {
      return this.search(gen('>', expression));
    },
  
    firstChild: function (expression) {
      return this.find(gen('^', expression));
    },
  
    lastChild: function (expression) {
      return this.find(gen('!^', expression));
    },
  
    parent: function (expression) {
      const buffer = [];
      for (let i = 0; i < this.length; i++) {
        let node = this[i].parentNode;
        while (node && node !== document) {
          if (!expression || _slick2.default.matches(node, expression)) {
            buffer.push(node);
            break;
          }
  
          node = node.parentNode;
        }
      }
      return $(buffer);
    },
  
    parents: function (expression) {
      const buffer = [];
      for (let i = 0; i < this.length; i++) {
        let node = this[i];
        while (node && node !== document) {
          if (!expression || _slick2.default.matches(node, expression)) buffer.push(node);
          node = node.parentNode;
        }
      }
      return $(buffer);
    }
  });
  
  module.exports = $;
  
  },{"./base":207,"mout/array/map":442,"slick":535}],213:[function(require,module,exports){
  'use strict';
  
  const $ = require('../spotify-elements');
  const live = require('../live');
  const liburi = require('spotify-liburi');
  const surroundMessages = require('../surround-messages');
  
  const center = require('./center');
  
  const IS_ADDED_PROPERTY = 'added';
  const BUTTON_SELECTOR = '[data-button=add]';
  const JUSTCHANGED_CLASSNAME = 'just-changed';
  const ADDED_CLASSNAME = 'added';
  const INTERACTION_INTENT_ATTR = 'data-interaction-intent';
  const INTERACTION_INTENT_SAVE = 'save';
  const INTERACTION_INTENT_ADD = 'add';
  const INTERACTION_INTENT_REMOVE = 'remove';
  const INTERACTION_INTENT_FOLLOW = 'follow';
  const INTERACTION_INTENT_UNFOLLOW = 'unfollow';
  const INTERACTION_TARGET_ATTR = 'data-interaction-target';
  const TOGGLE_FOLLOW_STATE_BUTTON = 'toggle-follow-state-button';
  const SAVE_REMOVE_BUTTON = 'save-remove-button';
  
  const changeHandlers = {};
  let isAttached = false;
  
  function getAddedClass(node) {
    const addedClassNode = node[0].closest('data-class-added');
    return addedClassNode ? addedClassNode.getAttribute('data-class-added') : ADDED_CLASSNAME;
  }
  
  function getURIFromNode(node) {
    const stateNode = node[0].closest('[data-uri]');
    if (!stateNode) {
      return null;
    }
    return stateNode.getAttribute('data-uri');
  }
  
  const eventHandlers = {
    click: function (event, node) {
      const stateNode = $(node[0].closest('[data-uri]'));

      if (!stateNode) return;
  
      let uri = stateNode.data('uri');
      if (!uri) return;
  
      // Rewrite collection-(album|artist) to vanilla counterparts.
      const uriObject = liburi.from(uri);
      if (uriObject) {
        if (uriObject.type === liburi.Type.COLLECTION_ALBUM) {
          uri = liburi.albumURI(uriObject.id).toURI();
        } else if (uriObject.type === liburi.Type.COLLECTION_ARTIST) {
          uri = liburi.artistURI(uriObject.id).toURI();
        }
      }
  
      const addedClass = getAddedClass(node);
      const shouldBeAdded = !stateNode.hasClass(addedClass);
      const data = {
        added: shouldBeAdded
      };
  
      node.addClass(JUSTCHANGED_CLASSNAME);
  
      // using capture=true to handle buttons with inner divs (used for changing button text)
      node.on('mouseout', function mouseoutHandler(innerEvent) {
        if (innerEvent.target !== node[0]) return;
        node.off('mouseout', mouseoutHandler);
        node.removeClass(JUSTCHANGED_CLASSNAME);
      }, true);
  
      // Publish will trigger the update event, which will then update the UI
      live(uri).publish(data);
  
      // Inform Surround (for More-Like-This)
      surroundMessages.dispatchUriAdded(uri, data);
    },
  
    changeHandler: function (actions, key, value, uri) {
      if (key === IS_ADDED_PROPERTY) {
        const numAffectedButtons = actions.setStateForURI(uri, value);
        if (numAffectedButtons === 0) {
          live(uri).off('update', changeHandlers[uri]);
          delete changeHandlers[uri];
        }
      }
    }
  };
  
  const actions = {
    setStateForURI: function (uri, isAdded) {
      let numAffectedButtons = 0;
  
      const nodes = $(document).search(BUTTON_SELECTOR);
      if (!nodes) return numAffectedButtons;
  
      for (let i = 0, l = nodes.length; i < l; i++) {
        const node = $(nodes[i]);
        const buttonURI = getURIFromNode(node);
        if (uri === buttonURI) {
          numAffectedButtons++;
          actions.setStateForButtonNode(node, isAdded);
        }
      }
  
      return numAffectedButtons;
    },
  
    setStateForButtonNode: function (node, isAdded) {
      const stateNode = $(node[0].closest('[data-uri]'));
      if (!stateNode) return;
  
      const addedClass = getAddedClass(node);
      actions.setVisualState(isAdded, stateNode, node, addedClass);
    },
  
    setVisualState: function (isAdded, node, button, className) {
      if (isAdded) {
        if (!node.hasClass(className)) node.addClass(className);
        const tooltipRemove = button.data('tooltip-remove');
        if (tooltipRemove) {
          button.setAttribute('data-tooltip', tooltipRemove);
          button.setAttribute('aria-label', tooltipRemove);
        }
        const interactionIntent = button.getAttribute(INTERACTION_TARGET_ATTR) === TOGGLE_FOLLOW_STATE_BUTTON ? INTERACTION_INTENT_UNFOLLOW : INTERACTION_INTENT_REMOVE;
        button.setAttribute(INTERACTION_INTENT_ATTR, interactionIntent);
      } else {
        if (node.hasClass(className)) node.removeClass(className);
        const tooltipAdd = button.data('tooltip-add');
        if (tooltipAdd) {
          button.setAttribute('data-tooltip', tooltipAdd);
          button.setAttribute('aria-label', tooltipAdd);
        }
        const interactionTarget = button.getAttribute(INTERACTION_TARGET_ATTR);
        switch (interactionTarget) {
          case SAVE_REMOVE_BUTTON:
            button.setAttribute(INTERACTION_INTENT_ATTR, INTERACTION_INTENT_SAVE);
            break;
  
          case TOGGLE_FOLLOW_STATE_BUTTON:
            button.setAttribute(INTERACTION_INTENT_ATTR, INTERACTION_INTENT_FOLLOW);
            break;
  
          default:
            button.setAttribute(INTERACTION_INTENT_ATTR, INTERACTION_INTENT_ADD);
            break;
        }
      }
    },
  
    addListenerForNode: function (node) {
      const uri = getURIFromNode(node);
  
      // If we have already added a change handler for this URI,
      // we don't want to add another one, so we cancel early.
      // Before cancelling though, we should update button states
      // with the current state from the live model, since there
      // might be new buttons in the DOM.
      if (changeHandlers[uri]) {
        live(uri).get('added', function (error, isAdded) {
          if (error) throw error;
          actions.setStateForButtonNode(node, isAdded);
        }, live.ASAP);
        return;
      }
  
      changeHandlers[uri] = function (properties) {
        for (const key in properties) {
          if (!properties.hasOwnProperty(key)) continue;
          const value = properties[key];
          eventHandlers.changeHandler(actions, key, value, uri);
        }
      };
  
      const model = live(uri);
  
      // Get the initial state and update the button
      model.get('added', function (error, isAdded) {
        if (error) throw error;
        actions.setStateForButtonNode(node, isAdded);
  
        // When the initial state is known, we listen for updates
        // so we can update all buttons for this URI later.
        model.on('update', changeHandlers[uri]);
      }, live.ASAP);
    }
  };
  
  const update = exports.update = function update(node, nodes) {
    if (!isAttached) return;
  
    // If the node passed in is a button node, just add listeners for that
    if (node && $(node).matches(BUTTON_SELECTOR)) {
      actions.addListenerForNode($(node));
  
      // Otherwise search all the children of the node (or the document
      // if node is not provided) for buttons.
    } else {
      const innerNodes = ($(nodes) || $(node) || $(document)).search(BUTTON_SELECTOR);
      if (!innerNodes) return;
  
      for (let i = 0, l = innerNodes.length; i < l; i++) {
        actions.addListenerForNode($(innerNodes[i]));
      }
    }
  };
  
  const handleScrollShow = function (data) {
    update(data.pageNode, data.nodes);
  };
  
  // Export for testing
  exports._classNames = {
    ADDED: ADDED_CLASSNAME,
    JUSTCHANGED: JUSTCHANGED_CLASSNAME
  };
  
  exports.attach = function attach() {
    if (isAttached) return;
    isAttached = true;
  
    $(document).delegate('click', BUTTON_SELECTOR, eventHandlers.click);
  
    center.on('scroll-show-before', handleScrollShow);
  };
  
  exports.detach = function detach() {
    if (!isAttached) return;
    isAttached = false;
  
    $(document).undelegate('click', BUTTON_SELECTOR, eventHandlers.click);
    center.off('scroll-show-before', handleScrollShow);
  };
  
  },{"../live":172,"../spotify-elements":210,"../surround-messages":315,"./center":221,"spotify-liburi":553}],214:[function(require,module,exports){
  'use strict';
  
  const $ = require('../spotify-elements');
  const Navigator = require('../navigation/navigator');
  
  const SELECTOR = 'a';
  
  const nav = new Navigator();
  
  const handleAnchorClick = function (event, node) {
    const anchor = node[0];
    const href = anchor.getAttribute('href');
    if (!href || href[0] === '#') {
      return;
    }
    event.preventDefault();
    nav.openURI(href);
  };
  
  let isAttached = false;
  
  exports.attach = function attach() {
    if (isAttached) {
      return;
    }
    isAttached = true;
    $(document).delegate('click', SELECTOR, handleAnchorClick);
  };
  
  exports.detach = function detach() {
    if (!isAttached) {
      return;
    }
    isAttached = false;
    $(document).undelegate('click', SELECTOR, handleAnchorClick);
  };
  
  exports.update = function () {};
  
  },{"../navigation/navigator":191,"../spotify-elements":210}],215:[function(require,module,exports){
  'use strict';
  
  const modules = [require('./touch'), require('./pressed'), require('./loading'), require('./remove')];
  
  exports.attach = function () {
    modules.forEach(function (module) {
      module.attach();
    });
  };
  
  exports.detach = function () {
    modules.forEach(function (module) {
      module.detach();
    });
  };
  
  exports.update = function () {
    modules.forEach(function (module) {
      module.update();
    });
  };
  
  },{"./loading":216,"./pressed":217,"./remove":218,"./touch":219}],216:[function(require,module,exports){
  'use strict';
  
  const CARD = '.card';
  const IMAGE_LOADING_TRANSITION = 1000;
  
  let currentCards = [];
  
  function addCardImage(card) {
    let imageUri;
    const element = card.querySelector('[data-image-url]');
    if (element) {
      imageUri = element.getAttribute('data-image-url');
    }
  
    if (imageUri) {
      const onImageLoad = function (t) {
        return () => {
          card.classList.add('image-loaded');
          clearTimeout(t);
        };
      };
  
      const onImageError = function () {
        card.classList.add('image-error');
      };
  
      const timeout = setTimeout(function () {
        card.classList.add('image-delayed');
      }, IMAGE_LOADING_TRANSITION);
  
      const image = new Image();
      image.addEventListener('load', onImageLoad(timeout));
      image.addEventListener('error', onImageError);
      image.src = imageUri;
    }
  }
  
  let isAttached = false;
  
  exports.attach = function () {
    if (isAttached) return;
    isAttached = true;
  };
  
  exports.detach = function () {
    if (!isAttached) return;
    isAttached = false;
  
    currentCards = [];
  };
  
  exports.update = function () {
    if (!isAttached) return;
  
    // Forget orphaned cards
    currentCards = currentCards.filter(function (card) {
      return card.closest('body');
    });
  
    const cards = document.querySelectorAll(CARD);
    cards.forEach(card => {
      const known = currentCards.indexOf(card) !== -1;
      if (known) {
        return;
      }
      currentCards.push(card);
      addCardImage(card);
    });
  };
  
  exports._reset = function () {
    currentCards = [];
  };
  
  },{}],217:[function(require,module,exports){
  'use strict';
  
  const $ = require('../../spotify-elements');
  const CARD = '[data-card-pressed-container]';
  const TRIGGER = '[data-card-trigger-pressed-state]';
  
  const doc = $(document);
  let pressedElement;
  
  function handleMouseDown(event, element) {
    // The card shouldn't animate when pressing buttons inside of it.
    if (event.target.nodeName.toLowerCase() === 'button') {
      return;
    }
    // The card shouldn't animate when right clicking it
    if (event.button === 2) {
      return;
    }
    element[0].closest(CARD).classList.add('pressed');
    pressedElement = element[0];
  }
  
  function handleMouseUp(event, element) {
    element[0].closest(CARD).classList.remove('pressed');
  
    if (pressedElement === element[0]) {
      pressedElement = null;
    }
  }
  
  function handleDragEnd(event, element) {
    element[0].closest(CARD).classList.remove('pressed');
  
    if (pressedElement === element[0]) {
      pressedElement = null;
    }
  }
  
  function handleDocumentMouseUp() {
    if (pressedElement) {
      pressedElement.closest(CARD).classList.remove('pressed');
      pressedElement = null;
    }
  }
  
  let isAttached = false;
  
  exports.attach = function () {
    if (isAttached) return;
    isAttached = true;
  
    doc.delegate('mousedown', TRIGGER, handleMouseDown);
    doc.delegate('mouseup', TRIGGER, handleMouseUp);
    doc.delegate('dragend', TRIGGER, handleDragEnd);
    doc.on('mouseup', handleDocumentMouseUp);
  };
  
  exports.detach = function () {
    if (!isAttached) return;
    isAttached = false;
  
    doc.undelegate('mousedown', TRIGGER, handleMouseDown);
    doc.undelegate('mouseup', TRIGGER, handleMouseUp);
    doc.undelegate('dragend', TRIGGER, handleDragEnd);
    doc.off('mouseup', handleDocumentMouseUp);
  };
  
  exports.update = function () {};
  
  },{"../../spotify-elements":210}],218:[function(require,module,exports){
  'use strict';
  
  const center = require('../center');
  const BUTTON = '[data-card-remove-button]';
  
  function handleClick(event) {
    const element = event.target;
    if (!element.matches(BUTTON)) {
      return;
    }
    const uriElement = element.closest('[data-uri]');
    if (!uriElement) {
      return;
    }
    const uri = uriElement.getAttribute('data-uri');
    center.emit('card-remove', { uri, element });
  
    // Make sure CEF doesn't follow the link that the button is contained in.
    event.preventDefault();
    // Also make sure spotify-events/anchor doesn't act upon it and follows the link.
    event.stopPropagation();
  }
  
  let isAttached = false;
  
  exports.attach = function () {
    if (isAttached) return;
    isAttached = true;
    // We need to get this before spotify-events/anchor.
    document.addEventListener('click', handleClick, true);
  };
  
  exports.detach = function () {
    if (!isAttached) return;
    isAttached = false;
    document.removeEventListener('click', handleClick, true);
  };
  
  exports.update = function () {};
  
  },{"../center":221}],219:[function(require,module,exports){
  'use strict';
  
  const $ = require('../../spotify-elements');
  const CARD = '.card';
  const IMAGE_HIT_AREA = '.card-image-hit-area';
  
  const doc = $(document);
  let hoverActive = false;
  
  let touchArray = [];
  const addEvent = function (touchEvent) {
    touchArray.push(touchEvent);
  };
  
  function getCards() {
    return doc.search(CARD);
  }
  
  function isCard(element) {
    return typeof element.closest === 'function' && !!element.closest(CARD);
  }
  
  function handleEvent(e) {
    const target = e.target;
    if (isCard(target)) {
      switch (e.type) {
        case 'touchstart':
          addEvent(e);
          if (hoverActive) {
            disableHover();
          }
          break;
  
        case 'touchmove':
          addEvent(e);
          break;
  
        case 'touchend':
          if (target && touchArray.length === 1 && touchArray[0].type === 'touchstart') {
            e.preventDefault();
            target.click();
          }
          touchArray = [];
          break;
  
        case 'contextmenu':
          addEvent(e);
          break;
  
        case 'mousemove':
          if (!hoverActive) {
            enableHover();
          }
          break;
  
        default:
          break;
      }
    }
  }
  
  function disableHover() {
    const cards = getCards();
    if (cards) {
      cards.forEach(function (card) {
        const hitArea = $(card).find(IMAGE_HIT_AREA);
        if (hitArea) {
          hitArea.addClass('no-hover');
        }
      });
      hoverActive = false;
    }
  }
  
  function enableHover() {
    const cards = getCards();
    if (cards) {
      cards.forEach(function (card) {
        const hitArea = $(card).find(IMAGE_HIT_AREA);
        if (hitArea) {
          hitArea.removeClass('no-hover');
        }
      });
      hoverActive = true;
    }
  }
  
  let isAttached = false;
  
  exports.attach = function () {
    if (isAttached) return;
    isAttached = true;
  
    doc.on('touchstart', handleEvent);
    doc.on('touchmove', handleEvent);
    doc.on('touchend', handleEvent);
    doc.on('mousemove', handleEvent);
    doc.on('contextmenu', handleEvent);
  };
  
  exports.detach = function () {
    if (!isAttached) return;
    isAttached = false;
  
    doc.off('touchstart', handleEvent);
    doc.off('touchmove', handleEvent);
    doc.off('touchend', handleEvent);
    doc.off('mousemove', handleEvent);
    doc.off('contextmenu', handleEvent);
  };
  
  exports.update = function () {};
  
  exports._reset = function () {
    hoverActive = true;
  };
  
  },{"../../spotify-elements":210}],221:[function(require,module,exports){
  /*
  Event Center
  */'use strict';
  
  const Emitter = require('../../libs/prime/emitter');
  
  module.exports = new Emitter();
  
  },{"../../libs/prime/emitter":201}],222:[function(require,module,exports){
  (function (global){
  'use strict';
  
  const $ = require('../spotify-elements');
  const live = require('../live');
  const selection = require('./selection');
  const wrappedUriUtils = require('../live-wrapped-uri');
  const cosmos = require('spotify-cosmos-api');
  const liburi = require('spotify-liburi');
  
  // getMetadataForUris can be set by the app.
  let getMetadataForUris;
  
  const CONTEXTMENU = ['a[data-uri]', '[data-contextmenu]'].join(',');
  
  const makeContextMenuData = function (selectionContexts, primaryIndex, uris, x, y) {
    const contexts = [];
  
    selectionContexts.forEach(selectionContext => {
      if (selectionContext !== undefined) {
        const keys = selectionContext.keys || [];
        const indices = selectionContext.indices || [];
        let newPrimaryIndex = primaryIndex || indices[0];
        newPrimaryIndex = newPrimaryIndex === undefined ? null : newPrimaryIndex;
  
        const contextUri = selectionContext.uri;
  
        const contextData = {
          uri: contextUri,
          keys: keys,
          indices: indices,
          primaryIndex: newPrimaryIndex
        };
  
        const origin = selectionContext.origin;
        if (origin) {
          if (newPrimaryIndex !== null) {
            const wrappedList = live(contextUri).get('rows');
            const originList = live(origin.uri).get('rows');
            if (wrappedList && originList) {
              const key = wrappedList.keys[newPrimaryIndex];
              origin.primaryIndex = originList.indexOf(key);
            }
          }
          contextData.origin = origin;
        }
        contexts.push(contextData);
      }
    });
    return {
      contexts: contexts,
      x: x,
      y: y,
      uris: uris
    };
  };
  
  const handleContextMenuActiveClass = function (node) {
    const subscribedParentNodes = node.parents('[data-contextmenu-spy]');
    const collection = $([node, subscribedParentNodes]);
    collection.addClass('contextmenu-active');
  
    // when a user right clicks while a context menu is already open,
    // there's a race condition because of the delay in the cosmos message.
    // the second right click both adds and then removes the active class
    // on the new target
    // right click -> post message -> addClass -> subscribe -> receive message -> removeClass
    // this can be avoided by pushing the subscription on the event loop
    // so that it happens after the message is received.
    // right click -> post message -> addClass -> receive message -> subscribe
    // doesn't work in the case of right click on the same target twice
    setTimeout(() => {
      const sub = cosmos.resolver.subscribe({
        url: 'sp://messages/v1/context_menu_state'
      }, function (error, response) {
        if (!error && response.getJSONBody().state === 'hide') {
          collection.removeClass('contextmenu-active');
          sub.cancel();
        }
      });
    }, 0);
  };
  
  const showContextMenu = function (data) {
    const appUri = global.__spotify.app_uri;
    const isCollectionApp = liburi.from(appUri).id === 'collection';
    data.appURI = appUri;
  
    // State is not set for neither zlink or buddy-list so we need to check
    // if it exists before getting URI
    data.entityURI = live('spotify:application').get('state') ? live('spotify:application').get('state').getURI() : null;
  
    // If getMetadataForUris is set by the app, call it with uris to get metadata.
    if (getMetadataForUris) {
      getMetadataForUris(data.uris, response => {
        data.metadata = response;
        const client = live('spotify:client');
        client.emit('show-context-menu', data);
      });
    } else {
      data.metadata = [];
      data.uris.forEach(uri => {
        // The "Save to Your Library"/"Remove from Your Library" menu item needs to
        // know the "added" state of the items to be able to show the correct string.
        // The collection app doesn't use live to handle the collection state, but it
        // can safely be assumed that what's in the collection app, is in collection.
        const added = isCollectionApp ? true : live(uri).get('added');
        if (added !== undefined) {
          data.metadata.push({
            uri: uri,
            added
          });
        }
      });
  
      const client = live('spotify:client');
      client.emit('show-context-menu', data);
    }
  };
  
  const handleContextMenuEvent = function (event, node) {
    event.preventDefault();
    event.stopPropagation();
  
    const x = event.clientX;
    const y = event.clientY;
  
    handleContextMenuActiveClass(node);
  
    if (node[0].tagName.toLowerCase() === 'a') {
      const uri = node.data('uri');
      if (uri) {
        showContextMenu({
          uris: [uri],
          x: x,
          y: y
        });
      }
    } else {
      const contextNode = node.parent('[data-list]');
  
      const index = +node.data('index');
  
      let isSelected = false;
      const indices = selection.getIndicesPerList();
  
      if (contextNode) {
        isSelected = selection.isNodeSelected(node);
  
        if (!isSelected) {
          selection.clear();
          const contextURI = contextNode.getAttribute('data-uri');
          if (contextURI) {
            selection.add(contextURI, index);
          }
        }
      }
      // The Play Queue is built around the notion that the currently playing
      // track has an index of -1 to make it kinda part of the backing live
      // and selection models, but not quite. Changing that notion seems to
      // require more fundamental changes, so I decided to add another workaround
      // here  ¯\_(ツ)_/¯
      //
      // See KM-13039 for more background.
      const canTrustSelecion = index !== -1;
      const uris = isSelected && canTrustSelecion ? selection.getUris() : [node.data('uri')];
      showContextMenu(makeContextMenuData(indices, index, uris, x, y));
    }
  };
  
  const handleContextMenuButton = function (event, node) {
    let parent;
    let uri = node.data('uri') || (parent = node.parent('[data-uri]')) && parent.data('uri');
    if (!uri) return;
  
    // The context menu can't know what to offer for a custom list.
    // In case of a sorted and/or filtered list, pass the origin list.
    uri = wrappedUriUtils.getOriginUri(uri) || uri;
  
    const contextURI = (parent = node.parent('[data-list][data-uri]')) && parent.data('uri');
    const contextIndex = (parent = node.parent('[data-index]')) && +parent.data('index');
  
    const contextMenuNode = $(node[0].closest('[data-button="contextmenu"]'));
    const relativeTo = contextMenuNode || parent || node;
  
    const rect = relativeTo[0].getBoundingClientRect();
    let x = rect.left + rect.width / 2;
    let y = rect.top + rect.height;
  
    // The coordinate must be within the app viewport
    if (y > window.innerHeight) y = window.innerHeight - 1;
    if (x > window.innerWidth) x = window.innerWidth - 1;
  
    const indices = contextIndex === null || contextIndex === undefined ? null : [contextIndex];
  
    let context;
    if (contextURI) {
      context = {
        uri: contextURI
      };
  
      if (indices) {
        context.indices = indices;
  
        const liveList = live(contextURI).get('rows');
        context.keys = liveList ? indices.map(function (selectedIndex) {
          return liveList.keys[selectedIndex];
        }) : [];
      }
  
      // Add originList
      const origin = wrappedUriUtils.getOriginUri(contextURI);
      if (origin) {
        context.origin = {
          uri: origin
        };
  
        if (indices) {
          const wrappedList = live(contextURI).get('rows');
          const originList = live(origin).get('rows');
          if (wrappedList && originList) {
            const originIndices = indices.map(function (index) {
              const key = wrappedList.keys[index];
              return originList.indexOf(key);
            });
            const originKeys = indices.map(function (index) {
              return wrappedList.keys[index];
            });
            context.origin.indices = originIndices;
            context.origin.keys = originKeys;
          }
        }
      }
    }
    showContextMenu(makeContextMenuData([context], contextIndex, [uri], x, y));
    handleContextMenuActiveClass(contextMenuNode);
  };
  
  let isAttached = false;
  
  exports.attach = function attach() {
    if (isAttached) return;
    isAttached = true;
  
    $(document).delegate('contextmenu', CONTEXTMENU, handleContextMenuEvent);
    $(document).delegate('click', '[data-button=contextmenu]', handleContextMenuButton);
  };
  
  exports.detach = function detach() {
    if (!isAttached) return;
    isAttached = false;
  
    $(document).undelegate('contextmenu', CONTEXTMENU, handleContextMenuEvent);
    $(document).undelegate('click', '[data-button=contextmenu]', handleContextMenuButton);
  };
  
  exports.update = function () {};
  exports.setMetadataRequestFunction = (func = null) => {
    getMetadataForUris = func;
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../live":172,"../live-wrapped-uri":171,"../spotify-elements":210,"./selection":278,"spotify-cosmos-api":543,"spotify-liburi":553}],223:[function(require,module,exports){
  /**
   * @module spotify-events/dragndrop/custom/queue
   * @private
   */
  'use strict';
  
  const hooks = require('../util/hooks');
  const QUEUE_URI = 'spotify:internal:queue:future';
  
  /**
   * Hook handler for validating a list target.
   *
   * @param {Object} result Result object. Setting the `valid` property will
   *     affect whether the drop is allowed.
   * @param {HTMLElement} list A DOM node marked up as a list.
   */
  function onListDropValidation(result, event, list, location, index) {
    const contextUri = list.getAttribute('data-uri');
    if (!contextUri || contextUri !== QUEUE_URI) return;
  
    // The "current" track in the queue is marked up as index -1. Disallow
    // dragging anything to that spot.
    if (index === -1) {
      result.valid = false;
      return;
    }
  }
  
  /**
   * Attach the module.
   */
  function attach() {
    hooks.add('list-drop-validation', onListDropValidation);
  }
  
  /**
   * Detach the module.
   */
  function detach() {
    hooks.remove('list-drop-validation', onListDropValidation);
  }
  
  exports.attach = attach;
  exports.detach = detach;
  
  },{"../util/hooks":232}],224:[function(require,module,exports){
  /**
   * @module spotify-events/dragndrop/custom/rootlist
   * @private
   */
  'use strict';
  
  const $ = require('../../../spotify-elements');
  
  const liburi = require('spotify-liburi');
  const live = require('../../../live');
  const intersection = require('mout/array/intersection');
  
  const hooks = require('../util/hooks');
  const eventModel = require('../util/eventmodel');
  const selection = require('../../selection');
  
  const SELECTOR_LIST = '[data-list]';
  const SELECTOR_LIST_ITEM = '[data-list-item]';
  const SELECTOR_DRAGGABLE = 'a, [draggable]';
  const ATTR_TARGET = 'data-drop-target';
  
  let dragFolderElement = null;
  
  /**
   * Handler for the dragstart event.
   */
  function onDragStart(event, node) {
    // Don't handle items that are specified with the new implementation.
    if (node.closest('[data-glue-draggable]')) {
      return;
    }
  
    dragFolderElement = null;
  
    const uris = selection.getUris();
    if (uris.length > 1) return;
  
    const uri = uris[0];
    const uriObject = uri && liburi.fromString(uri);
    if (!uriObject) return;
  
    // If dragging a folder, we need to temporarily save the folder DOM node,
    // so that while dragging over items we can check whether to show the drop
    // indicator. Dropping a folder inside itself should not be allowed.
    if (uriObject.type === liburi.Type.FOLDER) {
      const selectedNodes = selection.getNodes();
      for (let i = 0, l = selectedNodes.length; i < l; i++) {
        const selectedNode = selectedNodes[i];
        if (selectedNode.getAttribute('data-uri') === uri) {
          dragFolderElement = selectedNode;
          break;
        }
      }
    }
  }
  
  /**
   * Handler for ending a drag.
   */
  function onDragEnd() {
    dragFolderElement = null;
  }
  
  /**
   * Hook handler for setting drag data.
   *
   * @param {MouseEvent} event Event object for a dragstart event.
   * @param {Array.<string>} uris Array of URIs being dragged.
   * @param {string=} optContext Optional context URI.
   */
  function onSetDragData(event, uris, optContext) {
    if (optContext !== 'spotify:rootlist') {
      let followedPlaylistCount = 0;
      for (let i = 0, l = uris.length; i < l; i++) {
        if (liburi.isPlaylistV1OrV2(uris[i])) {
          const isFollowing = live(uris[i]).get('added');
          if (isFollowing) followedPlaylistCount++;
        } else {
          // If the item is not a playlist, it means we are dragging either only
          // items of another type, or playlists as well as other types.
          break;
        }
      }
  
      // Dropping only playlists that you are following in the rootlist should
      // not be allowed. If multiple playlists are dragged and some of them are
      // not being followed, dropping is allowed (and the receiver should handle
      // only inserting the new ones). To allow telling these cases apart while
      // not having access to drag data, we set a special MIME type that can be
      // checked later.
      if (followedPlaylistCount === uris.length) {
        event.dataTransfer.setData('text/x-spotify-only-followed-playlists', '');
      }
    }
  }
  
  /**
   * Hook handler for validating a list drop.
   *
   * @param {Object} result Result object. Setting the `valid` property will
   *     affect whether the drop is allowed.
   * @param {MouseEvent} event Some kind of drag and drop event.
   * @param {HTMLElement} list A DOM node marked up as a list.
   * @param {string} location The location of the cursor in relation to the list.
   *     This can be either 'inside' or 'below'. The value 'below' means that the
   *     cursor was below the last list found in the DOM.
   */
  function onListDropValidation(result, event, list, location) {
    const contextUri = list.getAttribute('data-uri');
    if (contextUri === 'spotify:rootlist') {
      // Dropping only already followed playlists into the rootlist should not
      // be allowed, since you can only have playlists once in the rootlist.
      if (hasOnlyFollowedPlaylists(event)) {
        result.valid = false;
        return;
      }
  
      // Dropping a folder inside or right below itself should not be allowed
      if (isInsideDraggedFolder(event, location)) {
        result.valid = false;
        return;
      }
  
      // Dropping on the bottom half of a folder should not be allowed as a list
      // target, but will be a regular target for the folder instead. However, if
      // the dragged items match the list target, dropping should be handled by
      // list code instead of target code (dragover should still not validate as
      // we want the active drop target style and not the list indicator).
      if (isInFolderBottomHalf(event)) {
        if (event.type !== 'drop' || !isMatchingTarget(event, list)) {
          result.valid = false;
          return;
        }
      }
    }
  }
  
  /**
   * Hook handler for validating a target.
   *
   * @param {Object} result Result object. Setting the `valid` property will
   *     affect whether the drop is allowed.
   * @param {MouseEvent} event Some kind of drag and drop event.
   * @param {HTMLElement} target DOM node for target that matches MIME types.
   */
  function onTargetValidation(result, event, target) {
    const list = target.closest(SELECTOR_LIST);
    if (list) {
      const contextUri = list.getAttribute('data-uri');
      if (contextUri === 'spotify:rootlist') {
        // Dropping only already followed playlists into the rootlist should not
        // be allowed, since you can only have playlists once in the rootlist.
        if (hasOnlyFollowedPlaylists(event)) {
          result.valid = false;
          return;
        }
  
        // Dropping valid rootlist items on the top half of a folder should not
        // be allowed as a regular target, but will be a list drop target instead.
        // For leave events, we need to validate the whole row as valid, so that
        // the target styling is removed correctly.
        if (event.type !== 'dragleave' && isMatchingTarget(event, list)) {
          const folderNode = getFolderNode(event);
          if (folderNode) {
            if (isBelowMiddleOfNode(event.clientY, folderNode)) {
              result.valid = true;
            } else {
              result.valid = false;
            }
            return;
          }
        }
      }
    }
  }
  
  /**
   * Hook handler for getting the indices being moved.
   *
   * @param {string} contextUri The context URI.
   * @param {LiveList} liveList Live list to be changed.
   * @param {Array.<number>} indices Array of indices in the list.
   */
  function onGetMoveIndices(contextUri, liveList, indices) {
    if (contextUri === 'spotify:rootlist') {
      const firstIndex = indices[0];
      const firstRow = liveList.get(firstIndex);
      const firstItem = firstRow && firstRow.get('playlist');
      const firstUri = firstItem && firstItem.uri;
      const firstUriType = firstUri && liburi.fromString(firstUri).type;
      const isFolder = firstUriType === liburi.Type.FOLDER;
  
      if (isFolder) {
        // Add all indices for the folder contents and the end marker
        if (indices.length === 1) {
          const length = getFolderLength(liveList, firstUri, firstIndex);
          for (let i = 1; i <= length; i++) {
            indices.push(firstIndex + i);
          }
        }
  
        // Abort the move operation if it only contains moving a folder start
        // marker, since moving folders should always move the entire folder.
        // Aborting by returning zero indices.
        if (indices.length === 1) {
          indices.length = 0;
          return;
        }
  
        const lastRow = liveList.get(indices[indices.length - 1]);
        const lastItem = lastRow && lastRow.get('playlist');
        const lastUri = lastItem && lastItem.uri;
  
        // Abort the move operation if moving a folder and the last item to move
        // is not the end marker for the folder. Aborting by returning zero
        // indices.
        if (lastUri !== `${firstUri}:end`) {
          indices.length = 0;
          return;
        }
      }
    }
  }
  
  /**
   * Check if only dragging playlists that are being followed already.
   *
   * @param {MouseEvent} event Some kind of drag and drop event.
   *
   * @return {boolean} True if only followed playlists are being dragged.
   */
  function hasOnlyFollowedPlaylists(event) {
    const types = event.dataTransfer.types;
    if (types.indexOf('text/x-spotify-only-followed-playlists') > -1) {
      return true;
    }
  
    return false;
  }
  
  /**
   * Check if dragging a folder into (or below) itself.
   *
   * @param {MouseEvent} event Some kind of drag and drop event.
   * @param {string} location The location of the cursor in relation to the list.
   *     This can be either 'inside' or 'below'. The value 'below' means that the
   *     cursor was below the last list found in the DOM.
   *
   * @return {boolean} True if dragging into the same folder being dragged.
   */
  function isInsideDraggedFolder(event, location) {
    if (dragFolderElement) {
      const rowNode = location === 'below' ? getLastRowNode() : getRowNode(event);
      if (rowNode) {
        let targetIndex = +rowNode.getAttribute('data-index');
        if (location === 'inside') {
          if (!isBelowMiddleOfNode(event.clientY, rowNode)) {
            targetIndex--;
          }
        }
        if (isIndexInsideDraggedFolder(targetIndex)) {
          return true;
        }
      }
    }
  
    return false;
  }
  
  /**
   * Check if dragging onto the bottom half of a folder.
   *
   * @param {MouseEvent} event Some kind of drag and drop event.
   *
   * @return {boolean} True if dragging onto the bottom half.
   */
  function isInFolderBottomHalf(event) {
    const folderNode = getFolderNode(event);
    if (folderNode) {
      return isBelowMiddleOfNode(event.clientY, folderNode);
    }
    return false;
  }
  
  /**
   * Check if the position is below the middle of the node.
   *
   * @param {number} y The Y position relative to the viewport.
   * @param {HTMLElement} node DOM node to check against.
   *
   * @return {boolean} True if the position is below.
   */
  function isBelowMiddleOfNode(y, node) {
    const rect = node.getBoundingClientRect();
    return y >= rect.top + rect.height / 2;
  }
  
  /**
   * Check if the index is inside (or right below) the folder being dragged.
   *
   * @param {number} index Current target index.
   *
   * @return {boolean} True if the target index is inside the folder.
   */
  function isIndexInsideDraggedFolder(index) {
    if (!dragFolderElement) return false;
  
    const liveList = live('spotify:rootlist').get('rows');
    if (!liveList) return false;
  
    const folderUri = dragFolderElement.getAttribute('data-uri');
    if (!folderUri) return false;
  
    const start = +dragFolderElement.getAttribute('data-index');
    const end = start + getFolderLength(liveList, folderUri, start);
  
    if (start > -1 && end > start && index >= start && index <= end) {
      return true;
    }
  
    return false;
  }
  
  /**
   * Check if the MIME types in the drag event matches the MIME types for the
   * specified drop target.
   *
   * @param {MouseEvent} event Some kind of drag and drop event.
   * @param {HTMLElement} node A DOM node.
   *
   * @return {boolean} True if the element matches the event.
   */
  function isMatchingTarget(event, node) {
    const eventTypes = event.dataTransfer.types;
    const nodeTypes = node.getAttribute(ATTR_TARGET);
  
    if (nodeTypes) {
      const matching = intersection(nodeTypes.split('|'), eventTypes);
      if (matching.length > 0) return true;
    }
  
    return false;
  }
  
  /**
   * Get the folder row node from the event.
   *
   * @param {MouseEvent} event Some kind of drag and drop event.
   *
   * @return {HTMLElement?} DOM node for the folder, or null if not found.
   */
  function getFolderNode(event) {
    const rowNode = getRowNode(event);
    if (rowNode) {
      const uri = rowNode.getAttribute('data-uri');
      const uriObject = liburi.from(uri);
      if (uriObject && uriObject.type === liburi.Type.FOLDER) {
        return rowNode;
      }
    }
  
    return null;
  }
  
  /**
   * Get the list row element from the event target.
   * If the event target itself isn't a list row, it will step out in the parent
   * tree until it finds a row.
   *
   * @param {MouseEvent} event Some kind of drag and drop event.
   *
   * @return {HTMLElement?} The list row element, or null if not found.
   */
  function getRowNode(event) {
    return event.target.closest(SELECTOR_LIST_ITEM);
  }
  
  /**
   * Get the last list row from the last list in the DOM.
   *
   * @return {HTMLElement?} The list row element, or null if not found.
   */
  function getLastRowNode() {
    const lists = $(SELECTOR_LIST);
    const lastList = lists && lists[lists.length - 1];
    if (lastList) {
      const listItems = $(lastList).search(SELECTOR_LIST_ITEM);
      const lastItem = listItems && listItems[listItems.length - 1];
      if (lastItem) return lastItem;
    }
  
    return null;
  }
  
  /**
   * Get the length of a folder, including the contents and end marker, but not
   * the start marker.
   *
   * @param {LiveList} liveList Live list for the rootlist.
   * @param {string} folderUri The folder URI.
   * @param {number} index The index where the folder start marker is.
   *
   * @return {number} The length of the folder.
   */
  function getFolderLength(liveList, folderUri, index) {
    let rowAtIndex = liveList.get(index);
    let itemAtIndex = rowAtIndex && rowAtIndex.get('playlist');
    let uriAtIndex = itemAtIndex && itemAtIndex.uri;
    if (uriAtIndex !== folderUri) return 0;
  
    const folderEndUri = `${folderUri}:end`;
    let amount = 0;
  
    for (let i = index + 1, l = liveList.length; i < l; i++) {
      amount++;
  
      rowAtIndex = liveList.get(i);
      itemAtIndex = rowAtIndex && rowAtIndex.get('playlist');
      uriAtIndex = itemAtIndex && itemAtIndex.uri;
      if (uriAtIndex === folderEndUri) break;
    }
  
    return amount;
  }
  
  /**
   * Attach the module.
   */
  function attach() {
    eventModel.delegate('dragstart', SELECTOR_DRAGGABLE, onDragStart);
    eventModel.delegate('dragend', 'body', onDragEnd);
  
    hooks.add('set-drag-data', onSetDragData);
    hooks.add('list-drop-validation', onListDropValidation);
    hooks.add('target-validation', onTargetValidation);
    hooks.add('get-move-indices', onGetMoveIndices);
  }
  
  /**
   * Detach the module.
   */
  function detach() {
    eventModel.undelegate('dragstart', SELECTOR_DRAGGABLE, onDragStart);
    eventModel.undelegate('dragend', 'body', onDragEnd);
  
    hooks.remove('set-drag-data', onSetDragData);
    hooks.remove('list-drop-validation', onListDropValidation);
    hooks.remove('target-validation', onTargetValidation);
    hooks.remove('get-move-indices', onGetMoveIndices);
  }
  
  exports.attach = attach;
  exports.detach = detach;
  
  },{"../../../live":172,"../../../spotify-elements":210,"../../selection":278,"../util/eventmodel":231,"../util/hooks":232,"mout/array/intersection":441,"spotify-liburi":553}],225:[function(require,module,exports){
  /**
   * @module spotify-events/dragndrop/custom/sortlist
   * @private
   */
  'use strict';
  
  const liburi = require('spotify-liburi');
  const sortUriUtils = require('../../../live-sort-uri');
  
  const hooks = require('../util/hooks');
  const eventModel = require('../util/eventmodel');
  const selection = require('../../selection');
  
  const SELECTOR_DRAGGABLE = 'a, [draggable]';
  
  let dragSelectionState = null;
  
  /**
   * Handler for the dragstart event.
   */
  function onDragStart(event, node) {
    // Don't handle items that are specified with the new implementation.
    if (node.closest('[data-glue-draggable]')) {
      return;
    }
  
    dragSelectionState = selection.getIndicesPerList();
  }
  
  /**
   * Handler for ending a drag.
   */
  function onDragEnd() {
    dragSelectionState = null;
  }
  
  /**
   * Hook handler for validating a list drop.
   *
   * @param {Object} result Result object. Setting the `valid` property will
   *     affect whether the drop is allowed.
   * @param {MouseEvent} event Some kind of drag and drop event.
   * @param {HTMLElement} list A DOM node marked up as a list.
   */
  function onListDropValidation(result, event, list) {
    const contextUri = list.getAttribute('data-uri');
    if (!contextUri) return;
  
    // Dropping in sorted lists should not allow specific drop positions,
    if (sortUriUtils.isValid(contextUri) && event.type !== 'drop') {
      result.valid = false;
    }
  }
  
  /**
   * Hook handler for validating a list target.
   *
   * @param {Object} result Result object. Setting the `valid` property will
   *     affect whether the drop is allowed.
   * @param {HTMLElement} list A DOM node marked up as a list.
   */
  function onListTargetValidation(result, list) {
    const contextUri = list.getAttribute('data-uri');
    if (!contextUri) return;
  
    // Bail on all invalid uris
    if (!sortUriUtils.isValid(contextUri)) return;
  
    // We should support incoming drag and drops as well, like from the
    // now playing view for example
    if (!dragSelectionState) {
      result.valid = true;
      return;
    }
  
    // Dropping in sorted lists should be allowed as a regular target
    if (dragSelectionState.length === 1) {
      if (dragSelectionState[0].uri !== contextUri) {
        result.valid = true;
      }
    }
  }
  
  /**
   * Hook handler for checking if a list is a track list.
   *
   * @param {Object} result Result object. Setting the `valid` property will
   *     affect the result.
   * @param {LiveList} liveList The live list.
   * @param {string} contextUri The context URI.
   */
  function onIsListOfTracks(result, liveList, contextUri) {
    if (sortUriUtils.isValid(contextUri)) {
      const originUri = sortUriUtils.getOriginUri(contextUri);
      if (liburi.isPlaylistV1OrV2(originUri)) {
        result.valid = true;
      }
    }
  }
  
  /**
   * Attach the module.
   */
  function attach() {
    eventModel.delegate('dragstart', SELECTOR_DRAGGABLE, onDragStart);
    eventModel.delegate('dragend', 'body', onDragEnd);
  
    hooks.add('list-drop-validation', onListDropValidation);
    hooks.add('list-target-validation', onListTargetValidation);
    hooks.add('is-list-of-tracks', onIsListOfTracks);
  }
  
  /**
   * Detach the module.
   */
  function detach() {
    eventModel.undelegate('dragstart', SELECTOR_DRAGGABLE, onDragStart);
    eventModel.undelegate('dragend', 'body', onDragEnd);
  
    hooks.remove('list-drop-validation', onListDropValidation);
    hooks.remove('list-target-validation', onListTargetValidation);
    hooks.remove('is-list-of-tracks', onIsListOfTracks);
  }
  
  exports.attach = attach;
  exports.detach = detach;
  
  },{"../../../live-sort-uri":170,"../../selection":278,"../util/eventmodel":231,"../util/hooks":232,"spotify-liburi":553}],226:[function(require,module,exports){
  (function (global){
  /**
   * @module spotify-events/dragndrop/dragdata
   * @private
   */
  'use strict';
  
  const liburi = require('spotify-liburi');
  
  const selection = require('../selection');
  const hooks = require('./util/hooks');
  const eventModel = require('./util/eventmodel');
  const itemText = require('./util/itemtext');
  const getLogContext = require('../util/get-log-context');
  
  const SELECTOR_DRAGGABLE = 'a, [draggable]';
  
  /**
   * Handler for the dragstart event.
   *
   * @param {MouseEvent} event Event object for a dragstart event.
   * @param {HTMLElement} target The target element.
   */
  function onDragStart(event, target) {
    // Don't handle items that are specified with the new implementation.
    if (target.closest('[data-glue-draggable]')) {
      return;
    }
  
    const isSelected = selection.isNodeSelected(target);
  
    if (isSelected) {
      const uris = selection.getUris();
      if (uris.length > 0) {
        const state = selection.getIndicesPerList();
        const singleContext = state.length === 1;
        const context = singleContext ? state[0] && state[0].uri : null;
        if (context) {
          setEventData(event, uris, null, context, getLogContext(target));
        } else {
          setEventData(event, uris, null, null, getLogContext(target));
        }
      }
    } else {
      let uri = target.getAttribute('data-uri');
      if (!uri) {
        const uriObject = liburi.from(target.getAttribute('href'));
        uri = uriObject ? uriObject.toURI() : '';
      }
      const dragText = itemText.getForElement(target);
  
      if (uri) {
        setEventData(event, [uri], [dragText], null, getLogContext(target));
      }
    }
  }
  
  /**
   * Set drag data on the event.
   * This includes regular MIME types such as plain text and html, but also custom
   * Spotify MIME types that can be used to detect the dragged content while
   * dragging (you can't access data while dragging, so there is no other way
   * to detect what kind of items are being dragged).
   *
   * @param {MouseEvent} event Event object for a dragstart event.
   * @param {Array.<string>} uris Array of Spotify URIs.
   * @param {Array.<string>=} optDragTexts Optional drag texts for each URI.
   * @param {string=} optContext Optional context URI.
   * @param {string=} optLogContext Optional log context.
   */
  function setEventData(event, uris, optDragTexts, optContext, optLogContext) {
    const urlsPerMimeType = getUrlsPerMimeType(uris);
    const urls = getUrlsForUris(uris);
    const urlList = urls.join('\n');
    const html = getLinkHTML(uris, urls, optDragTexts);
  
    // We set this before others because safari overrides any data that was set
    // before this particular mimetype was set.
    event.dataTransfer.setData('text/uri-list', urlList);
  
    for (const type in urlsPerMimeType) {
      if (!urlsPerMimeType.hasOwnProperty(type)) continue;
      const urlsForType = urlsPerMimeType[type];
      event.dataTransfer.setData(type, urlsForType.join('\n'));
    }
  
    event.dataTransfer.setData('text/plain', urlList);
    event.dataTransfer.setData('text/html', html);
  
    if (optContext) {
      // Provide the context as drag data. This can be used in the drop event to
      // know where it came from.
      event.dataTransfer.setData('text/x-spotify-data-context', optContext);
      event.dataTransfer.setData(`text/x-spotify-data-context-${optContext}`, '');
    }
  
    if (optLogContext) {
      event.dataTransfer.setData('text/x-spotify-data-log-context', optLogContext);
    }
  
    event.dataTransfer.setData('text/x-spotify-data-app-uri', global.__spotify && global.__spotify.app_uri || null);
  
    hooks.run('set-drag-data', event, uris, optContext);
  }
  
  /**
   * Get URLs for the URIs organized by MIME type.
   *
   * @param  {Array.<string>} uris Array of Spotify URIs.
   *
   * @return {Object.<string, Array.<string>>} Object of arrays of URLs.
   */
  function getUrlsPerMimeType(uris) {
    const urlsPerMimeType = {};
    for (let i = 0, l = uris.length; i < l; i++) {
      const uri = uris[i];
      const type = getMimeType(uri);
      const urlsForType = urlsPerMimeType[type] || (urlsPerMimeType[type] = []);
      urlsForType.push(getShareLink(uri));
    }
    return urlsPerMimeType;
  }
  
  /**
   * Get URLs for the URIs.
   *
   * @param {Array.<string>} uris Array of Spotify URIs.
   *
   * @return {Array.<string>} Array of share URLs.
   */
  function getUrlsForUris(uris) {
    const urls = new Array(uris.length);
    for (let i = 0, l = uris.length; i < l; i++) {
      urls[i] = getShareLink(uris[i]);
    }
    return urls;
  }
  
  /**
   * Get a share URL from a Spotify URI.
   *
   * @param {string} uri A Spotify URI.
   *
   * @return {string} The share URL.
   */
  function getShareLink(uri) {
    const uriObject = liburi.from(uri);
    return uriObject ? uriObject.toOpenURL() : uri;
  }
  
  /**
   * Get the custom Spotify MIME type for a URI.
   *
   * @param {string} uri A Spotify URI.
   *
   * @return {string} The MIME type.
   */
  function getMimeType(uri) {
    const type = 'text/x-spotify-';
  
    const uriObject = liburi.from(uri);
    if (uriObject) {
      const uriType = uriObject.type;
  
      if (uriType === liburi.Type.TRACK || uriType === liburi.Type.LOCAL) {
        return `${type}tracks`;
      }
      if (uriType === liburi.Type.ALBUM || uriType === liburi.Type.LOCAL_ALBUM) {
        return `${type}albums`;
      }
      if (uriType === liburi.Type.ARTIST || uriType === liburi.Type.LOCAL_ARTIST) {
        return `${type}artists`;
      }
      if (uriType === liburi.Type.USER || uriType === liburi.Type.PROFILE) {
        return `${type}users`;
      }
      if (uriType === liburi.Type.PLAYLIST || uriType === liburi.Type.PLAYLIST_V2) {
        return `${type}playlists`;
      }
      if (uriType === liburi.Type.FOLDER) {
        return `${type}folders`;
      }
      if (uriType === liburi.Type.STATION && !(uriObject.args.length > 2 && uriObject.args[2] === 'cluster')) {
        return `${type}stations`;
      }
      if (uriType === liburi.Type.EPISODE) {
        return `${type}episodes`;
      }
    }
  
    return `${type}unknown`;
  }
  
  /**
   * Get the HTML for links to the given URLs.
   *
   * @param {Array.<string>} uris Array of Spotify URIs.
   * @param {Array.<string>} urls Array of Spotify URLs used for sharing.
   * @param {Array.<string>=} optDragTexts Optional drag texts for each URI.
   *
   * @return {string} String of HTML.
   */
  function getLinkHTML(uris, urls, optDragTexts) {
    let html = '';
  
    for (let i = 0, l = uris.length; i < l; i++) {
      const textFromArgs = optDragTexts && optDragTexts[i];
      const text = textFromArgs || itemText.getForUri(uris[i]);
  
      html += `<a href="${urls[i]}">${text}</a>`;
  
      if (i < uris.length - 1) {
        html += '<br>';
      }
    }
  
    return html;
  }
  
  /**
   * Attach the module.
   */
  function attach() {
    eventModel.delegate('dragstart', SELECTOR_DRAGGABLE, onDragStart);
  }
  
  /**
   * Detach the module.
   */
  function detach() {
    eventModel.undelegate('dragstart', SELECTOR_DRAGGABLE, onDragStart);
  }
  
  exports.attach = attach;
  exports.detach = detach;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../selection":278,"../util/get-log-context":292,"./util/eventmodel":231,"./util/hooks":232,"./util/itemtext":233,"spotify-liburi":553}],227:[function(require,module,exports){
  /**
   * @module spotify-events/dragndrop/dragimage
   * @private
   */
  'use strict';
  
  const liburi = require('spotify-liburi');
  const defer = require('../../../libs/prime/defer');
  
  const selection = require('../selection');
  const eventModel = require('./util/eventmodel');
  const itemText = require('./util/itemtext');
  
  const i18n = require('../i18n/index');
  
  const SELECTOR_DRAGGABLE = 'a, [draggable]';
  const DRAGIMAGE_POS_X = -10;
  const DRAGIMAGE_POS_Y = -5;
  
  let dragImageElement = null;
  let dragImageShelter = null;
  
  /**
   * Handler for the dragstart event.
   *
   * @param {MouseEvent} event Event object for a dragstart event.
   * @param {HTMLElement} target The target element.
   */
  function onDragStart(event, target) {
    // Don't handle items that are specified with the new implementation.
    if (target.closest('[data-glue-draggable]')) {
      return;
    }
  
    const isSelected = selection.isNodeSelected(target);
  
    let text = '';
  
    if (isSelected) {
      const uris = selection.getUris();
      if (uris.length === 1) {
        text = itemText.getForUri(uris[0]);
      } else {
        text = getLocalizedTypeFromUris(uris);
      }
    } else {
      text = itemText.getForElement(target);
    }
  
    const dragImage = getDragImage(text);
  
    event.dataTransfer.setDragImage(dragImage, DRAGIMAGE_POS_X, DRAGIMAGE_POS_Y);
  
    removeDragImage();
  }
  
  /**
   * Get the drag image element with the specified text set.
   *
   * @param {string} text The text to set.
   *
   * @return {HTMLElement} The drag image element.
   */
  function getDragImage(text) {
    if (!dragImageElement) {
      dragImageElement = document.createElement('div');
      dragImageElement.className = 'dnd-image';
    }
    if (!dragImageShelter) {
      dragImageShelter = document.createElement('div');
      dragImageShelter.className = 'dnd-image-shelter';
    }
  
    dragImageElement.textContent = text;
  
    document.body.appendChild(dragImageElement);
    document.body.appendChild(dragImageShelter);
  
    return dragImageElement;
  }
  
  /**
   * Remove the drag image element and the shelter it hides behind.
   * This must be delayed a tick to let the native drag and drop handling
   * get the time to read the image.
   */
  function removeDragImage() {
    defer.immediate(function () {
      if (dragImageElement && dragImageElement.parentNode) {
        dragImageElement.parentNode.removeChild(dragImageElement);
      }
      if (dragImageShelter && dragImageShelter.parentNode) {
        dragImageShelter.parentNode.removeChild(dragImageShelter);
      }
    });
  }
  
  /**
   * Get a localized type for a list of URIs.
   * This could be for example 'tracks', 'playlists' etc. Falls back to 'items'
   * if the URIs are of different type. As this is only called when we drag
   * multiple items, we don't need the singular form.
   *
   * @param {Array.<string>} uris Array of URIs.
   *
   * @return {string} The localized type.
   */
  function getLocalizedTypeFromUris(uris) {
    let type = 'item';
  
    const uriObjectForFirst = liburi.from(uris[0]);
    const typeForFirst = uriObjectForFirst && uriObjectForFirst.type;
  
    if (typeForFirst) {
      type = typeForFirst;
  
      for (let i = 0, l = uris.length; i < l; i++) {
        const uriObject = liburi.from(uris[i]);
        if (!uriObject || uriObject.type !== typeForFirst) {
          type = 'item';
          break;
        }
      }
    }
  
    // User URIs are parsed with type 'profile' in liburi, but we want 'user'
    if (type === 'profile') {
      type = 'user';
    }
  
    const pluralType = `${type}s`;
  
    // Get the localized version of the plural type
    let localized = i18n.get(`drag_tooltip_many_${pluralType}`, uris.length);
  
    // If the type was not the generic 'items' type and no localization was found,
    // localize the generic type.
    if (localized === `drag_tooltip_many_${pluralType}` && pluralType !== 'items') {
      localized = i18n.get('drag_tooltip_many_items', uris.length);
    }
  
    // Return the localized type, or if nothing is found, fall back to the type
    return localized || pluralType;
  }
  
  /**
   * Attach the module.
   */
  function attach() {
    eventModel.delegate('dragstart', SELECTOR_DRAGGABLE, onDragStart);
  }
  
  /**
   * Detach the module.
   */
  function detach() {
    eventModel.undelegate('dragstart', SELECTOR_DRAGGABLE, onDragStart);
  }
  
  exports.attach = attach;
  exports.detach = detach;
  
  },{"../../../libs/prime/defer":200,"../i18n/index":237,"../selection":278,"./util/eventmodel":231,"./util/itemtext":233,"spotify-liburi":553}],228:[function(require,module,exports){
  /**
   * @module spotify-events/dragndrop
   */
  'use strict';
  
  const dragDataModule = require('./dragdata');
  const dragImageModule = require('./dragimage');
  const targetModule = require('./target');
  const listModule = require('./list');
  
  // Custom behaviors
  const sortlistModule = require('./custom/sortlist');
  const rootlistModule = require('./custom/rootlist');
  const queueModule = require('./custom/queue');
  
  /**
   * Attach the module.
   */
  function attach() {
    dragDataModule.attach();
    dragImageModule.attach();
    targetModule.attach();
    listModule.attach();
    queueModule.attach();
  
    sortlistModule.attach();
    rootlistModule.attach();
  }
  
  /**
   * Detach the module.
   */
  function detach() {
    dragDataModule.detach();
    dragImageModule.detach();
    targetModule.detach();
    listModule.detach();
    queueModule.detach();
  
    sortlistModule.detach();
    rootlistModule.detach();
  }
  
  exports.attach = attach;
  exports.detach = detach;
  exports.update = function () {};
  
  },{"./custom/queue":223,"./custom/rootlist":224,"./custom/sortlist":225,"./dragdata":226,"./dragimage":227,"./list":229,"./target":230}],229:[function(require,module,exports){
  /**
   * @module spotify-events/dragndrop/list
   * @private
   */
  'use strict';
  
  const $ = require('../../spotify-elements');
  const intersection = require('mout/array/intersection');
  const Range = require('../../range2');
  const live = require('../../live');
  const liburi = require('spotify-liburi');
  const listOperations = require('../../../libs/live-list-operations');
  
  const hooks = require('./util/hooks');
  const eventModel = require('./util/eventmodel');
  const selection = require('../selection');
  
  const SELECTOR_DROP_TARGET = '[data-drop-target]';
  const SELECTOR_LIST = '[data-list]';
  const SELECTOR_LIST_ITEM = '[data-list-item]';
  const SELECTOR_LIST_TARGET = `${SELECTOR_DROP_TARGET}, body`;
  const SELECTOR_DRAGGABLE = 'a, [draggable]';
  const ATTR_TARGET = 'data-drop-target';
  
  let dropIndicator = null;
  let draggedLinkContext = null;
  const disabledLists = {};
  
  /**
   * Simple wrapper to cache calls to selection.getIndicesPerList()
   *
   * @return {Object} The result of selection.getIndicesPerList()
   */
  const cachedSelectionState = function () {
    let cache = {
      lastTime: 0,
      lastValue: null
    };
    const CACHE_TIMEOUT = 500;
  
    return {
      get: function () {
        const now = Date.now();
  
        if (now - cache.lastTime > CACHE_TIMEOUT) {
          cache = {
            lastValue: selection.getIndicesPerList(),
            lastTime: now
          };
        }
  
        return cache.lastValue;
      },
      clear: function () {
        cache.lastTime = 0;
      }
    };
  }();
  
  /**
   * Handler for the dragstart event.
   *
   * @param {MouseEvent} event Event object for a dragstart event.
   * @param {HTMLElement} target The target element.
   */
  function onDragStart(event, target) {
    // Don't handle items that are specified with the new implementation.
    if (target.closest('[data-glue-draggable]')) {
      return;
    }
  
    draggedLinkContext = null;
  
    // clear the selectionState cache
    cachedSelectionState.clear();
  
    const matchingTarget = getMatchingTarget(event);
    if (matchingTarget) {
      const isInList = $(matchingTarget).matches(SELECTOR_LIST);
      if (isInList) {
        const uri = matchingTarget.getAttribute('data-uri');
  
        // Dragging a link from inside a list needs to store the list context,
        // since it is not allowed to drop a dragged link inside the same list.
        if (target.tagName.toLowerCase() === 'a') {
          draggedLinkContext = uri;
        }
  
        // Dragging is disallowed for lists where we're waiting for a list move
        // to finish. This is to prevent multiple moves to be called before the
        // response from the first comes back.
        if (disabledLists[uri]) {
          event.preventDefault();
          return;
        }
  
        const rowNode = getRowNode(event);
        if (rowNode) {
          const index = getTargetIndex(event.clientY, rowNode);
          if (index !== -1) {
            event.dataTransfer.setData('text/x-spotify-data-log-source-index', index);
          }
        }
      }
    }
  }
  
  /**
   * Handler for dragging over a drop target.
   *
   * @param {MouseEvent} event A dragover event object.
   * @param {HTMLElement} target DOM element for the drop target. This can be the
   *     body element even if it's not a drop target.
   */
  function onDragOver(event, target) {
    let positionedListDrop = false;
    let rowNode = null;
  
    const isBody = target === document.body;
    let list = null;
    if (isBody) list = getListTargetFromBody(event);else list = getMatchingListTarget(event);
  
    // If we found a list from the event, we must validate if it's allowed to drop
    // in the list.
    if (list) {
      const location = isBody ? 'below' : 'inside';
  
      let index = -1;
      rowNode = location === 'below' ? getLastRowFromLastList() : getRowNode(event);
      if (rowNode) {
        index = getTargetIndex(event.clientY, rowNode);
      }
  
      const contextUri = list.getAttribute('data-uri');
  
      if (validateListDrop(event, list, contextUri, location, index)) {
        if (isBody) {
          positionedListDrop = true;
        } else {
          if (rowNode) {
            positionedListDrop = true;
          }
        }
      }
    }
  
    if (positionedListDrop) {
      event.preventDefault();
  
      if (isBody) showDropIndicatorBelowList(list);else if (rowNode) showDropIndicatorForRow(rowNode, event.clientY);
    } else {
      hideDropIndicator();
  
      const listHookResult = { valid: false };
      if (list) {
        hooks.run('list-target-validation', listHookResult, list);
      }
  
      // The list might be accepted as a drop target even if dropping in a
      // specific position was not accepted. In that case we need to prevent the
      // default action to tell the browser that we accept drops here.
      if (listHookResult.valid) {
        event.preventDefault();
      } else if (!isBody && list) {
        const targetHookResult = { valid: false };
        hooks.run('target-validation', targetHookResult, event, target);
  
        // If we know that dropping is not allowed here at all, we need to remove
        // the drop effect from the cursor. This is mainly since the handler in
        // targets.js has already handled the event and accepted it, which sets
        // the drop effect.
        if (!targetHookResult.valid) {
          event.dataTransfer.dropEffect = 'none';
        }
      }
    }
  }
  
  /**
   * Handler for dragging out from a drop target.
   */
  function onDragLeave() {
    hideDropIndicator();
  }
  
  /**
   * Handler for ending a drag.
   */
  function onDragEnd() {
    hideDropIndicator();
    draggedLinkContext = null;
  }
  
  /**
   * Handler for dropping onto a drop target.
   *
   * @param {MouseEvent} event A drop event object.
   * @param {HTMLElement} target DOM element for the drop target.
   */
  function onDrop(event, target) {
    hideDropIndicator();
  
    const isBody = target === document.body;
    let list = null;
    if (isBody) list = getListTargetFromBody(event);else list = getMatchingListTarget(event);
  
    if (list) {
      const location = isBody ? 'below' : 'inside';
  
      let index = -1;
      const rowNode = location === 'below' ? getLastRowFromLastList() : getRowNode(event);
      if (rowNode) {
        index = getTargetIndex(event.clientY, rowNode);
      }
  
      const contextUri = list.getAttribute('data-uri');
  
      if (validateListDrop(event, list, contextUri, location, index)) {
        if (contextUri) {
          performListChangesForEvent(event, list, contextUri, location);
          event.handled = true;
        }
      }
    }
  }
  
  /**
   * Show the drop indicator below the provided list element.
   *
   * @param {HTMLElement} list A list element.
   */
  function showDropIndicatorBelowList(list) {
    const listRect = list.getBoundingClientRect();
    showDropIndicator(listRect.left, listRect.bottom, listRect.width);
  }
  
  /**
   * Show the drop indicator above or below a list row.
   *
   * @param {HTMLElement} rowNode A list row element.
   * @param {number} cursorY The cursor Y position, relative to the viewport.
   */
  function showDropIndicatorForRow(rowNode, cursorY) {
    const belowMiddle = isBelowMiddleOfNode(cursorY, rowNode);
    const rowRect = rowNode.getBoundingClientRect();
    const x = rowRect.left;
    const y = rowRect.top + (belowMiddle ? rowRect.height : 0);
    showDropIndicator(x, y, rowRect.width);
  }
  
  /**
   * Show the drop indicator line for the specified row and cursor position.
   * If the cursor position is below the middle of the row, the indicator will be
   * placed below the row, otherwise above the row.
   *
   * @param {number} x The X position relative to the viewport.
   * @param {number} y The Y position relative to the viewport.
   * @param {number} width The width of the indicator.
   */
  function showDropIndicator(x, y, width) {
    // Don't use window.scroll X/Y because cross browser issues.
    // https://developer.mozilla.org/en-US/docs/Web/API/Window.scrollY
    const indicatorAbsX = window.pageXOffset + x;
    const indicatorAbsY = window.pageYOffset + y;
    const indicatorWidth = width;
  
    if (!dropIndicator) {
      dropIndicator = document.createElement('div');
      dropIndicator.className = 'tr-drag-indicator';
    }
  
    dropIndicator.style.left = `${indicatorAbsX}px`;
    dropIndicator.style.width = `${indicatorWidth}px`;
    dropIndicator.style.top = `${indicatorAbsY}px`;
  
    if (!dropIndicator.parentNode) {
      document.body.appendChild(dropIndicator);
    }
  }
  
  /**
   * Hide the drop indicator.
   */
  function hideDropIndicator() {
    if (dropIndicator && dropIndicator.parentNode) {
      dropIndicator.parentNode.removeChild(dropIndicator);
    }
  }
  
  /**
   * Perform the list changes that are needed from the drop event.
   *
   * @param {MouseEvent} event A drop event.
   * @param {HTMLElement} list The list DOM node.
   * @param {string} contextUri The context URI for the list.
   * @param {string} location The location of the cursor in relation to the list.
   *     This can be either 'inside' or 'below'. The value 'below' means that the
   *     cursor was below the last list found in the DOM.
   */
  function performListChangesForEvent(event, list, contextUri, location) {
    const liveList = live(contextUri).get('rows');
    if (!liveList) return;
  
    let targetIndex = 0;
  
    if (location === 'below') {
      const lastRowNode = getLastRowFromLastList();
  
      // Dropping below a list without rows will use target index 0
      if (lastRowNode) {
        targetIndex = getTargetIndex(event.clientY, lastRowNode);
      }
    } else {
      const rowNode = getRowNode(event);
      if (!rowNode) return;
      targetIndex = getTargetIndex(event.clientY, rowNode);
    }
  
    const sourceContext = event.dataTransfer.getData('text/x-spotify-data-context');
    const isSameContext = sourceContext && sourceContext === contextUri;
  
    if (isSameContext) {
      performMoveChanges(contextUri, liveList, targetIndex);
    } else {
      performInsertChanges(event, list, contextUri, liveList, targetIndex);
    }
    event.data = {
      targetIndex: targetIndex
    };
  }
  
  /**
   * Move the selected items to a target index in the list.
   *
   * @param {string} contextUri The context URI of the list.
   * @param {LiveList} liveList The live list for the context.
   * @param {number} targetIndex The target index in the list.
   */
  function performMoveChanges(contextUri, liveList, targetIndex) {
    const indices = getMoveIndices(contextUri, liveList);
  
    // Selection can be nothing. This can happen if you select a row, drag it
    // and before dropping, the row is removed by something else (on another
    // device perhaps). Dropping should in that case not perform any action.
    // Similarly, if dragging multiple rows and some of the rows are removed
    // while dragging, only the rows that are still in the list will be moved.
    if (indices.length === 0) return;
  
    const operations = listOperations.getMoveOperations(indices, targetIndex);
  
    // If the list changed while dragging, it might happen that we end up
    // dropping at the same index as we're dragging from, which would give
    // us no operations to perform.
    if (operations.length === 0) return;
  
    liveList.publish(operations);
  
    temporarilyDisableList(contextUri, liveList);
  }
  
  /**
   * Insert items from the event into the list at the target index.
   *
   * @param {MouseEvent} event A drop event.
   * @param {HTMLElement} list The list DOM node.
   * @param {string} contextUri The context URI for the list.
   * @param {LiveList} liveList The live list for the context.
   * @param {number} targetIndex The target index in the list.
   */
  function performInsertChanges(event, list, contextUri, liveList, targetIndex) {
    getInsertValues(event, list, liveList, contextUri, function (error, values) {
      if (error) return;
      if (values.length === 0) return;
  
      const operations = [{ type: 'insert', index: targetIndex, values: values }];
  
      liveList.publish(operations);
  
      temporarilyDisableList(contextUri, liveList);
    });
  }
  
  /**
   * Check if dropping at the current position in a list is allowed.
   *
   * @param {MouseEvent} event Some kind of drag and drop event.
   * @param {HTMLElement} list A DOM node marked up as a list.
   * @param {string} contextUri The context URI of the list being dropped on.
   * @param {string} location The location of the cursor in relation to the list.
   *     This can be either 'inside' or 'below'. The value 'below' means that the
   *     cursor was below the last list found in the DOM.
   *
   * @return {boolean} True if it's a valid drop position.
   */
  function validateListDrop(event, list, contextUri, location, index) {
    // All positions are valid if we're not dragging from a context in the first
    // place. If we do have a context though, we need to validate the position.
    const types = event.dataTransfer.types;
  
    // We can't read drag data while dragging (only on drop), so we put the
    // context in the MIME type. The browser makes all MIME types in the drag
    // event lowercase, so we need to make it lowercase to properly compare it.
    const contextMimeType = `text/x-spotify-data-context-${contextUri}`.toLowerCase();
  
    const isDraggingFromContext = types.includes(contextMimeType);
    if (isDraggingFromContext) {
      if (!validatePotentialOperations(event, list, location)) {
        return false;
      }
    }
  
    const uri = list.getAttribute('data-uri');
    if (uri && draggedLinkContext === uri) {
      return false;
    }
  
    const result = { valid: true };
    hooks.run('list-drop-validation', result, event, list, location, index);
  
    return !!result.valid;
  }
  
  /**
   * Check if dropping at the current event position would actually result in
   * operations that would change the list.
   *
   * @param {MouseEvent} event Some kind of drag and drop event.
   * @param {HTMLElement} list A DOM node marked up as a list.
   * @param {string} location The location of the cursor in relation to the list.
   *     This can be either 'inside' or 'below'. The value 'below' means that the
   *     cursor was below the last list found in the DOM.
   *
   * @return {boolean} True if it would result in a change.
   */
  function validatePotentialOperations(event, list, location) {
    // get a cached version of the selectionState
    const selectionState = cachedSelectionState.get();
  
    // Dropping inside the only selected range inside the only container that
    // has a selection is not allowed, as that would result in no change.
    if (selectionState.length === 1) {
      // Dropping inside a different context is always fine
      const contextUri = list.getAttribute('data-uri');
      if (contextUri !== selectionState[0].uri) {
        return true;
      }
  
      const selectedRanges = Range.fromIndices(selectionState[0].indicesWithHidden);
  
      if (selectedRanges.length === 1) {
        const selectedRange = selectedRanges[0];
  
        // Include the next row in the selected range to handle holding on the
        // top part of the next row, which would be a no-op. The bottom part of
        // the previous row will be taken care of by the code below.
        selectedRange.update(selectedRange.start, selectedRange.end + 1);
  
        const rowNode = location === 'below' ? getLastRowFromLastList() : getRowNode(event);
        if (rowNode) {
          const targetIndex = getTargetIndex(event.clientY, rowNode);
          if (targetIndex > -1) {
            const targetRange = new Range(targetIndex, targetIndex + 1);
  
            // If the target index is inside the selected range, it's not allowed
            // to drop, as that would be a no-op.
            if (targetRange.contained(selectedRange)) {
              return false;
            }
          }
        }
      }
    }
  
    return true;
  }
  
  /**
   * Get the drop target element that is matching the event target and the drag
   * MIME types of the event. This will start with the event target and step
   * outwards in the DOM tree to find the first matching element.
   *
   * @param {MouseEvent} event Some kind of drag and drop event.
   *
   * @return {HTMLElement?} The element or null if not found.
   */
  function getMatchingTarget(event) {
    let currentNode = event.target;
  
    while (currentNode) {
      if (isMatchingTarget(event, currentNode)) {
        return currentNode;
      }
      currentNode = currentNode.parentElement;
    }
  
    return null;
  }
  
  /**
   * Get the list element that is matching the event target and the drag MIME
   * types of the event. This will start with the event target and step outwards
   * in the DOM tree to find the first matching element.
   *
   * @param {MouseEvent} event Some kind of drag and drop event.
   *
   * @return {HTMLElement?} The list element or null if not found.
   */
  function getMatchingListTarget(event) {
    let currentNode = event.target;
  
    while (currentNode) {
      const isList = $(currentNode).matches(SELECTOR_LIST);
      if (isList && isMatchingTarget(event, currentNode)) {
        return currentNode;
      }
      currentNode = currentNode.parentElement;
    }
  
    return null;
  }
  
  /**
   * Get a valid list target from the end of body.
   *
   * @param {MouseEvent} event Some kind of drag and drop event.
   *
   * @return {HTMLElement?} The list element or null if not found.
   */
  function getListTargetFromBody(event) {
    const lastList = getLastListNode();
    if (lastList) {
      const sameParent = event.target.contains(lastList);
      if (sameParent && isMatchingTarget(event, lastList)) {
        if (isBelowNode(event.clientX, event.clientY, lastList)) {
          return lastList;
        }
      }
    }
  
    return null;
  }
  
  /**
   * Get the list row element from the event target.
   * If the event target itself isn't a list row, it will step out in the parent
   * tree until it finds a row.
   *
   * @param {MouseEvent} event Some kind of drag and drop event.
   *
   * @return {HTMLElement?} The list row element, or null if not found.
   */
  function getRowNode(event) {
    return event.target.closest(SELECTOR_LIST_ITEM);
  }
  
  /**
   * Get the last list row from the last list in the DOM.
   *
   * @return {HTMLElement?} The list row element, or null if not found.
   */
  function getLastRowFromLastList() {
    const lastList = getLastListNode();
    if (lastList) {
      const listItems = $(lastList).search(SELECTOR_LIST_ITEM);
      const lastItem = listItems && listItems[listItems.length - 1];
      if (lastItem) return lastItem;
    }
  
    return null;
  }
  
  /**
   * Get the last list in the DOM.
   *
   * @return {HTMLElement?} The list element, or null if not found.
   */
  function getLastListNode() {
    const lists = document.querySelectorAll(SELECTOR_LIST);
    const lastList = lists && lists[lists.length - 1];
    return lastList || null;
  }
  
  /**
   * Get the target index based on a cursor position and the row that the cursor
   * is on.
   *
   * @param {number} y The Y position relative to the viewport.
   * @param {HTMLElement} rowNode The DOM node for the row.
   *
   * @return {number} The target index or -1 if not found.
   */
  function getTargetIndex(y, rowNode) {
    let index = rowNode.getAttribute('data-index');
  
    if (index === null) {
      index = -1;
    } else {
      index = parseInt(index, 10);
  
      if (isNaN(index)) {
        index = -1;
      } else {
        // If cursor is in the bottom part of the row, dropping will place items
        // after the row, so we need to increment the index.
        if (isBelowMiddleOfNode(y, rowNode)) {
          index++;
        }
      }
    }
  
    return index;
  }
  
  /**
   * Get the indices of the rows to move.
   *
   * @param {string} contextUri The context URI.
   * @param {LiveList} liveList The live list to be changed.
   *
   * @return {Array.<number>} Array of indices.
   */
  function getMoveIndices(contextUri, liveList) {
    // As we need the indices, we must use the current selection state,
    // since the list might have changed since the start of the drag.
    // We are only in this function if we are dragging in the same
    // context, so the current selection state will always be valid.
    const selectionState = selection.getIndicesPerList();
    if (selectionState.length !== 1) return [];
  
    const firstContainer = selectionState[0];
    const selectedIndices = firstContainer.indices;
    const indices = selectedIndices.slice();
  
    if (indices.length === 0) return [];
  
    hooks.run('get-move-indices', contextUri, liveList, indices);
  
    // The indices must be sorted for the utility that creates the list operations
    indices.sort(function (a, b) {
      return a - b;
    });
  
    return indices;
  }
  
  /**
   * Get the values to use for insertion in the list. This will get the event data
   * for the matching MIME types.
   *
   * @param {MouseEvent} event A drop event.
   * @param {HTMLElement} list The list DOM node.
   * @param {LiveList} liveList The live list for the context.
   * @param {string} contextUri The context URI for the list.
   * @param {Function} callback A callback function. The data argument will be an
   *     array of object values.
   */
  function getInsertValues(event, list, liveList, contextUri, callback) {
    const eventTypes = event.dataTransfer.types;
    const listTypes = list.getAttribute(ATTR_TARGET);
    if (!listTypes) {
      callback(null, []);
      return;
    }
  
    const matchingTypes = intersection(listTypes.split('|'), eventTypes);
    if (matchingTypes.length === 0) {
      callback(null, []);
      return;
    }
  
    const data = getDataFromTypes(event, matchingTypes);
  
    const isTrackList = isListOfTracks(liveList, contextUri);
  
    getRowsFromData(data, isTrackList, function (error, rows) {
      if (error) {
        callback(error);
        return;
      }
  
      callback(null, rows);
    });
  }
  
  /**
   * Get the event data associated with the provided MIME types.
   *
   * @param {MouseEvent} event Some kind of drag and drop event.
   * @param {Array.<string>} types Array of MIME types.
   *
   * @return {Object.<string, Array>} Object where key is MIME type and value is
   *     array of values (URIs).
   */
  function getDataFromTypes(event, types) {
    const data = {};
  
    for (let i = 0, l = types.length; i < l; i++) {
      const type = types[i];
      const itemsInData = data[type] = [];
  
      // A MIME type that starts with text/x-spotify is always containing a list
      // of Open URLs separated by '\n'.
      const hasURIs = type.indexOf('text/x-spotify') === 0;
  
      const dataForType = event.dataTransfer.getData(type);
      const items = hasURIs ? dataForType.split('\n') : [dataForType];
      for (let n = 0, len = items.length; n < len; n++) {
        const item = items[n];
        if (hasURIs) {
          const uriObject = liburi.from(item);
          if (uriObject) {
            itemsInData.push(uriObject.toURI());
          }
        } else {
          itemsInData.push(item);
        }
      }
    }
  
    return data;
  }
  
  /**
   * Get list row data from the event data. This might expand items if the list
   * is a track list (dropping an album into a track list will expand the album
   * into its tracks and return track rows instead).
   *
   * @param {Object.<string, Array>} data Data from drop event.
   * @param {boolean} isTrackList True if the list is a track list.
   * @param {Function} callback A callback function. The data argument will be an
   *     array of object values.
   */
  function getRowsFromData(data, isTrackList, callback) {
    const promised = [];
  
    if (data['text/x-spotify-tracks']) {
      promised.push(getRowObjects('track', data['text/x-spotify-tracks']));
    }
  
    if (data['text/x-spotify-albums']) {
      const albums = data['text/x-spotify-albums'];
      if (isTrackList) {
        promised.push(fetchListTrackRows(albums));
      } else {
        promised.push(getRowObjects('album', albums));
      }
    }
  
    if (data['text/x-spotify-playlists']) {
      const playlists = data['text/x-spotify-playlists'];
      if (isTrackList) {
        promised.push(fetchListTrackRows(playlists));
      } else {
        promised.push(getRowObjects('playlist', playlists));
      }
    }
  
    if (data['text/x-spotify-artists']) {
      promised.push(getRowObjects('artist', data['text/x-spotify-artists']));
    }
  
    if (data['text/x-spotify-users']) {
      promised.push(getRowObjects('user', data['text/x-spotify-users']));
    }
  
    Promise.all(promised).then(function (arrays) {
      let rows = [];
      rows = rows.concat.apply(rows, arrays);
      callback(null, rows);
    }, function (error) {
      callback(error);
    });
  }
  
  /**
   * Fetch the track rows from a set of list URIs. This is for the expansion of
   * for example an album into the tracks of the album.
   *
   * @param {Array.<string>} listUris Array of list URIs.
   */
  function fetchListTrackRows(listUris) {
    const promised = listUris.map(function (listUri) {
      return new Promise(function (resolve, reject) {
        live(listUri).query('rows(track(uri))', function (error, data) {
          if (error) {
            reject(error);
            return;
          }
          const rows = data.rows.map(function (row) {
            return live(row);
          });
          resolve(rows);
        }, live.ASAP);
      });
    });
  
    return Promise.all(promised).then(function (arrays) {
      let rows = [];
      rows = rows.concat.apply(rows, arrays);
      return rows;
    });
  }
  
  /**
   * Fetch the live object for the row of an item.
   *
   * @param {string} itemType The type of an item (for example 'track').
   * @param {Array.<string>} uris Array of item URIs.
   *
   * @return {Array.<LiveObject>} Array of row live objects.
   */
  function getRowObjects(itemType, uris) {
    return uris.map(function (uri) {
      const row = {};
      row[itemType] = { uri: uri };
      return live(row);
    });
  }
  
  /**
   * Check if the list is a track list.
   *
   * @param {LiveList} liveList The live list.
   * @param {string} contextUri The context URI for the list.
   *
   * @return {boolean} True if the list is a track list.
   */
  function isListOfTracks(liveList, contextUri) {
    if (liburi.isPlaylistV1OrV2(contextUri)) {
      return true;
    }
  
    const result = { valid: false };
    hooks.run('is-list-of-tracks', result, liveList, contextUri);
  
    if (!result.valid) {
      const firstRow = liveList.get(0);
      const firstTrack = firstRow && firstRow.get('track');
      if (firstTrack) return true;
    }
  
    return !!result.valid;
  }
  
  /**
   * Check if the position is below the bottom edge of the node.
   *
   * @param {number} x The X position relative to the viewport.
   * @param {number} y The Y position relative to the viewport.
   * @param {HTMLElement} node DOM node to check against.
   *
   * @return {boolean} True if the position is below.
   */
  function isBelowNode(x, y, node) {
    const rect = node.getBoundingClientRect();
    const below = y >= rect.bottom;
    const betweenX = x >= rect.left && x <= rect.right;
    return below && betweenX;
  }
  
  /**
   * Check if the position is below the middle of the node.
   *
   * @param {number} y The Y position relative to the viewport.
   * @param {HTMLElement} node DOM node to check against.
   *
   * @return {boolean} True if the position is below.
   */
  function isBelowMiddleOfNode(y, node) {
    const rect = node.getBoundingClientRect();
    return y >= rect.top + rect.height / 2;
  }
  
  /**
   * Check if the MIME types in the drag event matches the MIME types for the
   * specified drop target.
   *
   * @param {MouseEvent} event Some kind of drag and drop event.
   * @param {HTMLElement} node A DOM node.
   *
   * @return {boolean} True if the element matches the event.
   */
  function isMatchingTarget(event, node) {
    const eventTypes = event.dataTransfer.types;
    const nodeTypes = node.getAttribute(ATTR_TARGET);
  
    if (nodeTypes) {
      const matching = intersection(nodeTypes.split('|'), eventTypes);
      if (matching.length > 0) return true;
    }
  
    return false;
  }
  
  /**
   * Temporarily disable drag and dropping inside a list until the move or insert
   * is done. This is to prevent multiple operations to be called before the
   * response from the first comes back. We consider the operation done when we
   * receive the next update event, since it's very tricky to know when this
   * specific move is done. To prevent possible errors with being disabled
   * infinitely, we will enable it again after a certain timeout. This could
   * happen for example if the live list is switched out for a new list, in which
   * case we won't get the update event.
   *
   * @param {string} contextUri The context URI.
   * @param {LiveList} liveList The live list that was changed.
   */
  function temporarilyDisableList(contextUri, liveList) {
    disabledLists[contextUri] = true;
  
    const handler = function (t) {
      return () => {
        clearTimeout(t);
        liveList.off('update', handler);
  
        // Wait slighly longer to allow the change to trickle down to DOM
        setTimeout(function () {
          delete disabledLists[contextUri];
        }, 100);
      };
    };
  
    const timer = setTimeout(() => {
      handler(timer);
    }, 500);
  
    liveList.on('update', handler(timer));
  }
  
  /**
   * Attach the module.
   */
  function attach() {
    eventModel.delegate('dragstart', SELECTOR_DRAGGABLE, onDragStart);
    eventModel.delegate('dragover', SELECTOR_LIST_TARGET, onDragOver);
    eventModel.delegate('dragleave', SELECTOR_LIST_TARGET, onDragLeave);
    eventModel.delegate('dragend', 'body', onDragEnd);
    eventModel.delegate('drop', SELECTOR_LIST_TARGET, onDrop);
  }
  
  /**
   * Detach the module.
   */
  function detach() {
    eventModel.undelegate('dragstart', SELECTOR_DRAGGABLE, onDragStart);
    eventModel.undelegate('dragover', SELECTOR_LIST_TARGET, onDragOver);
    eventModel.undelegate('dragleave', SELECTOR_LIST_TARGET, onDragLeave);
    eventModel.undelegate('dragend', 'body', onDragEnd);
    eventModel.undelegate('drop', SELECTOR_LIST_TARGET, onDrop);
  }
  
  exports.attach = attach;
  exports.detach = detach;
  
  },{"../../../libs/live-list-operations":117,"../../live":172,"../../range2":204,"../../spotify-elements":210,"../selection":278,"./util/eventmodel":231,"./util/hooks":232,"mout/array/intersection":441,"spotify-liburi":553}],230:[function(require,module,exports){
  (function (global){
  /**
   * @module spotify-events/dragndrop/target
   * @private
   */
  'use strict';
  
  const $ = require('../../spotify-elements');
  const liburi = require('spotify-liburi');
  const defer = require('../../../libs/prime/defer');
  
  const center = require('../center');
  const eventModel = require('./util/eventmodel');
  const hooks = require('./util/hooks');
  const getLogContext = require('../util/get-log-context');
  
  const SELECTOR_DROP_TARGET = '[data-drop-target]';
  const SELECTOR_DRAGGABLE = 'a, [draggable]';
  const ATTR_TARGET = 'data-drop-target';
  const ATTR_TARGET_INCLUDE_TEXT = 'data-drop-target-include-text';
  const ATTR_NO_CLASSNAME = 'data-drop-no-classname';
  const CLASSNAME_ACTIVE = 'drop-target-active';
  const TEXT_PLAIN = 'text/plain';
  
  let longHoldTimerId = 0;
  let longHoldTimerDuration = 1000;
  let draggedElement = null;
  let lastEnteredElement = null;
  
  /**
   * Handler for starting to drag.
   *
   * @param {MouseEvent} event A dragstart event object.
   * @param {HTMLElement} node The draggable DOM element.
   */
  function onDragStart(event, node) {
    // Don't handle items that are specified with the new implementation.
    if (node.closest('[data-glue-draggable]')) {
      return;
    }
  
    draggedElement = node;
    lastEnteredElement = null;
  }
  
  /**
   * Handler for ending a drag.
   */
  function onDragEnd() {
    draggedElement = null;
    lastEnteredElement = null;
  
    stopLongHoldTimer();
    removeAnyActiveClass();
  }
  
  /**
   * Handler for dragging into a drop target.
   *
   * @param {MouseEvent} event A dragenter event object.
   * @param {HTMLElement} target DOM element for the drop target.
   */
  function onDragEnter(event, target) {
    if (validateTarget(event, target)) {
      setEnter(event, target);
    }
  }
  
  /**
   * Handler for dragging out from a drop target.
   *
   * @param {MouseEvent} event A dragleave event object.
   * @param {HTMLElement} target DOM element for the drop target.
   */
  function onDragLeave(event, target) {
    if (validateTarget(event, target)) {
      setLeave(event, target);
    }
  }
  
  /**
   * Handler for dragging over a drop target.
   *
   * @param {MouseEvent} event A dragover event object.
   * @param {HTMLElement} target DOM element for the drop target.
   */
  function onDragOver(event, target) {
    if (validateTarget(event, target)) {
      // Tell the event that we are accepting drops on this target
      event.preventDefault();
  
      // Since other code might have set the dropEffect to something else,
      // we need to override that, since we're now accepting drops
      event.dataTransfer.dropEffect = 'copy';
  
      setEnter(event, target);
  
      // If the target is not valid, remove any styling for active drop target
    } else {
      setLeave(event, target);
    }
  }
  
  /**
   * Handler for dropping on a drop target.
   *
   * @param {MouseEvent} event A drop event object.
   * @param {HTMLElement} target DOM element for the drop target.
   */
  function onDrop(event, target) {
    stopLongHoldTimer();
  
    // Dropping should remove active target class name.
    // Dropping outside of a target doesn't need this, as the target is only
    // active when hovering it.
    removeAnyActiveClass();
  
    if (validateTarget(event, target)) {
      const types = getMimeTypesForTarget(event, target);
      const dropData = getDropData(event, types);
      const sourceUri = event.dataTransfer.getData('text/x-spotify-data-context') || null;
      const sourceLogContext = event.dataTransfer.getData('text/x-spotify-data-log-context') || null;
      const sourceAppUri = event.dataTransfer.getData('text/x-spotify-data-app-uri') || null;
      let sourceIndex = parseInt(event.dataTransfer.getData('text/x-spotify-data-log-source-index'), 0);
      if (isNaN(sourceIndex)) sourceIndex = null;
  
      const targetUriNode = target.closest('[data-uri]');
      const targetUri = targetUriNode && targetUriNode.getAttribute('data-uri') || null;
      const targetLogContext = getLogContext(target);
      const targetAppUri = global.__spotify && global.__spotify.app_uri || null;
  
      // Give any other delegates a chance to handle the drop, and if so pass
      // that info along in the event.
      defer.immediate(function () {
        center.emit('drop', {
          handled: event.handled,
          node: target,
          sourceUri: sourceUri,
          targetUri: targetUri,
          data: dropData
        });
  
        const firstItem = dropData[Object.keys(dropData)[0]][0];
        const itemType = liburi.from(firstItem).type;
        const numberOfItems = Object.keys(dropData).reduce(function (sum, key) {
          return sum + dropData[key].length;
        }, 0);
        const targetIndex = event.data && event.data.targetIndex || null;
      });
    }
  
    // Prevent navigation on drop
    event.preventDefault();
  
    // Reset here since our multi-frame architecture means we're not guaranteed
    // to have gotten a dragstart event in this frame prior to dropping.
    lastEnteredElement = null;
  }
  
  /**
   * Set target to be entered, including setting style and sending event.
   *
   * @param {MouseEvent} event A dragenter event object.
   * @param {HTMLElement} target DOM element for the drop target.
   */
  function setEnter(event, target) {
    if (target === lastEnteredElement) {
      return;
    }
  
    lastEnteredElement = target;
  
    if (!target.hasAttribute(ATTR_NO_CLASSNAME)) {
      $(target).addClass(CLASSNAME_ACTIVE);
    }
  
    const types = getMimeTypesForTarget(event, target);
    center.emit('dragenter', { node: target, types: types });
  
    startLongHoldTimer(target, types);
  }
  
  /**
   * Set target to be left, including removing style and sending event.
   *
   * @param {MouseEvent} event A dragleave event object.
   * @param {HTMLElement} target DOM element for the drop target.
   */
  function setLeave(event, target) {
    if (target !== lastEnteredElement) {
      return;
    }
  
    lastEnteredElement = null;
  
    if (!target.hasAttribute(ATTR_NO_CLASSNAME)) {
      $(target).removeClass(CLASSNAME_ACTIVE);
    }
  
    const types = getMimeTypesForTarget(event, target);
    center.emit('dragleave', { node: target, types: types });
  
    stopLongHoldTimer();
  }
  
  /**
   * Check if the drop target matches the MIME types for the event.
   *
   * @param {MouseEvent} event Some kind of drag and drop event.
   * @param {HTMLElement} target DOM element for the drop target.
   *
   * @return {boolean} True if the target is valid.
   */
  function validateTarget(event, target) {
    // The element being dragged is not accepted as a drop target
    if (target === draggedElement) {
      return false;
    }
  
    const types = getMimeTypesForTarget(event, target);
  
    if (types.length) {
      const result = { valid: true };
      hooks.run('target-validation', result, event, target);
  
      if (result.valid) {
        return true;
      }
    }
  
    return false;
  }
  
  /**
   * Get the data associated with the drop, grouped by MIME type.
   * If the data contained Spotify URLs, they will be converted to Spotify URIs.
   *
   * @param {MouseEvent} event Event object for a drop event.
   * @param {Array.<string>} types Array of MIME types.
   *
   * @return {Object.<string, Array.<string>>} Data object of arrays of item
   *     URIs, grouped by MIME type.
   */
  function getDropData(event, types) {
    const data = {};
  
    for (let i = 0, l = types.length; i < l; i++) {
      const itemsInData = data[types[i]] = [];
      const dataForType = event.dataTransfer.getData(types[i]);
      const hasURIs = types[i].indexOf('text/x-spotify') === 0;
      const items = hasURIs ? dataForType.split('\n') : [dataForType];
  
      for (let n = 0, len = items.length; n < len; n++) {
        let item = items[n];
        if (hasURIs) {
          const uriObject = liburi.from(item);
          if (uriObject) item = uriObject.toURI();
        }
        if (item) itemsInData.push(item);
      }
    }
  
    const hasContextData = types.indexOf('text/x-spotify-data-context') > -1;
    if (hasContextData) {
      data['text/x-spotify-data-context'] = [event.dataTransfer.getData('text/x-spotify-data-context')];
    }
  
    return data;
  }
  
  /**
   * Start a timer for emitting an event for long-hold on a target.
   * If one is already started, it will use the previous timer and not restart it.
   *
   * @param {HTMLElement} target A DOM element for the drop target.
   * @param {Array.<string>} types Array of MIME types.
   */
  function startLongHoldTimer(target, types) {
    // Don't start a new timer if we already have one running
    if (longHoldTimerId) return;
  
    longHoldTimerId = setTimeout(function () {
      center.emit('dragover-long', { node: target, types: types });
    }, longHoldTimerDuration);
  }
  
  /**
   * Stop any running timer for emitting an event for long-hold on a target.
   */
  function stopLongHoldTimer() {
    clearTimeout(longHoldTimerId);
  
    // Reset the timer ID so we can know that there is no timer running
    longHoldTimerId = 0;
  }
  
  /**
   * Remove the active class name from any element that has it.
   */
  function removeAnyActiveClass() {
    const active = $(`.${CLASSNAME_ACTIVE}`);
    if (active) {
      active.removeClass(CLASSNAME_ACTIVE);
    }
  }
  
  /**
   * Get the MIME types that are matching both the drag event and the target.
   *
   * @param {MouseEvent} event Any kind of drag and drop event.
   * @param {HTMLElement} target A DOM element.
   *
   * @return {Array.<string>} Array of MIME types.
   */
  function getMimeTypesForTarget(event, target) {
    const mimeTypes = [];
    const dataTypes = event.dataTransfer.types;
  
    let targetTypes = target.getAttribute(ATTR_TARGET);
    // Even though the target may not accept just text/plain data, in can still
    // opt to receive that data along side the ones it accepts.
    // For example to get a list of mixed data types in order.
    const includeText = target.hasAttribute(ATTR_TARGET_INCLUDE_TEXT);
  
    if (targetTypes) {
      targetTypes = targetTypes.split('|');
  
      for (let i = 0, l = dataTypes.length; i < l; i++) {
        const type = dataTypes[i];
        if (targetTypes.indexOf(type) > -1) {
          mimeTypes.push(type);
        }
      }
  
      if (includeText && mimeTypes.length) {
        if (mimeTypes.indexOf(TEXT_PLAIN) === -1) {
          mimeTypes.push(TEXT_PLAIN);
        }
      }
    }
  
    return mimeTypes;
  }
  
  /**
   * Attach the module.
   */
  function attach() {
    eventModel.delegate('dragstart', SELECTOR_DRAGGABLE, onDragStart);
    eventModel.delegate('dragenter', SELECTOR_DROP_TARGET, onDragEnter);
    eventModel.delegate('dragleave', SELECTOR_DROP_TARGET, onDragLeave);
    eventModel.delegate('dragover', SELECTOR_DROP_TARGET, onDragOver);
    eventModel.delegate('dragend', 'body', onDragEnd);
    eventModel.delegate('drop', SELECTOR_DROP_TARGET, onDrop);
  }
  
  /**
   * Detach the module.
   */
  function detach() {
    eventModel.undelegate('dragstart', SELECTOR_DRAGGABLE, onDragStart);
    eventModel.undelegate('dragenter', SELECTOR_DROP_TARGET, onDragEnter);
    eventModel.undelegate('dragleave', SELECTOR_DROP_TARGET, onDragLeave);
    eventModel.undelegate('dragover', SELECTOR_DROP_TARGET, onDragOver);
    eventModel.undelegate('dragend', 'body', onDragEnd);
    eventModel.undelegate('drop', SELECTOR_DROP_TARGET, onDrop);
  }
  
  exports.attach = attach;
  exports.detach = detach;
  
  // Exporting for testing
  const originalDuration = longHoldTimerDuration;
  exports.setLongHoldDuration = function setLongHoldDuration(duration) {
    longHoldTimerDuration = duration;
  };
  exports.resetLongHoldDuration = function resetLongHoldDuration() {
    longHoldTimerDuration = originalDuration;
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../../libs/prime/defer":200,"../../spotify-elements":210,"../center":221,"../util/get-log-context":292,"./util/eventmodel":231,"./util/hooks":232,"spotify-liburi":553}],231:[function(require,module,exports){
  /**
   * @module spotify-events/dragndrop/util/eventmodel
   * @private
   *
   * This module is made to be an abstraction of the event model for drag and
   * drop. It also exposes a delegation interface which allows you to only
   * listen for events for targets matching a certain selector.
   *
   * The need for this abstraction is to simplify the code listening to drag
   * and drop events.
   *
   * - The raw events we get from the browser are sometimes not that easy to
   *   work with. For example, when moving from one target to another, we get
   *   a dragenter event before the dragleave event, which can cause confusion.
   *   This abstraction flips that around, so that you always get the dragleave
   *   event before the dragenter event.
   *
   * - The raw events also trigger for children. For example, if moving the
   *   cursor from one element to one of its children, we get a dragenter event
   *   for the child element and a dragleave for the outer element. This makes
   *   it difficult to highlight drop targets based on dragenter/dragleave.
   *   This abstraction helps by making sure the handler is not called if moving
   *   to a child element inside an element that matches the selector. There
   *   is an exception though when moving to a child that is also matching a
   *   selector, the outer element is then left and the child is entered.
   *
   * @example
   * function onDragEnter(event, target) {
   *   event instanceof MouseEvent; // true
   *   target instanceof HTMLElement; // true
   *   target.matches('[data-drop-target]'); // true
   * }
   *
   * // Start listening
   * eventmodel.delegate('dragenter', '[data-drop-target]', onDragEnter);
   *
   * // Stop listening
   * eventmodel.undelegate('dragenter', '[data-drop-target]', onDragEnter);
   */
  'use strict';
  
  const $ = require('../../../spotify-elements');
  
  const attached = {};
  const delegations = {};
  let movementHandlerCount = 0;
  
  // State variables
  let hasEntered = false;
  let lastEnteredElement = null;
  let lastEnterTarget = null;
  let lastEnterEvent = null;
  let wasLastEventEnter = false;
  
  /**
   * Reset the temporary state variables used while dragging.
   */
  function resetState() {
    hasEntered = false;
    lastEnteredElement = null;
    lastEnterTarget = null;
    lastEnterEvent = null;
    wasLastEventEnter = false;
  }
  
  /**
   * Generic handler for any event.
   * This function will look at the current delegations and run the handler
   * functions for the matching delegations.
   * Some events will be handled by calling a special handler, which in some cases
   * later will check for matching delegations.
   *
   * @param {Event} event Event object.
   */
  function onEvent(event) {
    const handler = getPrimaryHandler(event.type);
    if (handler) {
      handler(event);
    } else {
      handleDelegationsForEvent(event);
    }
  }
  
  /**
   * Handler for a dragenter event.
   * Depending on state, this might save the event for later and call the
   * handler at a later point, to ensure that the dragenter event is sent
   * after the dragleave.
   *
   * @param {MouseEvent} event Event object for a dragenter event.
   */
  function onPrimaryDragEnter(event) {
    wasLastEventEnter = true;
  
    if (hasEntered) {
      if (lastEnteredElement !== event.target) {
        lastEnterEvent = event;
      }
    } else {
      hasEntered = true;
      lastEnteredElement = event.target;
      handleDelegationsForEvent(event);
    }
  }
  
  /**
   * Handler for a dragleave event.
   * Depending on state, this might also trigger the handlers for a dragenter
   * event, if there was a saved event from before.
   *
   * @param {MouseEvent} event Event object for a dragleave event.
   */
  function onPrimaryDragLeave(event) {
    wasLastEventEnter = false;
    lastEnteredElement = null;
  
    handleDelegationsForEvent(event);
  
    if (hasEntered) {
      if (lastEnterEvent) {
        lastEnteredElement = lastEnterEvent.target;
        handleDelegationsForEvent(lastEnterEvent);
        lastEnterEvent = null;
      } else {
        hasEntered = false;
      }
    }
  }
  
  /**
   * Handler for a drop event.
   * This is handled by a special handler only to reset some state.
   *
   * @param {MouseEvent} event Event object for a drop event.
   */
  function onPrimaryDrop(event) {
    resetState();
    handleDelegationsForEvent(event);
  }
  
  /**
   * Handler for a dragend event.
   * This is handled by a special handler only to reset some state.
   *
   * @param {MouseEvent} event Event object for a drop event.
   */
  function onPrimaryDragEnd(event) {
    resetState();
    handleDelegationsForEvent(event);
  }
  
  /**
   * Delegation handler for a dragenter event.
   * After matching against stored delegations, we need to do some final checking
   * before calling the user handler.
   *
   * @param {MouseEvent} event Event object for a dragenter event.
   * @param {HTMLElement} target A DOM element.
   * @param {Function} handler The user handler.
   */
  function onDragEnter(event, target, handler) {
    if (target !== lastEnterTarget) {
      lastEnterTarget = target;
      callUserHandler(event, target, handler);
    }
  }
  
  /**
   * Delegation handler for a dragleave event.
   * After matching against stored delegations, we need to do some final checking
   * before calling the user handler.
   *
   * @param {MouseEvent} event Event object for a dragleave event.
   * @param {HTMLElement} target A DOM element.
   * @param {Function} handler The user handler.
   * @param {string} selector A CSS selector string.
   */
  function onDragLeave(event, target, handler, selector) {
    let shouldCallUserHandler = false;
  
    const lastElement = lastEnterEvent && lastEnterEvent.target;
    if (lastElement) {
      const isSameElement = target === lastElement;
      const isEnteringChild = !isSameElement && target.contains(lastElement);
      const isEnteringMatchingElement = $(lastElement).matches(selector);
      if (!isSameElement && (!isEnteringChild || isEnteringMatchingElement)) {
        shouldCallUserHandler = true;
      }
    }
  
    // Most often the sequence of events will be dragenter->dragenter->dragleave
    // when moving into one element and then into another element. But if the
    // sequence is just dragenter->dragleave, we should also call the user
    // handler. This would happen if there is no new entered element.
    if (!wasLastEventEnter) {
      shouldCallUserHandler = true;
    }
  
    if (shouldCallUserHandler) {
      lastEnterTarget = null;
      callUserHandler(event, target, handler);
    }
  }
  
  /**
   * Find matching delegations and call the handlers.
   *
   * @param {Event} event Event object.
   */
  function handleDelegationsForEvent(event) {
    const type = event.type;
    const delegationData = delegations[type];
  
    if (delegationData && delegationData.length > 0) {
      delegationData.forEach(function (data) {
        const selector = data.selector;
        // Check that the event.target is not the document element as it does not
        // have the closest function. This happens when dragging over scrollbars which
        // belong to the document.
        const matchingTarget = event.target !== document && event.target.closest(selector);
  
        if (matchingTarget) {
          const handler = getHandler(type) || callUserHandler;
          handler(event, matchingTarget, data.handler, selector);
        }
      });
    }
  }
  
  /**
   * Call the user handler for a delegation.
   *
   * @param {Event} event Event object.
   * @param {HTMLElement} target A DOM element.
   * @param {Function} handler The user handler.
   */
  function callUserHandler(event, target, handler) {
    handler.call(exports, event, target);
  }
  
  /**
   * Get any handler that needs to be called for a raw event before looking
   * for any delegations.
   *
   * @param {string} type Event type.
   *
   * @return {Function?} The handler function or null if no special handler
   *     should be called.
   */
  function getPrimaryHandler(type) {
    switch (type) {
      case 'dragenter':
        return onPrimaryDragEnter;
      case 'dragleave':
        return onPrimaryDragLeave;
      case 'dragend':
        return onPrimaryDragEnd;
      case 'drop':
        return onPrimaryDrop;
      default:
        break;
    }
    return null;
  }
  
  /**
   * Get any handler that needs to be called after matching delegations,
   * but before calling the user handler.
   *
   * @param {string} type Event type.
   *
   * @return {Function?} The handler function or null if no special handler
   *     should be called.
   */
  function getHandler(type) {
    switch (type) {
      case 'dragenter':
        return onDragEnter;
      case 'dragleave':
        return onDragLeave;
      default:
        break;
    }
    return null;
  }
  
  /**
   * Check if the event type is one of the events that need special handling
   * for movement (enter/leave events).
   *
   * @param {string} type Event type.
   *
   * @return {boolean} True if the type is a movement type, false otherwise.
   */
  function isMovementType(type) {
    switch (type) {
      case 'dragenter':
        return true;
      case 'dragleave':
        return true;
      case 'dragend':
        return true;
      case 'drop':
        return true;
      default:
        break;
    }
    return false;
  }
  
  /**
   * Attach a global event listener on document for the specified type.
   *
   * @param {string} type Event type.
   */
  function attach(type) {
    if (attached[type]) return;
    attached[type] = true;
    document.addEventListener(type, onEvent, false);
  }
  
  /**
   * Detach a global event listener on document for the specified type.
   *
   * @param {string} type Event type.
   */
  function detach(type) {
    if (!attached[type]) return;
    attached[type] = false;
    document.removeEventListener(type, onEvent, false);
  }
  
  /**
   * Start listening for an event via event delegation for the specified
   * selector.
   *
   * @example
   * var selector = '[data-drop-target]';
   * eventModel.delegate('dragenter', selector, function(event, target) {
   *   event instanceof MouseEvent; // true
   *   target instanceof HTMLElement; // true
   *   target.matches('[data-drop-target]'); // true
   * });
   */
  function delegate(type, selector, handler) {
    if (isMovementType(type)) {
      movementHandlerCount++;
      if (movementHandlerCount === 1) {
        attach('dragenter');
        attach('dragleave');
        attach('dragend');
        attach('drop');
      }
    } else if (!attached[type]) {
      attach(type);
    }
  
    if (!delegations[type]) delegations[type] = [];
  
    delegations[type].push({ selector: selector, handler: handler });
  }
  
  /**
   * Stop listening for an event via event delegation for the specified
   * selector and handler.
   *
   * @example
   * eventModel.undelegate('dragenter', '[data-drop-target]', handler);
   */
  function undelegate(type, selector, handler) {
    if (isMovementType(type)) {
      movementHandlerCount = Math.max(0, movementHandlerCount - 1);
      if (movementHandlerCount === 0) {
        detach('dragenter');
        detach('dragleave');
        detach('dragend');
        detach('drop');
      }
    }
  
    const delegationsForType = delegations[type];
    if (delegationsForType) {
      for (let i = 0, l = delegationsForType.length; i < l; i++) {
        const data = delegationsForType[i];
        if (data.selector === selector && data.handler === handler) {
          delegationsForType.splice(i, 1);
          break;
        }
      }
  
      if (!isMovementType && delegationsForType.length === 0) {
        detach(type);
      }
    }
  }
  
  exports.delegate = delegate;
  exports.undelegate = undelegate;
  
  },{"../../../spotify-elements":210}],232:[function(require,module,exports){
  /**
   * Hooks
   *
   * Almost like an event emitter. You can add hooks for different ids, and when
   * a hook for an ID is run, the handlers are called.
   *
   * @module spotify-events/dragndrop/util/hooks
   * @private
   */
  'use strict';
  
  const hooks = {};
  
  /**
   * Add a hook handler for a specific hook ID.
   *
   * @param {string} id The hook ID.
   * @param {function} handler The handler function. The hook sets the arguments.
   */
  function add(id, handler) {
    if (!hooks[id]) hooks[id] = [];
    hooks[id].push(handler);
  }
  
  /**
   * Remove a hook handler for a specific hook ID.
   *
   * @param {string} id The hook ID.
   * @param {function} handler The handler function.
   */
  function remove(id, handler) {
    if (hooks[id]) {
      const index = hooks[id].indexOf(handler);
      if (index > -1) {
        hooks[id].splice(index, 1);
      }
    }
  }
  
  /**
   * Run the hook handlers for a specific hook ID.
   *
   * @param {string} id The hook ID.
   */
  function run(id) {
    const handlers = hooks[id];
    if (handlers && handlers.length > 0) {
      const args = Array.prototype.slice.call(arguments, 1);
      for (let i = 0, l = handlers.length; i < l; i++) {
        handlers[i].apply(null, args);
      }
    }
  }
  
  exports.add = add;
  exports.remove = remove;
  exports.run = run;
  
  },{}],233:[function(require,module,exports){
  /**
   * @module spotify-events/dragndrop/util/itemtext
   * @private
   */
  'use strict';
  
  const live = require('../../../live');
  const liburi = require('spotify-liburi');
  
  /**
   * Get the text for a URI.
   *
   * @param {string} uri Any URI.
   *
   * @return {string} The text.
   */
  function getForUri(uri) {
    const uriObject = liburi.from(uri);
    if (!uriObject) return '';
  
    const type = uriObject.type;
  
    if (type === liburi.Type.TRACK || type === liburi.Type.LOCAL) {
      return getForTrack(uri);
    } else if (type === liburi.Type.ARTIST || type === liburi.Type.LOCAL_ARTIST) {
      return getForArtist(uri);
    } else if (type === liburi.Type.ALBUM || type === liburi.Type.LOCAL_ALBUM) {
      return getForAlbum(uri);
    } else if (type === liburi.Type.PLAYLIST || type === liburi.Type.PLAYLIST_V2) {
      return getForPlaylist(uri);
    } else if (type === liburi.Type.USER || type === liburi.Type.PROFILE) {
      return getForUser(uri);
    } else if (type === liburi.Type.FOLDER) {
      return getForFolder(uri);
    } else if (type === liburi.Type.STATION) {
      return getForStation(uri);
    } else if (type === liburi.Type.EPISODE) {
      return getForEpisode(uri);
    } else if (type === liburi.Type.SHOW) {
      return getForShow(uri);
    }
  
    return '';
  }
  
  /**
   * Get the text for a track URI.
   *
   * Examples:
   * Track Name – First Artist, Second Artist, Third Artist (and so on...)
   * Track Name
   *
   * @param {string} uri Track URI.
   *
   * @return {string} The text.
   */
  function getForTrack(uri) {
    const object = live(uri);
    const itemName = object.get('name');
    if (!itemName) return '';
  
    const artists = object.get('artists');
    if (!artists || artists.length === 0) return itemName;
  
    const artistNames = artists.get(0, artists.length).map(function (artist) {
      return artist.get('name');
    }).join(', ');
    if (!artistNames) return itemName;
  
    return separateDragTextItems(itemName, artistNames);
  }
  
  /**
   * Get the text for an artist URI.
   *
   * Examples:
   * Artist Name
   *
   * @param {string} uri Artist URI.
   *
   * @return {string} The text.
   */
  function getForArtist(uri) {
    return live(uri).get('name') || '';
  }
  
  /**
   * Get the text for an album URI.
   *
   * Examples:
   * Artist Name – Album Name
   * Album Name
   *
   * @param {string} uri Album URI.
   *
   * @return {string} The text.
   */
  function getForAlbum(uri) {
    const object = live(uri);
    const itemName = object.get('name');
    if (!itemName) return '';
  
    const artists = object.get('artists');
    if (!artists || artists.length === 0) return itemName;
  
    const artistName = artists.get(0).get('name');
    if (!artistName) return itemName;
  
    return separateDragTextItems(artistName, itemName);
  }
  
  /**
   * Get the text for a playlist URI.
   *
   * Examples:
   * Playlist Name – Owner Name
   * Playlist Name – owner_username
   * Playlist Name
   *
   * @param {string} uri Playlist URI.
   *
   * @return {string} The text.
   */
  function getForPlaylist(uri) {
    const object = live(uri);
    const itemName = object.get('name');
    if (!itemName) return '';
  
    const owner = object.get('owner');
    const ownerName = owner && (owner.get('name') || owner.get('username'));
    if (!ownerName) return itemName;
  
    return separateDragTextItems(itemName, ownerName);
  }
  
  /**
   * Get the text for a playlist URI.
   *
   * Examples:
   * User Name
   * username
   *
   * @param {string} uri Playlist URI.
   *
   * @return {string} The text.
   */
  function getForUser(uri) {
    const object = live(uri);
    const itemName = object.get('name');
    if (itemName) return itemName;
  
    const uriObject = liburi.fromString(uri);
    const username = uriObject && uriObject.username;
    return username || '';
  }
  
  /**
   * Get the text for a folder URI.
   *
   * Examples:
   * Folder Name
   *
   * @param {string} uri Folder URI.
   *
   * @return {string} The text.
   */
  function getForFolder(uri) {
    return live(uri).get('name');
  }
  
  /**
   * Get the text for a station URI.
   *
   * Examples:
   * Station Name
   *
   * @param {string} uri Station URI.
   *
   * @return {string} The text.
   */
  function getForStation(uri) {
    return live(uri).get('name');
  }
  
  /**
   * Get the text for a episode URI.
   *
   * Examples:
   * The Making of "Day of the Dead"
   *
   * @param {string} uri Episode URI.
   *
   * @return {string} The text.
   */
  function getForEpisode(uri) {
    return live(uri).get('name');
  }
  
  /**
   * Get the text for a show URI.
   *
   * Examples:
   * "Stuff You Should Know"
   *
   * @param {string} uri Show URI.
   *
   * @return {string} The text.
   */
  function getForShow(uri) {
    return live(uri).get('name');
  }
  
  /**
   * Separate two strings by the special dash ('en dash', &#8211;) we use.
   *
   * @param {string} item1 First string.
   * @param {string} item2 Second string.
   *
   * @return {string} Final string.
   */
  function separateDragTextItems(item1, item2) {
    return `${item1} – ${item2}`;
  }
  
  /**
   * Get the text for an element.
   *
   * @param {HTMLElement} target The target element being dragged.
   *
   * @return {string} The drag text.
   */
  function getForElement(target) {
    let title = target.getAttribute('data-drag-text');
    if (title) return title;
  
    let uri = target.getAttribute('data-uri');
    if (!uri) {
      const uriObject = liburi.from(target.getAttribute('href'));
      uri = uriObject ? uriObject.toURI() : '';
    }
    if (uri) {
      title = getForUri(uri);
      if (title) return title;
    }
  
    title = target.getAttribute('title');
    if (title) return title;
  
    return target.textContent;
  }
  
  exports.getForUri = getForUri;
  exports.getForElement = getForElement;
  
  },{"../../../live":172,"spotify-liburi":553}],236:[function(require,module,exports){
  (function (global){
  'use strict';
  
  var _glue = require('../glue');
  
  var _glue2 = _interopRequireDefault(_glue);
  
  var _frameUpdater = require('../frame-updater');
  
  var _cancellationTokenSource = require('../cancellation-token-source');
  
  var _cancellationTokenSource2 = _interopRequireDefault(_cancellationTokenSource);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  // Export _-prefixed functions for unit tests. This is so that we don't have to
  // repeat all the tests for the contents of these functions in all situations
  // it's run in and lets us test the functions as units and test that the various
  // situations call these functions.
  
  let isAttached = false;
  let isFontLoaded = false;
  let topBarHeight = 0;
  let headerHeight = 0;
  let lastHeaderHeight = 0;
  let stickyHeight = 0;
  let currentScrollY = 0;
  let lastScrollY = 0;
  let lastScrollYDiffs = [0, 0, 0, 0, 0];
  let lastLoadedImageNode = null;
  let scrollListenerId = 0;
  let needsTitleSizeUpdate = true;
  let lastUpdatedTitleNode = null;
  let lastUpdatedTitleContent = '';
  let fontPromise = null;
  let cancellationTokenSource = null;
  let frameRequestIds = [];
  let lastHeaderNode = null;
  let scrollNode = null;
  let lastDescriptionNode = null;
  let lastDescriptionMouseEnterHandler = null;
  let lastDescriptionMouseLeaveHandler = null;
  let fullDescriptionNode = null;
  
  exports._updateHeader = function _updateHeader() {
    const header = document.querySelector('[data-glue-page-header]');
  
    // Optimize to only run header updates when scrolling within the header area
    // at the top of the page, and also when the current scroll event is below the
    // header but the previous event was within the header.
    if (header !== lastHeaderNode || currentScrollY < headerHeight || lastScrollY < headerHeight || headerHeight !== lastHeaderHeight) {
      lastHeaderNode = header;
      lastHeaderHeight = headerHeight;
  
      removeDescriptionHandlers();
      addDescriptionHandlers();
  
      updateHeaderBackgroundPosition();
      updateHeaderAlphaMask();
      updateHeaderBackgroundScrollOverlay();
      updateHeaderBackgroundScrollOverlayEllipse();
      updateHeaderImageScaling();
    }
  };
  
  // This function runs first in each update cycle and sets variables scoped to
  // the module. Ideally each function would just read the value it needs, but for
  // performance reasons we do this once at the start of each update.
  exports._updateHeaderSizes = function _updateHeaderSizes() {
    const header = document.querySelector('[data-glue-page-header]');
    if (header) {
      const node = scrollNode === document ? document.documentElement : scrollNode;
      topBarHeight = parseInt(node.getAttribute('data-top-bar-height'), 10) || 0;
      headerHeight = header.offsetHeight;
      stickyHeight = parseInt(header.getAttribute('data-glue-page-header-sticky-height'), 10) || 0;
    } else {
      topBarHeight = 0;
      headerHeight = 0;
      stickyHeight = 0;
    }
  };
  
  function updateHeaderAlphaMask() {
    const headerContent = document.querySelector('[data-glue-page-header-content]');
    if (headerContent) {
      const maskHeight = 24;
      const offset1 = currentScrollY + topBarHeight;
      const offset2 = offset1 + maskHeight;
  
      headerContent.style.webkitMaskImage = `linear-gradient(to bottom, transparent ${offset1}px, black ${offset2}px)`;
    }
  }
  
  function updateHeaderBackgroundPosition() {
    const headerBackground = document.querySelector('[data-glue-page-header-background]');
    if (headerBackground) {
      const isSticky = currentScrollY > headerHeight - stickyHeight;
      headerBackground.style.position = isSticky ? 'fixed' : '';
      headerBackground.style.top = isSticky ? `-${headerHeight - stickyHeight}px` : '';
      // JSDom doesn't seem to support the `auto` keyword as a value for bottom
      // so we set it on a different property to be able to test it in unit tests.
      headerBackground.style._bottom = isSticky ? 'auto' : '';
      headerBackground.style.bottom = headerBackground.style._bottom;
      headerBackground.style.height = isSticky ? `${headerHeight}px` : '';
    }
  
    const headerImage = document.querySelector('[data-glue-page-header-background-image]');
    if (headerImage) {
      const currentTransform = headerImage.style.transform || '';
      headerImage.style.transform = `${currentTransform.replace(/translateY\(.*?\)/, '')} translateY(${Math.min(currentScrollY, headerHeight - stickyHeight)}px)`.trim();
    }
  }
  
  function updateHeaderBackgroundScrollOverlay() {
    const scrollOverlay = document.querySelector('[data-glue-page-header-background-scroll-overlay]');
    if (scrollOverlay) {
      const maxOpacity = 0.4;
      scrollOverlay.style.opacity = Math.round(Math.min(1, currentScrollY / (headerHeight - stickyHeight)) * maxOpacity * 1000) / 1000;
    }
  }
  
  function updateHeaderBackgroundScrollOverlayEllipse() {
    const scrollPercentage = Math.min(1, currentScrollY / (headerHeight - stickyHeight));
  
    const scrollOverlay = document.querySelector('[data-glue-page-header-background-scroll-overlay-ellipse]');
    if (scrollOverlay) {
      scrollOverlay.style.opacity = Math.round(Math.min(1, scrollPercentage * 2) * 1000) / 1000;
    }
  
    const scrollOverlayBackdrop = document.querySelector('[data-glue-page-header-background-scroll-overlay-ellipse-backdrop]');
    if (scrollOverlayBackdrop) {
      scrollOverlayBackdrop.style.opacity = Math.round(scrollPercentage * 1000) / 1000;
    }
  }
  
  function updateHeaderImageScaling() {
    const headerImage = document.querySelector('[data-glue-page-header-background-image]');
    if (headerImage) {
      const loader = document.querySelector('[data-glue-page-header-background-image-loader]');
      const isImageLoaded = loader ? loader.classList.contains('glue-page-header__background-image-loader--is-loaded') : false;
      if (isImageLoaded) {
        const maxScale = 1.07;
        const minScale = 1;
        const scrollPercentage = Math.min(1, currentScrollY / (headerHeight - stickyHeight));
        const scale = Math.round((maxScale - scrollPercentage * (maxScale - minScale)) * 1000) / 1000;
        const currentTransform = headerImage.style.transform || '';
        headerImage.style.transform = `${currentTransform.replace(/scale\(.*?\)/, '')} scale(${scale})`.trim();
      }
    }
  }
  
  function addDescriptionHandlers() {
    const headerNode = document.querySelector('[data-glue-page-header]');
    const descriptionNode = document.querySelector('[data-glue-page-header-description]');
    if (!headerNode || !descriptionNode) {
      return;
    }
  
    lastDescriptionNode = descriptionNode;
  
    lastDescriptionMouseEnterHandler = event => {
      // Only show the full description if it doesn't fit already.
      if (event.target.scrollHeight !== event.target.clientHeight) {
        const rect = event.target.getBoundingClientRect();
        fullDescriptionNode = event.target.cloneNode(true);
        fullDescriptionNode.addEventListener('mouseleave', lastDescriptionMouseLeaveHandler);
        fullDescriptionNode.classList.add('glue-page-header__full-description-overlay');
        fullDescriptionNode.style.top = `${rect.top}px`;
        fullDescriptionNode.style.left = `${rect.left}px`;
        fullDescriptionNode.style.width = `${rect.width}px`;
        headerNode.appendChild(fullDescriptionNode);
      }
    };
  
    lastDescriptionMouseLeaveHandler = event => {
      // Hide the full description if leaving the description area, but only if
      // the user is not hovering the full description or any link inside of it.
      if (fullDescriptionNode && event.relatedTarget !== fullDescriptionNode && !fullDescriptionNode.contains(event.relatedTarget)) {
        fullDescriptionNode.parentNode.removeChild(fullDescriptionNode);
        fullDescriptionNode.removeEventListener('mouseleave', lastDescriptionMouseLeaveHandler);
        fullDescriptionNode = null;
      }
    };
  
    lastDescriptionNode.addEventListener('mouseenter', lastDescriptionMouseEnterHandler);
    lastDescriptionNode.addEventListener('mouseleave', lastDescriptionMouseLeaveHandler);
  }
  
  function removeDescriptionHandlers() {
    if (fullDescriptionNode) {
      fullDescriptionNode.removeEventListener('mouseleave', lastDescriptionMouseLeaveHandler);
      fullDescriptionNode.parentNode.removeChild(fullDescriptionNode);
      fullDescriptionNode = null;
    }
  
    if (lastDescriptionMouseEnterHandler) {
      lastDescriptionNode.removeEventListener('mouseenter', lastDescriptionMouseEnterHandler);
      lastDescriptionMouseEnterHandler = null;
    }
  
    if (lastDescriptionMouseLeaveHandler) {
      lastDescriptionNode.removeEventListener('mouseleave', lastDescriptionMouseLeaveHandler);
      lastDescriptionMouseLeaveHandler = null;
    }
  
    lastDescriptionNode = null;
  }
  
  exports._updateTitleTypeSize = function _updateTitleTypeSize() {
    const title = document.querySelector('[data-glue-page-header-content] [data-glue-page-header-title]');
    if (title) {
      // Optimize performance by only updating if specifically needed (triggered
      // by resize for example) or if the title node is different or if title
      // content changed.
      const isNewNode = title !== lastUpdatedTitleNode;
      const isNewContent = title.textContent !== lastUpdatedTitleContent;
      if (title && (needsTitleSizeUpdate || isNewNode || isNewContent)) {
        needsTitleSizeUpdate = false;
        lastUpdatedTitleNode = title;
        lastUpdatedTitleContent = title.textContent;
  
        _glue2.default.responsiveTitleType.setSizeForTitle(title, {
          useLargeTitle: title.hasAttribute('data-glue-page-header-use-large-title')
        });
  
        // Force an update of sizes since it might have changed due to title size
        exports._updateHeaderSizes();
      }
    }
  };
  
  exports._updateScrollClassNames = function _updateScrollClassNames() {
    const header = document.querySelector('[data-glue-page-header]');
    if (header) {
      const hasNewHeaderNode = !header._hasBeenSeen;
  
      // Disable header animations when the header node changes. This solves the
      // problem where the header content would otherwise animate when switching
      // between playlists.
      if (hasNewHeaderNode) {
        header._hasBeenSeen = true;
        header.classList.add('glue-page-header--with-no-animations');
      } else {
        frameRequestIds.push((0, _frameUpdater.requestFrame)(null, () => {
          header.classList.remove('glue-page-header--with-no-animations');
        }));
      }
  
      lastScrollYDiffs.push(Math.abs(currentScrollY - lastScrollY));
      lastScrollYDiffs.shift();
  
      const threshold = 30;
      const isScrollingFast = lastScrollYDiffs.reduce((isFast, diff) => {
        return isFast || diff > threshold;
      }, false);
  
      // This allows the header to go into a fast scroll mode where the header
      // animations are tweaked a bit.
      if (isScrollingFast) {
        header.classList.add('glue-page-header--with-fast-scrolling');
      } else {
        header.classList.remove('glue-page-header--with-fast-scrolling');
      }
  
      if (global.onPageHeaderToggleFastScrolling) {
        global.onPageHeaderToggleFastScrolling(isScrollingFast);
      }
    }
  };
  
  exports._updateHeaderImageLoading = function _updateHeaderImageLoading() {
    const headerImage = document.querySelector('[data-glue-page-header-background-image]');
  
    if (headerImage) {
      if (headerImage === lastLoadedImageNode) {
        return;
      }
  
      const imageUrl = headerImage.getAttribute('data-glue-page-header-background-image-url');
  
      lastLoadedImageNode = headerImage;
  
      if (imageUrl) {
        const image = new window.Image();
        image.onload = () => {
          const loader = document.querySelector('[data-glue-page-header-background-image-loader]');
  
          if (!loader.classList.contains('glue-page-header__background-image-loader--is-loaded')) {
            loader.classList.add('glue-page-header__background-image-loader--is-loaded');
            loader.classList.add('glue-page-header__background-image-loader--with-loading-animation');
            updateHeaderImageScaling();
  
            loader.addEventListener('transitionend', function handler() {
              loader.removeEventListener('transitionend', handler);
              loader.classList.remove('glue-page-header__background-image-loader--with-loading-animation');
            });
          }
        };
        image.src = imageUrl;
      }
    }
  };
  
  exports._updateScrollValue = function _updateScrollValue() {
    currentScrollY = scrollNode === document ? window.scrollY : scrollNode.scrollTop;
  };
  
  exports._updateLastScrollValue = function _updateLastScrollValue() {
    lastScrollY = currentScrollY;
  };
  
  function handleResize() {
    needsTitleSizeUpdate = true;
  
    exports._ensureFontIsLoaded(error => {
      if (error) {
        console.error(error);
        return;
      }
      exports._updateTitleTypeSize();
      exports._updateHeaderSizes();
      exports._updateHeader();
    });
  }
  
  exports._ensureFontIsLoaded = function _ensureFontIsLoaded(callback) {
    if (isFontLoaded) {
      callback(null);
      return;
    }
    if (!fontPromise) {
      fontPromise = _glue2.default.responsiveTitleType.loadFont();
    }
    const token = cancellationTokenSource.token();
    fontPromise.then(() => {
      if (token.isCanceled()) {
        callback(token.operationCanceledError());
        return;
      }
      isFontLoaded = true;
      callback(null);
    }, fontPromiseError => {
      callback(token.errorIfCanceled() || fontPromiseError);
    });
  };
  
  exports.attach = function attach() {
    if (!isAttached) {
      isAttached = true;
      cancellationTokenSource = new _cancellationTokenSource2.default();
  
      if (!scrollNode) {
        scrollNode = document;
      }
  
      exports._ensureFontIsLoaded(error => error && console.error(error));
      exports._updateHeaderSizes();
  
      scrollListenerId = (0, _frameUpdater.addDebouncedScrollListener)(scrollNode, () => {
        exports._updateScrollValue();
      }, () => {
        exports._updateScrollClassNames();
        exports._updateHeader();
        exports._updateLastScrollValue();
      });
  
      window.addEventListener('resize', handleResize);
    }
  };
  
  exports.detach = function detach() {
    if (isAttached) {
      cancellationTokenSource.cancel();
      cancellationTokenSource = null;
  
      frameRequestIds.forEach(id => {
        (0, _frameUpdater.cancelFrame)(id);
      });
      frameRequestIds = [];
  
      removeDescriptionHandlers();
  
      isAttached = false;
      isFontLoaded = false;
      fontPromise = null;
      headerHeight = 0;
      stickyHeight = 0;
      currentScrollY = 0;
      lastScrollY = 0;
      lastScrollYDiffs = [0, 0, 0, 0, 0];
      needsTitleSizeUpdate = true;
      lastUpdatedTitleNode = null;
      lastUpdatedTitleContent = '';
      scrollNode = null;
      lastDescriptionNode = null;
      lastDescriptionMouseEnterHandler = null;
      lastDescriptionMouseLeaveHandler = null;
      fullDescriptionNode = null;
  
      (0, _frameUpdater.removeDebouncedScrollListener)(scrollListenerId);
      window.removeEventListener('resize', handleResize);
    }
  };
  
  exports.update = function update() {
    if (!isAttached) {
      return;
    }
  
    frameRequestIds.push((0, _frameUpdater.requestFrame)(() => {
      exports._updateScrollValue();
    }, () => {
      exports._updateScrollClassNames();
      exports._updateHeaderImageLoading();
    }));
  
    exports._ensureFontIsLoaded(error => {
      if (error) {
        console.error(error);
        return;
      }
      frameRequestIds.push((0, _frameUpdater.requestFrame)(() => {
        exports._updateHeaderSizes();
      }, () => {
        exports._updateTitleTypeSize();
        exports._updateHeader();
      }));
    });
  };
  
  // A way to let an app override the node to listen for scroll events on.
  // This has to be called before `attach` is called.
  exports.setScrollNode = function setScrollNode(node) {
    scrollNode = node;
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../cancellation-token-source":54,"../frame-updater":59,"../glue":82}],237:[function(require,module,exports){
  arguments[4][1][0].apply(exports,arguments)
  },{"../../../libs/spotify-i18n":312,"./json/cs.json":238,"./json/de.json":239,"./json/el.json":240,"./json/en.json":241,"./json/es-419.json":242,"./json/es.json":243,"./json/fi.json":244,"./json/fr-CA.json":245,"./json/fr.json":246,"./json/hu.json":247,"./json/id.json":248,"./json/it.json":249,"./json/ja.json":250,"./json/nl.json":251,"./json/pl.json":252,"./json/pt-BR.json":253,"./json/sv.json":254,"./json/th.json":255,"./json/tr.json":256,"./json/vi.json":257,"./json/zh-Hant.json":258,"./json/zsm.json":259,"dup":1}],238:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 položka",
      "other": "Počet položek: {0}"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 skladba",
      "other": "Počet skladeb: {0}"
    },
    "drag_tooltip_many_artists": {
      "one": "1 umělec",
      "other": "Počet umělců: {0}"
    },
    "drag_tooltip_many_albums": {
      "one": "1 album",
      "other": "Počet alb: {0}"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 playlist",
      "other": "{0} playlistů"
    },
    "drag_tooltip_many_users": {
      "one": "1 uživatel",
      "other": "Počet uživatelů: {0}"
    }
  }
  },{}],239:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 Element",
      "other": "{0} Elemente"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 Titel",
      "other": "{0} Titel"
    },
    "drag_tooltip_many_artists": {
      "one": "1 Künstler",
      "other": "{0} Künstler"
    },
    "drag_tooltip_many_albums": {
      "one": "1 Album",
      "other": "{0} Alben"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 Playlist",
      "other": "{0} Playlists"
    },
    "drag_tooltip_many_users": {
      "one": "1 User",
      "other": "{0} User"
    }
  }
  },{}],240:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 στοιχείο",
      "other": "{0} στοιχεία"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 κομμάτι",
      "other": "{0} κομμάτια"
    },
    "drag_tooltip_many_artists": {
      "one": "1 καλλιτέχνης",
      "other": "{0} καλλιτέχνες"
    },
    "drag_tooltip_many_albums": {
      "one": "1 άλμπουμ",
      "other": "{0} άλμπουμ"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 playlist",
      "other": "{0} λίστες"
    },
    "drag_tooltip_many_users": {
      "one": "1 χρήστης",
      "other": "{0} χρήστες"
    }
  }
  },{}],241:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 item",
      "other": "{0} items"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 track",
      "other": "{0} tracks"
    },
    "drag_tooltip_many_artists": {
      "one": "1 artist",
      "other": "{0} artists"
    },
    "drag_tooltip_many_albums": {
      "one": "1 album",
      "other": "{0} albums"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 playlist",
      "other": "{0} playlists"
    },
    "drag_tooltip_many_users": {
      "one": "1 user",
      "other": "{0} users"
    }
  }
  },{}],242:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 elemento",
      "other": "{0} elementos"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 canción",
      "other": "{0} canciones"
    },
    "drag_tooltip_many_artists": {
      "one": "1 artista",
      "other": "{0} artistas"
    },
    "drag_tooltip_many_albums": {
      "one": "1 álbum",
      "other": "{0} álbumes"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 playlist",
      "other": "Playlists de {0}"
    },
    "drag_tooltip_many_users": {
      "one": "1 usuario",
      "other": "{0} usuarios"
    }
  }
  },{}],243:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 elemento",
      "other": "{0} elementos"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 canción",
      "other": "{0} canciones"
    },
    "drag_tooltip_many_artists": {
      "one": "1 artista",
      "other": "{0} artistas"
    },
    "drag_tooltip_many_albums": {
      "one": "1 álbum",
      "other": "{0} álbumes"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 playlist",
      "other": "{0} playlists"
    },
    "drag_tooltip_many_users": {
      "one": "1 usuario",
      "other": "{0} usuarios"
    }
  }
  },{}],244:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 kohde",
      "other": "{0} kohdetta"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 kappale",
      "other": "{0} kappaletta"
    },
    "drag_tooltip_many_artists": {
      "one": "1 artisti",
      "other": "{0} artistia"
    },
    "drag_tooltip_many_albums": {
      "one": "1 albumi",
      "other": "{0} albumia"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 soittolista",
      "other": "{0} soittolistaa"
    },
    "drag_tooltip_many_users": {
      "one": "1 käyttäjä",
      "other": "{0} käyttäjää"
    }
  }
  },{}],245:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 article",
      "other": "{0} articles"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 chanson",
      "other": "{0} chansons"
    },
    "drag_tooltip_many_artists": {
      "one": "1 artiste",
      "other": "{0} artistes"
    },
    "drag_tooltip_many_albums": {
      "one": "1 album",
      "other": "{0} albums"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 liste de lecture",
      "other": "{0} listes de lecture"
    },
    "drag_tooltip_many_users": {
      "one": "1 utilisateur",
      "other": "{0} utilisateur"
    }
  }
  },{}],246:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 élément",
      "other": "{0} éléments"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 titre",
      "other": "{0} titres"
    },
    "drag_tooltip_many_artists": {
      "one": "1 artiste",
      "other": "{0} artistes"
    },
    "drag_tooltip_many_albums": {
      "one": "1 album",
      "other": "{0} albums"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 playlist",
      "other": "{0} playlists"
    },
    "drag_tooltip_many_users": {
      "one": "1 utilisateur",
      "other": "{0} utilisateurs"
    }
  }
  },{}],247:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 elem",
      "other": "{0} elem"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 dal",
      "other": " {0} dal"
    },
    "drag_tooltip_many_artists": {
      "one": "1 előadó",
      "other": "{0} előadó"
    },
    "drag_tooltip_many_albums": {
      "one": "1 album",
      "other": "{0} album"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 műsorlista",
      "other": "{0} lejátszási lista"
    },
    "drag_tooltip_many_users": {
      "one": "1 felhasználó",
      "other": "{0} felhasználó"
    }
  }
  },{}],248:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 item",
      "other": "{0} item"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 lagu",
      "other": "{0} lagu"
    },
    "drag_tooltip_many_artists": {
      "one": "1 artis",
      "other": "{0} artis"
    },
    "drag_tooltip_many_albums": {
      "one": "1 album",
      "other": "{0} album"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 playlist",
      "other": "{0} playlist"
    },
    "drag_tooltip_many_users": {
      "one": "1 pengguna",
      "other": "{0} pengguna"
    }
  }
  },{}],249:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 elemento",
      "other": "{0} elementi"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 brano",
      "other": "{0} brani"
    },
    "drag_tooltip_many_artists": {
      "one": "1 artista",
      "other": "{0} artisti"
    },
    "drag_tooltip_many_albums": {
      "one": "1 album",
      "other": "{0} album"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 playlist",
      "other": "{0} playlist"
    },
    "drag_tooltip_many_users": {
      "one": "1 utente",
      "other": "{0} utenti"
    }
  }
  },{}],250:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1個のアイテム",
      "other": "{0}個のアイテム"
    },
    "drag_tooltip_many_tracks": {
      "one": "1曲",
      "other": "{0}曲"
    },
    "drag_tooltip_many_artists": {
      "one": "1人のアーティスト",
      "other": "{0}人のアーティスト"
    },
    "drag_tooltip_many_albums": {
      "one": "1枚のアルバム",
      "other": "{0}枚のアルバム"
    },
    "drag_tooltip_many_playlists": {
      "one": "1つのプレイリスト",
      "other": "{0}個のプレイリスト"
    },
    "drag_tooltip_many_users": {
      "one": "1人のユーザー",
      "other": "{0}人のユーザー"
    }
  }
  },{}],251:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 item",
      "other": "{0} items"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 nummer",
      "other": "{0} nummers"
    },
    "drag_tooltip_many_artists": {
      "one": "1 artist",
      "other": "{0} artiesten"
    },
    "drag_tooltip_many_albums": {
      "one": "1 album",
      "other": "{0} albums"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 afspeellijst",
      "other": "{0} afspeellijsten"
    },
    "drag_tooltip_many_users": {
      "one": "1 gebruiker",
      "other": "{0} gebruikers"
    }
  }
  },{}],252:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 element",
      "other": "Elementy: {0}"
    },
    "drag_tooltip_many_tracks": {
      "one": "1utwór",
      "other": "Liczba utworów: {0}"
    },
    "drag_tooltip_many_artists": {
      "one": "1 wykonawca",
      "other": "Wykonawcy: {0}"
    },
    "drag_tooltip_many_albums": {
      "one": "1 album",
      "other": "Albumy: {0}"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 playlista",
      "other": "{0} playlisty"
    },
    "drag_tooltip_many_users": {
      "one": "1 użytkownik",
      "other": "Użytkownicy: {0}"
    }
  }
  },{}],253:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 item",
      "other": "{0} itens"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 faixa",
      "other": "{0} faixas"
    },
    "drag_tooltip_many_artists": {
      "one": "1 artista",
      "other": "{0} artistas"
    },
    "drag_tooltip_many_albums": {
      "one": "1 álbum",
      "other": "{0} álbuns"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 playlist",
      "other": "{0} playlists"
    },
    "drag_tooltip_many_users": {
      "one": "1 usuário",
      "other": "{0} usuários"
    }
  }
  },{}],254:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 objekt",
      "other": "{0} objekt"
    },
    "drag_tooltip_many_tracks": {
      "one": "Ett spår",
      "other": "{0} spår"
    },
    "drag_tooltip_many_artists": {
      "one": "1 artist",
      "other": "{0} artister"
    },
    "drag_tooltip_many_albums": {
      "one": "1 album",
      "other": "{0} album"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 spellista",
      "other": "{0} spellistor"
    },
    "drag_tooltip_many_users": {
      "one": "1 användare",
      "other": "{0} användare"
    }
  }
  },{}],255:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 รายการ",
      "other": "{0} รายการ"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 แทร็ก",
      "other": "{0} แทร็ก"
    },
    "drag_tooltip_many_artists": {
      "one": "ศิลปิน 1 ราย",
      "other": "ศิลปิน {0} ราย"
    },
    "drag_tooltip_many_albums": {
      "one": "1 อัลบั้ม",
      "other": "{0} อัลบั้ม"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 เพลย์ลิสต์",
      "other": "{0} เพลย์ลิสต์"
    },
    "drag_tooltip_many_users": {
      "one": "ผู้ใช้ 1 คน",
      "other": "ผู้ใช้ {0} คน"
    }
  }
  },{}],256:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 Öge",
      "other": "{0} öge"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 parça",
      "other": "{0} parça"
    },
    "drag_tooltip_many_artists": {
      "one": "1 sanatçı",
      "other": "{0} sanatçı"
    },
    "drag_tooltip_many_albums": {
      "one": "1 albüm",
      "other": "{0} albüm"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 çalma listesi",
      "other": "{0} çalma listesi"
    },
    "drag_tooltip_many_users": {
      "one": "1 kullanıcı",
      "other": "{0} kullanıcı"
    }
  }
  },{}],257:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 mục",
      "other": "{0} mục"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 bản nhạc",
      "other": "{0} bản nhạc"
    },
    "drag_tooltip_many_artists": {
      "one": "1 nghệ sĩ",
      "other": "{0} nghệ sĩ"
    },
    "drag_tooltip_many_albums": {
      "one": "1 album",
      "other": "{0} album"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 playlist",
      "other": "{0} playlist"
    },
    "drag_tooltip_many_users": {
      "one": "1 người dùng",
      "other": "{0} người dùng"
    }
  }
  },{}],258:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 個項目",
      "other": "{0} 個項目"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 首歌曲",
      "other": "{0} 首歌曲"
    },
    "drag_tooltip_many_artists": {
      "one": "1 位藝人",
      "other": "{0} 位藝人"
    },
    "drag_tooltip_many_albums": {
      "one": "1 張專輯",
      "other": "{0} 張專輯"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 個播放清單",
      "other": "{0} 個播放清單"
    },
    "drag_tooltip_many_users": {
      "one": "1 位使用者",
      "other": "{0} 位使用者"
    }
  }
  },{}],259:[function(require,module,exports){
  module.exports={
    "drag_tooltip_many_items": {
      "one": "1 item",
      "other": "{0} item"
    },
    "drag_tooltip_many_tracks": {
      "one": "1 lagu",
      "other": "{0} lagu"
    },
    "drag_tooltip_many_artists": {
      "one": "1 artis",
      "other": "{0} artis"
    },
    "drag_tooltip_many_albums": {
      "one": "1 album",
      "other": "{0} album"
    },
    "drag_tooltip_many_playlists": {
      "one": "1 senarai main",
      "other": "{0} senarai main"
    },
    "drag_tooltip_many_users": {
      "one": "1 pengguna",
      "other": "{0} pengguna"
    }
  }
  },{}],260:[function(require,module,exports){
  'use strict';
// iopiop 
  const modules = [

    require('./lang'), //Set locale language code to document attributes
    require('./anchor'), //To navigate through the app sections/tabs
    require('./player'), //Get track, play track, handle play buttons
    require('./dragndrop'), //Handle drag and drop behaviour
    require('./add'), //Handle Add to library buttons
    require('./selection'), //Select behaviour
    require('./contextmenu'), //Send data to Context menu handler (in zlink)
    require('./sticky'), //Handle top bar when scrolling down
    require('./navbar'), //Section/tab handler
    require('./offline'), require('./tooltip'), require('./scroll'), require('./scrollbar'), 
    require('./header'), require('./list-quick-jump'), require('./cards'), 
    require('reddit-setting'), //Set Settings buttons action
    require('youtube-card') //Handle youtube cards (popover, backdrop)
  ];
  
  const events = {};
  
  let isAttached = false;
  
  events.update = function update(node) {
    if (!isAttached) return;
  
    modules.forEach(function (module) {
      module.update(node);
    });
  };
  
  events.attach = function attach() {
    if (isAttached) return;
    isAttached = true;
  
    modules.forEach(function (module) {
      module.attach();
    });
  };
  
  events.detach = function detach() {
    if (!isAttached) return;
    isAttached = false;
  
    modules.forEach(function (module) {
      module.detach();
    });
  };
  
  module.exports = events;
  
  },{
    "./add":213,"./anchor":214,"./cards":215,"./contextmenu":222,"./dragndrop":228,
    "./header":236, "./lang":261,"./list-quick-jump":262,
    "./navbar":265,"./offline":266, "./player":268,
    "./scroll":273,"./scrollbar":274,"./selection":278,"./sticky":284,
    "./tooltip":286,"reddit-setting":595,"youtube-card":600
}],261:[function(require,module,exports){
  'use strict';
  
  let isAttached = false;
  
  exports.update = () => {};
  
  exports.attach = () => {
    if (isAttached) return;
    isAttached = true;
  
    const locale = window.__spotify && window.__spotify.locale;
    if (locale) {
      document.documentElement.setAttribute('lang', locale);
    }
  };
  
  exports.detach = () => {
    if (!isAttached) return;
    isAttached = false;
  
    document.documentElement.removeAttribute('lang');
  };
  
  },{}],262:[function(require,module,exports){
  'use strict';
  
  const center = require('./center');
  const cosmos = require('./util/cosmos');
  
  const SPACE_KEYCODE = 32;
  const BACKSPACE_KEYCODE = 8;
  const DELETE_KEYCODE = 46;
  
  exports.attach = function attach() {
    document.addEventListener('keypress', onKeyPress, false);
    document.addEventListener('keydown', onKeyDown, false);
  };
  
  exports.detach = function detach() {
    document.removeEventListener('keypress', onKeyPress, false);
    document.removeEventListener('keydown', onKeyDown, false);
  };
  
  exports.update = function () {};
  
  const selector = ':not([data-sticky-active]) > [data-list-quick-jump]';
  const SEARCH_STRING_CONCATENATION_TIMEOUT = 1000;
  
  const searchStringsByUri = {};
  const searchTimeoutsByUri = {};
  
  // onKeyPress is needed to read characters properly
  function onKeyPress(event) {
    if (shouldEventBeIgnored(event)) return;
    processEvent(event);
  }
  
  // onKeyDown is needed to read <backspace>
  function onKeyDown(event) {
    if (shouldEventBeIgnored(event)) return;
  
    const isBackspace = event.keyCode === BACKSPACE_KEYCODE;
  
    if (!isBackspace) return;
    processEvent(event);
  }
  
  function processEvent(event) {
    const isBackspace = event.keyCode === BACKSPACE_KEYCODE;
  
    const letter = event.keyCode === SPACE_KEYCODE ? ' ' : String.fromCharCode(event.keyCode).trim();
    if (!letter && !isBackspace) return;
  
    const listNode = getListNode(event.target);
    if (!listNode) return;
  
    const uri = getUri(listNode);
    if (!uri) return;
  
    // No need to do anything if there is no active search and user presses
    // BACKSPACE, since that would temporarily disable using SPACE for toggling
    // music, even though no quick-jump was actually made (KM-10204)
    if (isBackspace && !searchStringsByUri[uri]) return;
  
    if (!(uri in searchStringsByUri)) searchStringsByUri[uri] = '';
  
    if (isBackspace) {
      searchStringsByUri[uri] = searchStringsByUri[uri].slice(0, -1);
    } else if (letter) {
      searchStringsByUri[uri] += letter;
    }
  
    center.emit('list-quick-jump-request', uri, searchStringsByUri[uri] || null);
  
    if (searchTimeoutsByUri[uri]) clearTimeout(searchTimeoutsByUri[uri]);
  
    if (event.keyCode === SPACE_KEYCODE) {
      event.preventDefault(); // Prevent space from scrolling page
    }
  
    const shortcutsToIgnore = [{ keyCode: SPACE_KEYCODE }, { keyCode: BACKSPACE_KEYCODE }, { keyCode: DELETE_KEYCODE }];
  
    // Tell container to ignore <space>, <backspace> and <delete> shortcuts
    // until further notice
    cosmos.post({
      url: 'sp://messages/v1/ignoreshortcuts',
      body: {
        action: 'ignore',
        shortcuts: shortcutsToIgnore
      }
    });
  
    searchTimeoutsByUri[uri] = setTimeout(function () {
      // Tell container to stop ignoring <space>, <backspace> and <delete>
      // shortcuts
      cosmos.post({
        url: 'sp://messages/v1/ignoreshortcuts',
        body: {
          action: 'unignore',
          shortcuts: shortcutsToIgnore
        }
      });
  
      delete searchStringsByUri[uri];
      delete searchTimeoutsByUri[uri];
      center.emit('list-quick-jump-request', uri, null);
    }, SEARCH_STRING_CONCATENATION_TIMEOUT);
  }
  
  function shouldEventBeIgnored(event) {
    return event.metaKey || event.ctrlKey || event.target.matches('input, textarea, [contenteditable]');
  }
  
  function getListNode(target) {
    let listNode = target.closest(selector);
    if (!listNode) {
      const listNodes = document.querySelectorAll(selector);
      const hasOnlyOneListNode = listNodes && listNodes.length === 1;
  
      if (hasOnlyOneListNode) {
        listNode = listNodes[0];
      }
    }
    return listNode;
  }
  
  function getUri(listNode) {
    const uriParent = listNode.closest('[data-uri]');
    return uriParent && uriParent.getAttribute('data-uri');
  }
  
  },{"./center":221,"./util/cosmos":290}],265:[function(require,module,exports){
  (function (global){
  'use strict';
  
  const $ = require('../spotify-elements');
  
  const live = require('../live');
  const center = require('./center');
  
  const NAVBAR_ATTR_NAME = 'data-navbar';
  const ITEM_ID_ATTR_NAME = 'data-navbar-item-id';
  const ITEM_MATCHER_ATTR_NAME = 'data-navbar-item-matcher';
  const VIEW_ID_ATTR_NAME = 'data-navbar-view-id';
  const ACTIVE_ID_ATTR_NAME = 'data-navbar-active-id';
  const HISTORY_POS_ATTR_NAME = 'data-navbar-history-position';
  const CLASSNAME_ACTIVE = 'active';
  const OVERFLOW_MENU_ID = 'navbar-overflow-menu';
  const STICKY_WRAPPER_ATTR_NAME = 'data-sticky-fixed-wrapper';
  
  const APPLICATION_MODEL_URI = 'spotify:application';
  
  const storedMatchers = {};
  
  let navbarOverflowItems;
  let isShowingNavbarOverflow;
  let shouldShowNavbarOverflow;
  
  const getSelector = function (name, optOperator, optMatcher) {
    if (optOperator && optMatcher) {
      return `[${name}${optOperator}"${optMatcher}"` + ']';
    }
    return `[${name}]`;
  };
  
  const updateOverflowMenu = function () {
    const moreMenuItem = document.querySelector('[data-navbar-item-id=navbar-overflow-menu]');
    if (!moreMenuItem) {
      return;
    }
  
    const canBeOpen = navbarOverflowItems && navbarOverflowItems.length;
    if (!canBeOpen) {
      shouldShowNavbarOverflow = false;
    }
  
    // There's no check for isShowingNavbarOverflow, the event is intentionally
    // resent on update, to support resizing.
    if (shouldShowNavbarOverflow) {
      isShowingNavbarOverflow = true;
      const descriptors = navbarOverflowItems.map(function (item) {
        const link = item.querySelector('a[href]');
  
        // The text is in the <a> element inside the item.
        return {
          name: item.firstElementChild && item.firstElementChild.textContent || '',
          id: item.getAttribute(ITEM_ID_ATTR_NAME),
          url: link && link.getAttribute('href'),
          active: item.classList.contains('active')
        };
      });
  
      const targetRect = moreMenuItem.getBoundingClientRect();
      global.top.postMessage({
        type: 'show-navbar-overflow',
        items: descriptors,
        right: targetRect.right,
        top: targetRect.bottom
      }, '*');
    }
  
    if (!shouldShowNavbarOverflow && isShowingNavbarOverflow) {
      isShowingNavbarOverflow = false;
      global.top.postMessage({
        type: 'hide-navbar-overflow'
      }, '*');
    }
  };
  
  const getTabsForId = function (tabId) {
    if (!tabId) {
      return [];
    }
    // This is intentionally selecting on all of the document
    const tabs = document.querySelectorAll(getSelector(ITEM_ID_ATTR_NAME, '=', tabId));
    return Array.prototype.slice.call(tabs);
  };
  
  const getView = function (id) {
    return document.querySelector(getSelector(VIEW_ID_ATTR_NAME, '=', id));
  };
  
  const getNavbar = function (optNode) {
    const node = optNode || document.documentElement;
    const selector = getSelector(NAVBAR_ATTR_NAME);
    return node.matches(selector) ? node : node.querySelector(selector);
  };
  
  const getActiveTab = function () {
    const navbar = getNavbar();
    if (!navbar) return null;
    const activeTabId = navbar.getAttribute(ACTIVE_ID_ATTR_NAME);
    const activeTabs = getTabsForId(activeTabId);
    return activeTabs[0];
  };
  
  const getTabs = function () {
    const tabs = document.querySelectorAll(getSelector(ITEM_ID_ATTR_NAME));
    return Array.prototype.slice.call(tabs);
  };
  
  const setActiveTabs = function (id, tabs) {
    const navbar = getNavbar();
    if (!navbar) {
      return;
    }
  
    navbar.setAttribute(ACTIVE_ID_ATTR_NAME, id);
  
    tabs.forEach(function (tab) {
      tab.classList.add(CLASSNAME_ACTIVE);
    });
  
    // Remove the active class name from all tabs except the ones that should be active
    const tabList = getTabs();
    if (tabList.length) {
      tabList.forEach(function (_tab) {
        if (_tab.getAttribute(ITEM_ID_ATTR_NAME) !== id) {
          _tab.classList.remove(CLASSNAME_ACTIVE);
        }
      });
    }
  };
  
  const getViews = function () {
    const views = document.querySelectorAll(getSelector(VIEW_ID_ATTR_NAME));
    return Array.prototype.slice.call(views);
  };
  
  const hide = function (element) {
    element.style.display = 'none';
  };
  const show = function (element) {
    element.style.display = 'block';
  };
  
  const setActiveView = function (id, view) {
    if (!view) {
      return;
    }
  
    const views = getViews();
  
    // Hide all views except the one that should be active
    for (let i = 0, l = views.length; i < l; i++) {
      if (views[i] !== view[0]) {
        hide(views[i]);
      }
    }
  
    show(view);
  };
  
  const setActive = function (id, tabs, view) {
    const currentActiveTab = getActiveTab();
    const currentActiveId = currentActiveTab && currentActiveTab.getAttribute(ITEM_ID_ATTR_NAME);
    // Abort if the new tab is already active
    if (currentActiveId === id) {
      return;
    }
    
    setActiveTabs(id, tabs);
    setActiveView(id, view);
  
    center.emit('navbar-change', { id: id });
  };
  
  const activateItem = function (id) {
    updateScrollPosition();
  
    const view = getView(id);
    const tabs = getTabsForId(id);
  
    // Set styles for navbar items and show/hide views
    setActive(id, tabs, view);
  };
  
  const handleClick = function (event, tab) {
    const id = tab.getAttribute(ITEM_ID_ATTR_NAME);
    if (id === OVERFLOW_MENU_ID) {
      shouldShowNavbarOverflow = true;
      updateOverflowMenu();
      return;
    }
  
    activateItem(id);
    // Prevent any default click action (like link navigation)
    event.preventDefault();
  };
  
  const hideOverflow = function () {
    if (isShowingNavbarOverflow) {
      shouldShowNavbarOverflow = false;
      updateOverflowMenu();
    }
  };
  
  const onMessage = function (message) {
    if (message.data && message.data.name === 'navbar-overflow-click') {
      activateItem(message.data.itemId);
    }
  
    if (message.data && message.data.name === 'hide-navbar-overflow') {
      hideOverflow();
    }
  };
  
  const getFirstTabId = function () {
    const tab = document.querySelector(getSelector(ITEM_ID_ATTR_NAME));
    if (tab) {
      if (tab.getAttribute(ITEM_ID_ATTR_NAME) === OVERFLOW_MENU_ID) {
        return null;
      }
      return tab.getAttribute(ITEM_ID_ATTR_NAME);
    }
    return null;
  };
  
  const setActiveForMatchingId = function (ids) {
    // Off the list of passed ids, find the first one that matches a tab, then set
    // that one as active.
    let count = 0;
    let tabs;
    let id;
    while (count < ids.length && (!tabs || !tabs.length)) {
      if (ids[count]) {
        id = ids[count];
        tabs = getTabsForId(id);
      }
      count++;
    }
    const useID = id;
    if (!tabs || !tabs.length) {
      return;
    }
    const view = getView(useID);
  
    setActive(useID, tabs, view);
  };
  
  // Hack to get around bridge bug (reported at WP-471) where WP reports
  // the uri with the arguments as well.
  const fixAppURI = function (uri) {
    return uri.split(':').slice(0, 3).join(':');
  };
  
  const getIdFromMatchers = function (uri) {
    for (const regexp in storedMatchers) {
      if (storedMatchers.hasOwnProperty(regexp)) {
        const regexpObj = new RegExp(regexp);
        if (regexpObj.test(uri)) return storedMatchers[regexp];
      }
    }
    return null;
  };
  
  const updateForNode = function (node) {
    const navbar = getNavbar(node);
    if (!navbar) return;
  
    live(APPLICATION_MODEL_URI).query('appURI, arguments', function (error, data) {
      if (error) throw error;
  
      const uri = fixAppURI(data.appURI);
      let fullAppURI = uri;
      if (data.arguments) {
        fullAppURI += `:${data.arguments}`;
      }
      const id = getIdFromMatchers(fullAppURI);
  
      if (id) {
        // This is only the case when matchers (ITEM_MATCHER_ATTR_NAME) are used in DOM.
        setActiveForMatchingId([id]);
      } else {
        // For cases where id is only to be matched from one argument, find the id to
        // highlight by looking at the argument at HISTORY_POS_ATTR_NAME.
        const argumentIndex = +navbar.getAttribute(HISTORY_POS_ATTR_NAME) || 0;

        const idAtArgumentIndex = data.arguments.split(':')[argumentIndex];
  
        // For cases where nothing matches, also pass the first tab ID. This is
        // how the first tab is highlighted by default.
        const firstTabId = getFirstTabId();
  
        setActiveForMatchingId([fullAppURI, idAtArgumentIndex, firstTabId]);
      }
    });
  };
  
  function updateForOverflow(node) {
    const navbar = getNavbar(node);
    if (!navbar) return;
  
    const itemSelector = '[data-navbar] > [data-navbar-item-id]:not(.overflow-menu-button)';
    let items = node.querySelectorAll(itemSelector);
    if (!items) {
      return;
    }
  
    items = Array.prototype.slice.call(items);
  
    const overflowMenuButton = navbar.querySelector('.overflow-menu-button');
  
    // If we have less than two items, only show the navbar and be done. We need
    // at least two items for messurement and chances are a "more" menu won't be
    // needed.
    if (items.length < 2) {
      navbar.style.visibility = 'visible';
      if (overflowMenuButton) {
        overflowMenuButton.style.display = 'none';
      }
      return;
    }
  
    const navbarWidth = getWidth(navbar);
    if (!navbarWidth) {
      // The navbar isn't rendered. Bail out.
      return;
    }
  
    // Reset to the initial rendering state to be able to measure items.
    if (overflowMenuButton) {
      overflowMenuButton.style.display = 'inline';
    }
  
    items.forEach(function (item) {
      item.style.display = 'block';
      item.classList.remove('last-visible');
    });
  
    const sizing = measureNavbar(items, overflowMenuButton);
  
    // If stuff doesn't completely fit, reduce the available space by the "more"
    // menu with, then calculate what needs to go in it.
    let availableSpace = navbarWidth;
    let firstOverflown = getFirstOverflownIndex(availableSpace, sizing.itemWidths);
    const needsMoreMenu = items.length !== firstOverflown;
  
    if (needsMoreMenu) {
      availableSpace -= sizing.overflowMenuButtonWidth;
      firstOverflown = getFirstOverflownIndex(availableSpace, sizing.itemWidths);
    }
  
    if (!navbarOverflowItems) {
      navbarOverflowItems = [];
    }
    navbarOverflowItems.length = 0;
  
    items.forEach(function (item, i) {
      const shouldBeInOverflow = i >= firstOverflown;
      if (shouldBeInOverflow) {
        navbarOverflowItems.push(item);
      }
  
      // If we don't draw an overflow menu, set the last-visible class on the
      // very last item so its margin-right is removed.
      const isLastInNotOverflown = !needsMoreMenu && i === items.length - 1;
  
      item.style.display = shouldBeInOverflow ? 'none' : 'block';
      item.classList.toggle('last-visible', isLastInNotOverflown);
    });
  
    if (overflowMenuButton) {
      overflowMenuButton.style.display = needsMoreMenu ? 'inline' : 'none';
    }
  
    // Update items and position, possibly close the menu
    updateOverflowMenu();
    navbar.style.visibility = 'visible';
  }
  
  function updateScrollPosition() {
    const scrollTop = window.pageYOffset;
    const stickyElements = Array.prototype.slice.call(document.querySelectorAll(`[${STICKY_WRAPPER_ATTR_NAME}]`));
  
    if (stickyElements.length === 0) return;
  
    // Get the `top` value of a sticky element that is
    // in a fixed position (negative value)
    // and the closest to the top (smallest top value).
    const topTarget = stickyElements.reduce(function (res, el) {
      const top = el.getBoundingClientRect().top;
      return top < res ? top : res;
    }, 0);
  
    const topBarHeight = parseInt(document.documentElement.getAttribute('data-top-bar-height'), 10) || 0;
  
    if (topTarget < 0) {
      // Add 1 px to have the sticky element in fixed position.
      window.scrollTo(0, scrollTop + topTarget + 1 - topBarHeight);
    }
  }
  
  function handleResize() {
    updateForOverflow(document.body);
  }
  
  function measureNavbar(items, overflowMenuButton) {
    // Find out what the margin per item is my messuring beween the first 2 items.
    const item1 = items[0];
    const item2 = items[1];
  
    const right1 = Math.floor(item1.getBoundingClientRect().right);
    const left2 = Math.floor(item2.getBoundingClientRect().left);
    const marginPerSide = (left2 - right1) / 2;
  
    let itemWidths = [];
    if (items) {
      itemWidths = items.map(function (item, i, all) {
        const marginLeft = i === 0 ? 0 : marginPerSide;
        const marginRight = i === all.length - 1 ? 0 : marginPerSide;
        return getWidth(item) + marginLeft + marginRight;
      });
    }
  
    // When the overflowMenuButton is showns, it's always at the end, so it has
    // no right margin.
    let overflowMenuButtonWidth = 0;
    if (overflowMenuButton) {
      overflowMenuButtonWidth = getWidth(overflowMenuButton) + marginPerSide;
    }
  
    return {
      itemWidths: itemWidths,
      overflowMenuButtonWidth: overflowMenuButtonWidth
    };
  }
  
  function getWidth(element) {
    return element && element.offsetWidth || 0;
  }
  
  function getFirstOverflownIndex(space, sizes) {
    let count = 0;
    let used = 0;
    while (sizes[count] !== undefined && used < space) {
      used += sizes[count];
      // This is actually expected to return up to .length of the sizes.
      // That will be the case when everything fits.
      count++;
    }
    if (used < space) {
      return count;
    }
    return count - 1;
  }
  
  // Matchers allow you to tell a tab to be active for a pattern of the app arguments.
  // If you define data-navbar-item-matcher="user:.*?:playlist:.*$" you tell the tab
  // to be active when the arguments match that regexp.
  const getMatchers = function (node) {
    let matchers = node.querySelectorAll(getSelector(ITEM_MATCHER_ATTR_NAME));
    if (!matchers.length) return;
  
    matchers = Array.prototype.slice.call(matchers);
    matchers.forEach(function (matcher) {
      const regexp = matcher.getAttribute(ITEM_MATCHER_ATTR_NAME);
      const id = matcher.getAttribute(ITEM_ID_ATTR_NAME);
      storedMatchers[regexp] = id;
    });
  };
  
  const onApplicationChange = function (event) {
    if ('arguments' in event || 'active' in event) {
      updateForNode(document.documentElement);
    }
  };
  
  let isAttached = false;
  
  exports.update = function update(node) {
    if (!isAttached) return;
  
    updateForNode(node || document.documentElement);
    getMatchers(node || document.documentElement);
    updateForOverflow(node || document.documentElement);
  };
  
  exports.attach = function attach() {
    if (isAttached) return;
    isAttached = true;
  
    $(document).delegate('click', getSelector(ITEM_ID_ATTR_NAME), handleClick);
    window.addEventListener('message', onMessage);
    live(APPLICATION_MODEL_URI).on('update', onApplicationChange);
    window.addEventListener('resize', handleResize);
    center.on('navbar-hide', hideOverflow);
  };
  
  exports.detach = function detach() {
    if (!isAttached) return;
    isAttached = false;
  
    navbarOverflowItems = null;
    isShowingNavbarOverflow = false;
    shouldShowNavbarOverflow = false;
  
    $(document).undelegate('click', getSelector(ITEM_ID_ATTR_NAME), handleClick);
    window.removeEventListener('message', onMessage);
    live(APPLICATION_MODEL_URI).off('update', onApplicationChange);
    window.removeEventListener('resize', handleResize);
    center.off('navbar-hide', hideOverflow);
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../live":172,"../spotify-elements":210,"./center":221}],266:[function(require,module,exports){
  'use strict';
  
  const $ = require('../spotify-elements');
  const live = require('../live');
  
  let sessionModel;
  
  function onSessionUpdate(properties) {
    if ('online' in properties) {
      if (properties.online) {
        $(document.body).removeClass('offline');
      } else {
        $(document.body).addClass('offline');
      }
    }
  }
  
  function updateWithSessionValue() {
    live('spotify:client').query('session(online)', function (error, data) {
      if (error) throw error;
      onSessionUpdate({ online: data.session.online });
    });
  }
  
  let isAttached = false;
  
  exports.update = function () {
    if (!isAttached) return;
  
    updateWithSessionValue();
  };
  
  exports.attach = function () {
    if (isAttached) return;
    isAttached = true;
  
    if (!sessionModel) {
      live('spotify:client').get('session', function (error, session) {
        if (error) throw error;
        sessionModel = session;
        sessionModel.on('update', onSessionUpdate);
      });
    }
  };
  
  exports.detach = function () {
    if (!isAttached) return;
    isAttached = false;
  
    if (sessionModel) {
      sessionModel.off('update', onSessionUpdate);
      sessionModel = null;
    }
  };
  
  },{"../live":172,"../spotify-elements":210}],267:[function(require,module,exports){
  'use strict';
  
  var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
  
  /**
   * @private
   */
  
  const $ = require('../../spotify-elements');
  const utils = require('./utils');
  
  const SELECTOR_LIST_ROW = '[data-context] [data-list-item]';
  const SELECTOR_PLAY_BUTTON = '[data-button=play]';
  const SELECTOR_PLAY_CONTEXT_BUTTON = '[data-button=play-context]';
  
  // This array will store event types for touch handling
  let lastEventTypes = [];
  
  // Methods that need implementation from another module.
  // This is to support generic handling of user actions from within this module,
  // but allow further checks and actions to be performed by another module. This
  // allows multiple different implementations of starting playback for example.
  const methods = {
    getPlayerVariantUri: utils.createEmptyMethod('getPlayerVariantUri'),
    handlePlayButton: utils.createEmptyMethod('handlePlayButton'),
    handlePlayFromRow: utils.createEmptyMethod('handlePlayFromRow')
  };
  
  /**
   * Handle the click of a play button.
   *
   * @param {MouseEvent} event An event object for a click event.
   * @param {Elements} button An element instance from the `elements` package.
   *
   * @private
   */
  function handlePlayButtonClick(event, button) {
    const buttonNode = button[0];
    const buttonContextUri = utils.getUriFromButton(buttonNode);
  
    // We need an associated context URI for the button
    if (!buttonContextUri) {
      return;
    }
  
    const playerContextUri = methods.getPlayerVariantUri();
    const isPlayingContext = methods.getPlayerTrackProvider() !== 'queue' && utils.compareContexts(playerContextUri, buttonContextUri);
  
    const source = utils.getAttributeValueFromNode(buttonNode, 'data-play-source');
  
    methods.handlePlayButton(buttonNode, {
      isPlayingContext: isPlayingContext,
      contextUri: buttonContextUri,
      source: source,
      reason: 'playbtn'
    });
  }
  
  /**
   * Handle the double click or tap of a list row to start playback.
   *
   * @param {Elements} row An element instance from the `elements` package.
   *
   * @private
   */
  function handleListRow(row) {
    const rowNode = row[0];
    const contextNode = $(rowNode.closest('[data-context]'));
    if (contextNode && !rowNode.classList.contains('thumbs-down')) {
      const source = utils.getAttributeValueFromNode(rowNode, 'data-play-source');
      methods.handlePlayFromRow(rowNode, {
        contextUri: contextNode.data('uri'),
        source: source,
        reason: 'clickrow'
      });
    }
  }
  
  /**
   * Handle double clicking on a list row to start playback.
   *
   * @param {MouseEvent} event An event object for a double click.
   * @param {Elements} row An element instance from the `elements` package.
   *
   * @private
   */
  function handleRowDoubleClick(event, row) {
    // Only play when not clicking on interactive elements such as buttons
    if (!utils.isInteractiveElement(event.target)) {
      handleListRow(row);
    }
  }
  
  /**
   * Handle a key press on a list row.
   * This will take care of starting playback when pressing Enter.
   *
   * @param {KeyboardEvent} event A keyboard event.
   * @param {Elements} row An element instance from the `elements` package.
   *
   * @private
   */
  function handleRowKeyDown(event, row) {
    const isEnter = event.keyCode === 13;
    if (isEnter) {
      if (utils.isFormOrLinkElement(event.target)) {
        return;
      }
  
      event.preventDefault();
      event.stopPropagation();
  
      handleListRow(row);
    }
  }
  
  /**
   * Handle the 'contextmenu' event. This is only for touch handling.
   * This is needed since we use a custom context menu in Spotify, and the handler
   * for that calls `preventDefault` for the 'contextmenu' event, which means the
   * rest of the touch handling will go on as usual. So as we don't get the
   * 'touchcancel' event, we need to catch the 'contextmenu' event here to know
   * that we should not act on the following 'touchend' event.
   *
   * @param {MouseEvent} event A 'contextmenu' event.
   *
   * @private
   */
  function handleContextMenu(event) {
    lastEventTypes.push(event.type);
  }
  
  /**
   * Handle the start of a touch. Used to detect a tap.
   *
   * @param {TouchEvent} event A touch event.
   *
   * @private
   */
  function handleRowTouchStart(event) {
    lastEventTypes.push(event.type);
  }
  
  /**
   * Handle the move of a touch. Used to abort detection of a tap.
   *
   * @param {TouchEvent} event A touch event.
   *
   * @private
   */
  function handleRowTouchMove(event) {
    lastEventTypes.push(event.type);
  }
  
  /**
   * Handle the end of a touch. Used to detect a tap and start playback of a row.
   *
   * @param {TouchEvent} event A touch event.
   * @param {Elements} row An element instance from the `elements` package.
   *
   * @private
   */
  function handleRowTouchEnd(event, row) {
    // If this was a tap (only touchstart was triggered before this)
    if (lastEventTypes.length === 1 && lastEventTypes[0] === 'touchstart') {
      // Prevent the simulated mouse events (including click). This is to
      // avoid the simulated click event to click on the play button that
      // appears when selecting a row on touchstart.
      // See: https://jira.spotify.net/browse/KM-7983
      if (!utils.isInteractiveElement(event.target)) {
        event.preventDefault();
  
        // Tapping on a row should play it
        handleListRow(row);
      }
    }
    lastEventTypes = [];
  }
  
  /**
   * Attach the needed event listeners for the module.
   */
  const defaultSelectors = {
    selectorPlayButton: SELECTOR_PLAY_BUTTON,
    selectorListRow: SELECTOR_LIST_ROW,
    selectorPlayContextButton: SELECTOR_PLAY_CONTEXT_BUTTON
  };
  let selectors = defaultSelectors;
  
  exports.attach = function () {
    const doc = $(document);
  
    doc.delegate('keydown', selectors.selectorListRow, handleRowKeyDown);
    doc.delegate('click', selectors.selectorPlayButton, handlePlayButtonClick);
    doc.delegate('click', selectors.selectorPlayContextButton, handlePlayButtonClick);
    doc.delegate('dblclick', selectors.selectorListRow, handleRowDoubleClick);
  
    /* For touch handling */
    doc.delegate('touchstart', selectors.selectorListRow, handleRowTouchStart);
    doc.delegate('touchend', selectors.selectorListRow, handleRowTouchEnd);
    doc.delegate('touchmove', selectors.selectorListRow, handleRowTouchMove);
    doc.delegate('contextmenu', selectors.selectorListRow, handleContextMenu);
  };
  
  /**
   * Detach the event listeners for the module.
   */
  exports.detach = function () {
    const doc = $(document);
  
    doc.undelegate('keydown', selectors.selectorListRow, handleRowKeyDown);
    doc.undelegate('click', selectors.selectorPlayButton, handlePlayButtonClick);
    doc.undelegate('click', selectors.selectorPlayContextButton, handlePlayButtonClick);
    doc.undelegate('dblclick', selectors.selectorListRow, handleRowDoubleClick);
  
    /* For touch handling */
    doc.undelegate('touchstart', selectors.selectorListRow, handleRowTouchStart);
    doc.undelegate('touchend', selectors.selectorListRow, handleRowTouchEnd);
    doc.undelegate('touchmove', selectors.selectorListRow, handleRowTouchMove);
    doc.undelegate('contextmenu', selectors.selectorListRow, handleContextMenu);
  };
  
  exports.setSelectors = function (customSelectors = {}) {
    exports.detach();
    selectors = _extends({}, defaultSelectors, customSelectors);
    exports.attach();
  };
  
  /**
   * Set the external methods for this module to add implementations for starting
   * playback etc.
   *
   * @param {Object} methodDefinitions Object where keys are method names and
   *     values are functions.
   */
  exports.setMethods = function (methodDefinitions) {
    for (const name in methodDefinitions) {
      if (methodDefinitions.hasOwnProperty(name)) {
        methods[name] = methodDefinitions[name];
      }
    }
  };
  
  },{"../../spotify-elements":210,"./utils":270}],268:[function(require,module,exports){
  'use strict';
  
  const playerWithIndices = require('./withIndices');
  const playerWithUids = require('./withUids');
  const actions = require('./actions');
  
  let playerModule = playerWithIndices;
  
  exports.setMode = function (type) {
    switch (type) {
      case 'indices':
        playerModule = playerWithIndices;
        break;
      case 'uids':
        playerModule = playerWithUids;
        break;
      default:
        break;
    }
  };
  
  exports.setSelectors = actions.setSelectors;
  
  exports.attach = function () {
    playerModule.attach();
  };
  
  exports.detach = function () {
    playerModule.detach();
  };
  
  exports.update = function (optNode) {
    playerModule.update(optNode);
  };
  
  },{"./actions":267,"./withIndices":271,"./withUids":272}],269:[function(require,module,exports){
  'use strict';
  
  /**
   * @private
   */
  
  const difference = require('mout/array/difference');
  const $ = require('../../spotify-elements');
  const live = require('../../live');
  
  const center = require('../center');
  const utils = require('./utils');
  
  const CURRENT = 'current';
  const PAUSED = 'paused';
  const PLAYING = 'playing';
  
  const LOG_PLAY_BUTTON = 'play-button';
  const LOG_PAUSE_BUTTON = 'pause-button';
  const LOG_INTENT_PLAY = 'play';
  const LOG_INTENT_PAUSE = 'pause';
  
  const SELECTOR_PLAY_BUTTON = '[data-button=play]';
  const SELECTOR_PLAY_CONTEXT_BUTTON = '[data-button=play-context]';
  const SELECTOR_ALL_PLAY_BUTTONS = `${SELECTOR_PLAY_BUTTON},${SELECTOR_PLAY_CONTEXT_BUTTON}`;
  
  const INTERACTION_INTENT_ATTR = 'data-interaction-intent';
  const SELECTOR_PLAYABLE_ROW = '[data-context-index]';
  
  // Methods that need implementation from another module.
  // This is to support generic handling of DOM updates from within this module,
  // but allow further checks and actions to be performed by another module. This
  // allows multiple different implementations of highlighting playing objects.
  const methods = {
    getPlayerVariantUri: utils.createEmptyMethod('getPlayerVariantUri'),
    getPlayerIsPlaying: utils.createEmptyMethod('getPlayerIsPlaying'),
    getPlayingNodes: utils.createEmptyMethod('getPlayingNodes'),
  
    // This should be a function that takes a function as the first argument. That
    // function argument should be called whenever the player state has changed.
    subscribeToPlayerChange: utils.createEmptyMethod('subscribeToPlayerChange')
  };
  
  let playableChangeHandlers = {};
  let playerSubscription = null;
  
  /**
   * Update states in the DOM based on current player state.
   *
   * @param {HTMLElement=} node An optional DOM node. If passed, only nodes inside
   *     of that node will be updated.
   */
  function updateDOM(optNode) {
    const containerNode = optNode || document;
  
    updateButtonsBasedOnPlayable(containerNode);
  
    const playingNodes = getPlayingNodes(containerNode);
    const currentNodes = getCurrentNodes(containerNode);
  
    if (currentNodes) {
      let nodesForRemoval = currentNodes;
      if (playingNodes) {
        nodesForRemoval = difference(currentNodes, playingNodes);
      }
      nodesForRemoval.forEach(removePlayingStateFromNode);
    }
  
    if (playingNodes) {
      let nodesForAddition = playingNodes;
      if (currentNodes) {
        nodesForAddition = difference(playingNodes, currentNodes);
      }
      nodesForAddition.forEach(addPlayingStateToNode);
  
      const nodesForUpdate = difference(playingNodes, nodesForAddition);
      nodesForUpdate.forEach(updatePlayingStateForNode);
    }
  }
  
  /**
   * Update the playable status of all play buttons found inside the passed
   * container node.
   *
   * @param {HTMLElement} containerNode A DOM node to search for nodes inside.
   *
   * @private
   */
  function updateButtonsBasedOnPlayable(containerNode) {
    // `[data-button=play]` buttons are used to play something specific *in* a
    // context (defining an index as a track number or uid/uri or range).
    // Those are currently not updated themselves, instead their rows are being
    // set to `.unavailable`.
    const buttons = $(containerNode).search('[data-button=play-context]');
    if (buttons) {
      buttons.forEach(updateButtonBasedOnPlayable);
    }
  }
  
  /**
   * Update the playable status of the passed button.
   *
   * @param {HTMLElement} button A button DOM node.
   *
   * @private
   */
  function updateButtonBasedOnPlayable(button) {
    // Play buttons in list rows should not be handled, as the buttons are
    // hidden by CSS anyway for unplayable rows.
    if ($(button).parent('[data-list-item]')) {
      return;
    }
  
    const contextURI = utils.getUriFromButton(button);
  
    // Check if the context has a playable flag specified.
    const playableValue = live(contextURI).get('playable');
    const definesPlayable = playableValue !== undefined;
  
    // If it does, update the `disabled` attribute based on it
    if (definesPlayable) {
      if (playableValue) {
        button.removeAttribute('disabled');
      } else {
        button.setAttribute('disabled', 'disabled');
      }
  
      if (!playableChangeHandlers[contextURI]) {
        playableChangeHandlers[contextURI] = function (changed) {
          if (!changed.hasOwnProperty('playable')) {
            return;
          }
          // Update all buttons currently in DOM when `playable` changes on a
          // context. Not worth adding logic to only pick out buttons pointing to
          // a specific URI.
          updateButtonsBasedOnPlayable(document.body);
        };
        live(contextURI).on('update', playableChangeHandlers[contextURI]);
      }
    }
  }
  
  /**
   * Get the nodes inside the passed container node that are currently marked as
   * playing.
   *
   * @param {HTMLElement} containerNode A DOM node to search for nodes inside.
   *
   * @return {?Elements} An element instance from the `elements` package, or null.
   *
   * @private
   */
  function getCurrentNodes(containerNode) {
    return $(containerNode).search('[data-playback-active=true]');
  }
  
  /**
   * Get the nodes inside the passed container node that should be marked as
   * playing based on the current player state.
   *
   * @param {HTMLElement} containerNode A DOM node to search for nodes inside.
   *
   * @return {?Elements} An element instance from the `elements` package, or null.
   *
   * @private
   */
  function getPlayingNodes(containerNode) {
    let contextNodes = [];
  
    if (methods.getPlayerTrackProvider() !== 'queue') {
      const variantUri = methods.getPlayerVariantUri();
      const selectors = utils.getContextSelectorsWithVariants(variantUri);
  
      const foundContextNodes = $(containerNode).search(selectors.join(', '));
      if (foundContextNodes) {
        contextNodes = Array.prototype.slice.call(foundContextNodes);
      }
    }
  
    // Pass it through the outside implementation, which might filter out
    // nodes from `contextNodes` and add more nodes to the match.
    return methods.getPlayingNodes(containerNode, contextNodes);
  }
  
  /**
   * Remove the playing state from the passed DOM node, as it's no longer playing.
   *
   * @param {HTMLElement} node The DOM node to remove state from.
   *
   * @private
   */
  function removePlayingStateFromNode(node) {
    const className = [CURRENT, PAUSED, PLAYING].join(' ');
    const newNode = $(node);
    newNode.removeClass(className).removeAttribute('data-playback-active');
  
    if (newNode.matches(SELECTOR_ALL_PLAY_BUTTONS)) {
      newNode.attribute(INTERACTION_INTENT_ATTR, LOG_INTENT_PLAY);
    }
  
    // Play buttons in list rows are not handled automatically
    if (newNode.matches(SELECTOR_PLAYABLE_ROW)) {
      const playButton = newNode[0].querySelector(SELECTOR_PLAY_BUTTON);
      if (playButton) {
        playButton.setAttribute(INTERACTION_INTENT_ATTR, LOG_INTENT_PLAY);
      }
    }
  }
  
  /**
   * Add the playing state to the passed DOM node, as it's now playing.
   *
   * @param {HTMLElement} node The DOM node to add state to.
   *
   * @private
   */
  function addPlayingStateToNode(node) {
    const isPlaying = methods.getPlayerIsPlaying();
  
    const classNames = [CURRENT, isPlaying ? PLAYING : PAUSED].join(' ');
  
    const newNode = $(node);
    newNode.addClass(classNames).data('playback-active', true);
  
    if (newNode.matches(SELECTOR_ALL_PLAY_BUTTONS)) {
      newNode.attribute(INTERACTION_INTENT_ATTR, isPlaying ? LOG_INTENT_PAUSE : LOG_INTENT_PLAY);
    }
  
    // Play buttons in list rows are not handled automatically
    if (newNode.matches(SELECTOR_PLAYABLE_ROW)) {
      const playButton = newNode[0].querySelector(SELECTOR_PLAY_BUTTON);
      if (playButton) {
        playButton.setAttribute(INTERACTION_INTENT_ATTR, isPlaying ? LOG_INTENT_PAUSE : LOG_INTENT_PLAY);
      }
    }
  }
  
  /**
   * Update the playing state for the passed DOM node, as it's still representing
   * the currently playing context, but the player might switch between
   * playing/paused.
   *
   * @param {HTMLElement} node The DOM node to update state for.
   *
   * @private
   */
  function updatePlayingStateForNode(node) {
    const isPlaying = methods.getPlayerIsPlaying();
  
    const currentClassName = isPlaying ? PAUSED : PLAYING;
    const newClassName = isPlaying ? PLAYING : PAUSED;
    const element = $(node);
  
    if (element.hasClass(currentClassName)) {
      element.removeClass(currentClassName);
    }
  
    if (!element.hasClass(newClassName)) {
      element.addClass(newClassName);
    }
  
    if (element.matches(SELECTOR_ALL_PLAY_BUTTONS)) {
      element.attribute(INTERACTION_INTENT_ATTR, isPlaying ? LOG_INTENT_PAUSE : LOG_INTENT_PLAY);
    }
  
    // Play buttons in list rows are not handled automatically
    if (node.matches(SELECTOR_PLAYABLE_ROW)) {
      const playButton = node.querySelector(SELECTOR_PLAY_BUTTON);
      if (playButton) {
        playButton.setAttribute(INTERACTION_INTENT_ATTR, isPlaying ? LOG_INTENT_PAUSE : LOG_INTENT_PLAY);
      }
    }
  }
  
  /**
   * Handle updating of DOM nodes when they have been added to the DOM by a scroll
   * agent.
   *
   * @param {Object} event Custom event object sent from the scroll agent. Should
   *     contain a `container` property pointing to the DOM node containing the
   *     rows.
   *
   * @private
   */
  function handleScrollShowAfter(event) {
    const container = $(event.container);
    let contextNode = null;
    if (container.matches('[data-context]')) {
      contextNode = container;
    } else {
      contextNode = container.parent('[data-context]');
    }
  
    // We can't pass the context node itself as root because of how the playing
    // state queries are set up.
    const contextParent = contextNode && contextNode[0].parentElement || document;
  
    updateDOM(contextParent);
  }
  
  /**
   * Attach the needed event listeners for the module.
   */
  exports.attach = function () {
    playerSubscription = methods.subscribeToPlayerChange(function () {
      updateDOM();
    });
  
    center.on('scroll-show-after', handleScrollShowAfter);
  };
  
  /**
   * Detach the needed event listeners for the module.
   */
  exports.detach = function () {
    if (playerSubscription) {
      playerSubscription.cancel();
      playerSubscription = null;
    }
  
    for (const contextUri in playableChangeHandlers) {
      if (playableChangeHandlers.hasOwnProperty(contextUri)) {
        live(contextUri).off('update', playableChangeHandlers[contextUri]);
      }
    }
    playableChangeHandlers = {};
  
    center.off('scroll-show-after', handleScrollShowAfter);
  };
  
  /**
   * Update things in the DOM.
   */
  exports.update = updateDOM;
  
  /**
   * Set the external methods for this module to add implementations for methods
   * needed for highlighting playing objects.
   *
   * @param {Object} methodDefinitions Object where keys are method names and
   *     values are functions.
   */
  exports.setMethods = function (methodDefinitions) {
    for (const name in methodDefinitions) {
      if (methodDefinitions.hasOwnProperty(name)) {
        methods[name] = methodDefinitions[name];
      }
    }
  };
  
  },{"../../live":172,"../../spotify-elements":210,"../center":221,"./utils":270,"mout/array/difference":435}],270:[function(require,module,exports){
  (function (global){
  'use strict';
  
  /**
   * This module might be used by modules outside of spotify-events.
   * @public
   */
  
  const liburi = require('spotify-liburi');
  
  const getOriginUri = require('../../live-wrapped-uri').getOriginUri;
  
  /**
   * Get the context URI from a button node or its parents.
   *
   * @param {HTMLElement} button A DOM node for a button.
   *
   * @return {string} The context URI or empty string if not found.
   */
  function getUriFromButton(button) {
    const contextNode = button.closest('[data-context]');
    return contextNode && contextNode.getAttribute('data-uri') || '';
  }
  
  /**
   * Get the value for the passed attribute name, either from the passed node or
   * from any parent node.
   *
   * @param {HTMLElement} node The node to start searching from.
   * @param {string} attributeName The name of the attribute.
   *
   * @return {string} The value, or empty string if not found.
   */
  function getAttributeValueFromNode(node, attributeName) {
    const selector = `[${attributeName}]`;
    const attributeNode = node.closest(selector);
    return attributeNode && attributeNode.getAttribute(attributeName) || '';
  }
  
  /**
   * Check if the passed element is a form or link element.
   *
   * @param {HTMLElement} element A DOM node.
   *
   * @return {boolean} True if it is a form or link element.
   */
  function isFormOrLinkElement(element) {
    const tagName = element.tagName.toLowerCase();
    const elements = ['input', 'textarea', 'button', 'select', 'optgroup', 'option', 'a'];
    return elements.indexOf(tagName) > -1;
  }
  
  /**
   * Check if the passed element is an interactive element (links, buttons).
   *
   * @param {HTMLElement}  element A DOM node.
   *
   * @return {boolean} True if the element is interactive.
   */
  function isInteractiveElement(element) {
    const tagName = element.tagName.toLowerCase();
    return tagName === 'a' || tagName === 'button';
  }
  
  /**
   * Get a CSS selector string for a context.
   *
   * @param {string} operator A CSS selector operator ($= or similar).
   * @param {string} uri The context URI.
   *
   * @return {string} The CSS selector string.
   */
  function getContextSelector(operator, uri) {
    return `[data-context][data-uri${operator}${uri}]`;
  }
  
  /**
   * Get an array of CSS selectors for searching for context nodes inside the DOM.
   * This also includes any variant of the passed context URI. For example, if
   * passing a sorted playlist URI, it will return selectors for matching all
   * variations of that playlist URI, including the regular playlist URI, filtered
   * URI etc.
   *
   * @param {string} uri The context URI to search for. This can be either the
   *     origin URI, or any variant URI. The function will convert it to the
   *     origin URI automatically.
   *
   * @return {Array.<string>} Array of CSS selector strings.
   */
  function getContextSelectorsWithVariants(uri) {
    const STARTS_WITH = '^=';
    const ENDS_WITH = '$=';
    const EQUALS = '=';
  
    const originUri = getOriginUri(uri) || uri;
    const originUriNormalized = normalizeUri(originUri);
  
    const originUriEnding = originUri.replace(/^spotify:/, ':');
    const originUriEndingNormalized = normalizeUri(originUriEnding);
  
    const selectors = [getContextSelector(EQUALS, originUri)];
  
    selectors.push(getContextSelector(STARTS_WITH, 'spotify:internal:filterlist:') + getContextSelector(ENDS_WITH, originUri.replace(/^spotify:/, ':')));
  
    selectors.push(getContextSelector(STARTS_WITH, 'spotify:internal:sortlist:') + getContextSelector(ENDS_WITH, originUri.replace(/^spotify:/, ':')));
  
    if (originUri !== originUriNormalized) {
      selectors.push(getContextSelector(EQUALS, originUriNormalized));
      selectors.push(getContextSelector(STARTS_WITH, 'spotify:internal:filterlist:') + getContextSelector(ENDS_WITH, originUriEndingNormalized));
      selectors.push(getContextSelector(STARTS_WITH, 'spotify:internal:sortlist:') + getContextSelector(ENDS_WITH, originUriEndingNormalized));
    }
  
    return selectors;
  }
  
  /**
   * Check if two context URIs are representing the same context. This will take
   * care of normalizing the casing of URL encoding and filtered/sorted contexts.
   *
   * @param {string} context1 The first context.
   * @param {string} context2 The second context.
   *
   * @return {boolean} True if they represent the same context.
   */
  function compareContexts(context1, context2) {
    if (!context1 || !context2) return false;
  
    const newContext1 = normalizeUri(getOriginUri(context1) || context1);
    const newContext2 = normalizeUri(getOriginUri(context2) || context2);
  
    return newContext1 === newContext2;
  }
  
  /**
   * A URI might contain URL encoded characters, and per spec the URL encoding can
   * be either in lowercase or uppercase (should be treated as same URL). If the
   * input URL has lowercase encoding, liburi will convert it into uppercase
   * encoding.
   *
   * @param {string} uri The URI to normalize.
   *
   * @return {string} The normalized URI.
   */
  function normalizeUri(uri) {
    const uriObj = liburi.from(uri);
    let normalized = uriObj ? uriObj.toString() : uri;
  
    // Work around incorrect handling of `spotify:internal:...` URIs by liburi,
    // since it does not recognize `internal` URIs, and therefore uses the default
    // `app` type.
    normalized = normalized.replace(/^spotify:app:internal:/, 'spotify:internal:');
  
    // Work around the bad conversion of spaces in filtered list URIs. The URI
    // spec we use says that spaces should be encoded as `+`, but the `filterlist`
    // URI uses the regular `%20` encoding for spaces. Passing it through liburi
    // converts it to `+`, so we need to convert it back to `%20` again.
    normalized = normalized.replace(/:internal:filterlist:[^:]+/, function (match) {
      return match.replace(/\+/g, '%20');
    });
  
    return normalized;
  }
  
  /**
   * Create an empty method that will only log a warning when called.
   *
   * @param {string} methodName The name of the method to warn about.
   *
   * @return {Function} The function.
   */
  function createEmptyMethod(methodName) {
    return function () {
      if (global.console) {
        console.warn(`spotify-events/player: \`${methodName}\` is not implemented.`);
      }
    };
  }
  
  exports.getUriFromButton = getUriFromButton;
  exports.getAttributeValueFromNode = getAttributeValueFromNode;
  exports.isFormOrLinkElement = isFormOrLinkElement;
  exports.isInteractiveElement = isInteractiveElement;
  exports.getContextSelector = getContextSelector;
  exports.getContextSelectorsWithVariants = getContextSelectorsWithVariants;
  exports.compareContexts = compareContexts;
  exports.normalizeUri = normalizeUri;
  exports.createEmptyMethod = createEmptyMethod;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../live-wrapped-uri":171,"spotify-liburi":553}],271:[function(require,module,exports){
  'use strict';
  
  /**
   * @private
   */
  
  const live = require('../../live');
  const Range = require('../../range2');
  const $ = require('../../spotify-elements');
  
  const playerActions = require('./actions');
  const playerUpdates = require('./updates');
  const utils = require('./utils');
  
  let isAttached = false;
  
  exports.attach = function () {
    if (isAttached) return;
    isAttached = true;
  
    playerActions.setMethods({
      getPlayerTrackProvider: getPlayerTrackProvider,
      getPlayerVariantUri: getPlayerVariantUri,
      handlePlayButton: handlePlayButton,
      handlePlayFromRow: handlePlayFromRow
    });
  
    playerUpdates.setMethods({
      getPlayerTrackProvider: getPlayerTrackProvider,
      getPlayerVariantUri: getPlayerVariantUri,
      getPlayerIsPlaying: getPlayerIsPlaying,
      getPlayingNodes: getPlayingNodes,
      subscribeToPlayerChange: subscribeToPlayerChange
    });
  
    playerActions.attach();
    playerUpdates.attach();
  };
  
  exports.detach = function () {
    if (!isAttached) return;
    isAttached = false;
  
    playerActions.detach();
    playerUpdates.detach();
  };
  
  exports.update = function (optNode) {
    if (!isAttached) return;
  
    playerUpdates.update(optNode);
  };
  
  function getPlayerTrackProvider() {
    const track = live('spotify:player').get('track');
    return track ? track.get('provider') || '' : '';
  }
  
  /**
   * Get the variant URI for the currently playing context. For example, if
   * playing a sorted playlist, this should return the sorted URI.
   *
   * @return {string} The context variant URI, or empty string if nothing is
   *     playing.
   */
  function getPlayerVariantUri() {
    const variant = live('spotify:player').get('variant');
    return variant ? variant.uri : '';
  }
  
  /**
   * Get the index of the currently playing track within the playing context.
   *
   * @return {number} The index, or -1 if not found.
   */
  function getPlayerIndex() {
    const indexObject = live('spotify:player').get('index');
    return indexObject ? indexObject.get('track') : -1;
  }
  
  /**
   * Get the URI of the currently playing track.
   *
   * @return {string} The track URI, or empty string if nothing is playing.
   */
  function getPlayerTrackUri() {
    const track = live('spotify:player').get('track');
    return track && track.uri || '';
  }
  
  /**
   * Get the playing status of the player.
   *
   * @return {boolean} True if the player is currently playing audio.
   */
  function getPlayerIsPlaying() {
    const isPlaying = live('spotify:player').get('isPlaying');
    const isPaused = live('spotify:player').get('isPaused');
    return isPlaying && !isPaused;
  }
  
  /**
   * Subscribe to player changes.
   *
   * @param {Function} handler A handler function. The data passed to this
   *     function is irrelevent, only the fact that something changed is needed.
   *
   * @return {Object} An object with a `cancel` method to cancel the subscription.
   */
  function subscribeToPlayerChange(handler) {
    live('spotify:player').on('update', handler);
    let active = true;
    return {
      cancel: function () {
        if (active) {
          live('spotify:player').off('update', handler);
          active = false;
        }
      }
    };
  }
  
  /**
   * Handle the click of a play button.
   * This function should decide what the action should be, and pause/resume
   * playback or start playback of a new context.
   *
   * @param {HTMLElement} buttonNode A button DOM node.
   * @param {Object} data Object of data about the action:
   *     isPlayingContext: (boolean) Whether the button looks to be representing
   *         the currently playing context.
   *     contextUri: (string) The context URI for the button.
   *     source: (string) The 'play source' value, for logging purposes.
   *     reason: (string) The 'play reason' value, for logging purposes.
   */
  function handlePlayButton(buttonNode, data) {
    const button = $(buttonNode);
    const playerIndex = getPlayerIndex();
    let range;
    let newIndex;
    let uids;
    let uris;
  
    const dataRange = button.data('range');
    if (dataRange) {
      range = Range.fromString(dataRange);
      const list = live(data.contextUri).get('rows');
      if (list) {
        const indices = range.toIndices();
        uids = indices.map(function (index) {
          return list.keys[index];
        });
        uris = indices.map(function (index) {
          const track = list.get(index).get('track');
          return track.uri;
        });
      }
      newIndex = null;
    } else {
      let contextIndexNode = null;
      if (button.matches('[data-context-index]')) {
        contextIndexNode = button;
      } else {
        contextIndexNode = button.parent('[data-context-index]');
      }
  
      if (contextIndexNode) {
        newIndex = parseInt(contextIndexNode.data('context-index'), 10);
        const list = live(data.contextUri).get('rows');
        if (list) {
          uids = [list.keys[newIndex]];
          uris = [list.get(newIndex).get('track').uri];
        }
      } else {
        newIndex = null;
      }
    }
  
    let shouldPauseResume = false;
  
    if (data.isPlayingContext) {
      if (range) {
        shouldPauseResume = range.contains(new Range(playerIndex, playerIndex + 1));
      } else if (newIndex !== null) {
        shouldPauseResume = newIndex === playerIndex;
      } else {
        shouldPauseResume = true;
      }
    }
  
    if (shouldPauseResume) {
      if (getPlayerIsPlaying()) {
        live('spotify:player').emit('pause');
      } else {
        live('spotify:player').emit('resume');
      }
    } else {
      const eventData = {
        context: data.contextUri,
        index: newIndex,
        source: data.source,
        reason: data.reason
      };
  
      if (range) {
        eventData.range = [range.start, range.end];
      }
  
      if (uids) {
        eventData.uids = uids;
      }
  
      if (uris) {
        eventData.uris = uris;
      }
  
      live('spotify:player').emit('play', eventData);
    }
  }
  
  /**
   * Handle playing a list row. This function should start playback of a context
   * starting from a specific row.
   *
   * @param {HTMLElement} rowNode A DOM node for a list row.
   * @param {Object} data Object of data about the action:
   *     contextUri: (string) The context URI for the list.
   *     source: (string) The 'play source' value, for logging purposes.
   *     reason: (string) The 'play reason' value, for logging purposes.
   */
  function handlePlayFromRow(rowNode, data) {
    let index = parseInt(rowNode.getAttribute('data-context-index'), 10);
    if (isNaN(index)) {
      index = -1;
    }
    const uid = rowNode.getAttribute('data-uid') || '';
    const uri = rowNode.getAttribute('data-uri') || '';
  
    const eventData = {
      context: data.contextUri,
      index: index,
      source: data.source,
      reason: data.reason
    };
  
    if (uid) {
      eventData.uids = [uid];
    }
  
    if (uri) {
      eventData.uris = [uri];
    }
  
    live('spotify:player').emit('play', eventData);
  }
  
  /**
   * Get the nodes inside the passed container node that should be marked as
   * playing.
   *
   * @param {HTMLElement} containerNode The DOM node to search inside.
   * @param {Array.<HTMLElement>} contextNodes Array of DOM nodes that are already
   *     matched based on context URIs. These can be filtered if needed, but
   *     otherwise they should be included in the return value.
   *
   * @return {Array.<HTMLElement>} An array of DOM nodes.
   */
  function getPlayingNodes(containerNode, contextNodes) {
    const playerIndex = getPlayerIndex();
    const playerTrackUri = getPlayerTrackUri();
    const variantUri = getPlayerVariantUri();
  
    // Remove any matched context nodes that have a range specified and where
    // the current player index does not match.
    const filteredContextNodes = filterRanges(playerIndex, contextNodes);
  
    const selectorsWithVariants = utils.getContextSelectorsWithVariants(variantUri);
  
    const rowNodes = getContextNodesForRows(containerNode, selectorsWithVariants, playerIndex, playerTrackUri) || [];
  
    const rangeNodes = getContextNodesForRanges(containerNode, selectorsWithVariants, playerIndex) || [];
  
    // Only return button nodes whos closest context is one of the nodes in the
    // contextNodes array. Imagine this minimal node tree:
    //
    // [data-context][data-uri="spotify:root-context"]
    //   [data-button=play-context]
    //   [data-context][data-uri="spotify:nested-context"]
    //     [data-button=play-context]
    //
    // The first [data-button=play-context] should play "spotify:root-context".
    // Ths second [data-button=play-context] should play "spotify:nested-context".
    let buttonNodes = [];
    if (contextNodes.length) {
      const candidates = $(contextNodes).search('[data-button=play-context]');
      if (candidates) {
        buttonNodes = candidates.filter(candidate => {
          const closestContextNode = candidate.closest('[data-context]');
          return contextNodes.indexOf(closestContextNode) >= 0;
        });
      }
    }
  
    // Let `elements` remove any duplicate nodes
    const nodes = $([filteredContextNodes, rowNodes, rangeNodes, buttonNodes]);
  
    return nodes ? Array.prototype.slice.call(nodes) : [];
  }
  
  /**
   * Get the nodes inside the passed container node that match what's currently
   * playing and are list rows.
   *
   * @param {HTMLElement} containerNode The DOM node to search inside.
   * @param {Array.<string>} selectors Array of CSS selectors for matching the
   *     currently playing context.
   * @param {number} index The currently playing index.
   * @param {string} trackUri The URI of the currently playing track.
   *
   * @return {Array.<HTMLElement>} Array of DOM nodes.
   */
  function getContextNodesForRows(containerNode, selectors, index, trackUri) {
    // Per container node, find a node that's playing in two steps by finding
    // Attempt 1: the first node where the playing index and
    // the track uri matches.
    // Attempt 2: the first node where only the track uri matches [1].
    const uriSelector = `[data-uri="${trackUri}"]`;
    const indexAndUriSelector = `[data-context-index=${index}]${uriSelector}`;
  
    // Attempt 1
    const selectorsIndexAndUriSelector = selectors.map(s => `${s} ${indexAndUriSelector}`);
    const elemsViaIndexAndUriSelector = $(containerNode).find(selectorsIndexAndUriSelector);
  
    if (elemsViaIndexAndUriSelector) {
      return elemsViaIndexAndUriSelector;
    }
  
    // Attempt 2
    const selectorsUriSelector = selectors.map(s => `${s} ${uriSelector}`);
    return $(containerNode).find(selectorsUriSelector);
  
    // [1] The index is very likely wrong when while on shuffle, the playing
    // device has changed. Indices are then shuffled indices, and despite this
    // files is all about highlighting in "index mode" we can disregard the index.
  }
  
  /**
   * Get the nodes inside the passed container node that match what's currently
   * playing and have a matching range specified.
   *
   * @param {HTMLElement} containerNode The DOM node to search inside.
   * @param {Array.<string>} selectors Array of CSS selectors for matching the
   *     currently playing context.
   * @param {number} index The currently playing index.
   *
   * @return {Array.<HTMLElement>} Array of DOM nodes.
   */
  function getContextNodesForRanges(containerNode, selectors, index) {
    const rangeAttribute = '[data-range]';
  
    // It needs to search for all range elements somewhere inside an element
    // marked up as a context, as well as range elements that themselves are
    // contexts.
    const newSelectors = selectors.map(function (s) {
      const rangeInContextSelector = `${s} ${rangeAttribute}`;
      const rangedContextSelector = s + rangeAttribute;
      const rangeSelector = `${rangeInContextSelector}, ${rangedContextSelector}`;
      return rangeSelector;
    });
  
    let rangeNodes = $(containerNode).search(newSelectors.join(', '));
    if (rangeNodes) {
      rangeNodes = Array.prototype.slice.call(rangeNodes);
    } else {
      rangeNodes = [];
    }
  
    return filterRanges(index, rangeNodes);
  }
  
  /**
   * Return a new array of nodes where nodes that specify a range that doesn't
   * match the currently playing index are removed.
   *
   * @param {number} index The currently playing index.
   * @param {Array.<HTMLElement>} nodes Array of DOM nodes.
   *
   * @return {Array.<HTMLElement>} Array of DOM nodes.
   */
  function filterRanges(index, nodes) {
    const thisRange = new Range(index, index + 1);
  
    return nodes.filter(function (node) {
      const newNode = $(node);
      let range = newNode.data('range');
  
      // Node without a range should always be active
      if (!range) return true;
  
      // If player has no index, but the node has a range, we can't be sure if
      // it's playing
      if (!thisRange) return false;
  
      range = Range.fromString(range);
      return range.contains(thisRange);
    });
  }
  
  },{"../../live":172,"../../range2":204,"../../spotify-elements":210,"./actions":267,"./updates":269,"./utils":270}],272:[function(require,module,exports){
  'use strict';
  
  const live = require('../../live');
  const $ = require('../../spotify-elements');
  
  const playerActions = require('./actions');
  const playerUpdates = require('./updates');
  const utils = require('./utils');
  
  let isAttached = false;
  
  exports.attach = function () {
    if (isAttached) return;
    isAttached = true;
  
    playerActions.setMethods({
      getPlayerTrackProvider: getPlayerTrackProvider,
      getPlayerVariantUri: getPlayerVariantUri,
      handlePlayButton: handlePlayButton,
      handlePlayFromRow: handlePlayFromRow
    });
  
    playerUpdates.setMethods({
      getPlayerTrackProvider: getPlayerTrackProvider,
      getPlayerVariantUri: getPlayerVariantUri,
      getPlayerIsPlaying: getPlayerIsPlaying,
      getPlayingNodes: getPlayingNodes,
      subscribeToPlayerChange: subscribeToPlayerChange
    });
  
    playerActions.attach();
    playerUpdates.attach();
  };
  
  exports.detach = function () {
    if (!isAttached) return;
    isAttached = false;
  
    playerActions.detach();
    playerUpdates.detach();
  };
  
  exports.update = function (optNode) {
    if (!isAttached) return;
  
    playerUpdates.update(optNode);
  };
  
  function getPlayerTrackProvider() {
    const track = live('spotify:player').get('track');
    return track ? track.get('provider') || '' : '';
  }
  
  /**
   * Get the variant URI for the currently playing context. For example, if
   * playing a sorted playlist, this should return the sorted URI.
   *
   * @return {string} The context variant URI, or empty string if nothing is
   *     playing.
   */
  function getPlayerVariantUri() {
    const variant = live('spotify:player').get('variant');
    return variant ? variant.uri : '';
  }
  
  /**
   * Get the index of the currently playing page.
   *
   * @return {number} The page index, or -1 if not found.
   */
  function getPlayerPageIndex() {
    const indexObject = live('spotify:player').get('index');
    const pageIndex = indexObject && indexObject.get('page');
    const hasPageIndex = typeof pageIndex === 'number';
    return hasPageIndex ? pageIndex : -1;
  }
  
  /**
   * Get the UID of the currently playing track.
   *
   * @return {string} The UID, or empty string if not found.
   */
  function getPlayerTrackUid() {
    return live('spotify:player').get('uid') || '';
  }
  
  /**
   * Get the URI of the currently playing track.
   *
   * @return {string} The track URI, or empty string if nothing is playing.
   */
  function getPlayerTrackUri() {
    const track = live('spotify:player').get('track');
    return track && track.uri || '';
  }
  
  /**
   * Get the playing status of the player.
   *
   * @return {boolean} True if the player is currently playing audio.
   */
  function getPlayerIsPlaying() {
    const isPlaying = live('spotify:player').get('isPlaying');
    const isPaused = live('spotify:player').get('isPaused');
    return isPlaying && !isPaused;
  }
  
  /**
   * Get whether the playback was initiated from open.spotify.com
   *
   * This info is needed to be able to do a best-effort of highlighting rows when
   * playing from open.
   *
   * @return {boolean} True if playing from open.spotify.com.
   */
  function getPlayerIsPlayingFromOpen() {
    const playOrigin = live('spotify:player').get('playOrigin');
    const externalReferrer = playOrigin && playOrigin.get('externalReferrer');
  
    return externalReferrer && externalReferrer.indexOf('https://open.spotify.com/') === 0;
  }
  
  /**
   * Subscribe to player changes.
   *
   * @param {Function} handler A handler function. The data passed to this
   *     function is irrelevent, only the fact that something changed is needed.
   *
   * @return {Object} An object with a `cancel` method to cancel the subscription.
   */
  function subscribeToPlayerChange(handler) {
    live('spotify:player').on('update', handler);
    let active = true;
    return {
      cancel: function () {
        if (active) {
          live('spotify:player').off('update', handler);
          active = false;
        }
      }
    };
  }
  
  /**
   * Handle the click of a play button.
   * This function should decide what the action should be, and pause/resume
   * playback or start playback of a new context.
   *
   * @param {HTMLElement} buttonNode A button DOM node.
   * @param {Object} data Object of data about the action:
   *     isPlayingContext: (boolean) Whether the button looks to be representing
   *         the currently playing context.
   *     contextUri: (string) The context URI for the button.
   *     source: (string) The 'play source' value, for logging purposes.
   *     reason: (string) The 'play reason' value, for logging purposes.
   */
  function handlePlayButton(buttonNode, data) {
    let isPlaying = data.isPlayingContext;
  
    const trackUid = utils.getAttributeValueFromNode(buttonNode, 'data-uid');
    const trackUri = utils.getAttributeValueFromNode(buttonNode, 'data-track-uri');
    const pageIndex = parseInt(utils.getAttributeValueFromNode(buttonNode, 'data-context-page'), 10);
  
    if (isPlaying) {
      isPlaying = verifyIsPlayingIndex(trackUid, trackUri, pageIndex);
    }
  
    if (isPlaying) {
      if (getPlayerIsPlaying()) {
        live('spotify:player').emit('pause');
      } else {
        live('spotify:player').emit('resume');
      }
    } else {
      const eventData = {
        context: data.contextUri,
        source: data.source,
        reason: data.reason
      };
  
      if (trackUid) {
        eventData.uid = trackUid;
      }
  
      if (trackUri) {
        eventData.trackUri = trackUri;
      }
  
      if (!isNaN(pageIndex)) {
        eventData.page = pageIndex;
      }
  
      live('spotify:player').emit('play', eventData);
    }
  }
  
  /**
   * Verify that a uid, uri or pageIndex is playing, or that just the context
   * is playing when none of that is set.
   * This is used when we already know that we're playing the context.
   *
   * @param {string} trackUid
   * @param {string} trackUri
   * @param {number} pageIndex
   *
   * @return {boolean} True if it's really playing
   */
  function verifyIsPlayingIndex(trackUid, trackUri, pageIndex) {
    if (trackUid) {
      const playerTrackUid = getPlayerTrackUid();
      return playerTrackUid === trackUid;
    }
  
    if (trackUri) {
      const playerTrackUri = getPlayerTrackUri();
      return playerTrackUri === trackUri;
    }
  
    if (!isNaN(pageIndex)) {
      const playerPageIndex = getPlayerPageIndex();
      return playerPageIndex === pageIndex;
    }
  
    // No index given also means it's really playing.
    return true;
  }
  
  /**
   * Handle playing a list row. This function should start playback of a context
   * starting from a specific row.
   *
   * @param {HTMLElement} rowNode A DOM node for a list row.
   * @param {Object} data Object of data about the action:
   *     contextUri: (string) The context URI for the list.
   *     source: (string) The 'play source' value, for logging purposes.
   *     reason: (string) The 'play reason' value, for logging purposes.
   */
  function handlePlayFromRow(rowNode, data) {
    const eventData = {
      context: data.contextUri,
      source: data.source,
      reason: data.reason
    };
  
    const uid = rowNode.getAttribute('data-uid');
    if (uid) {
      eventData.uid = uid;
    }
  
    const pageIndex = parseInt(utils.getAttributeValueFromNode(rowNode, 'data-context-page'), 10);
    if (!isNaN(pageIndex)) {
      eventData.page = pageIndex;
    }
  
    live('spotify:player').emit('play', eventData);
  }
  
  /**
   * Get the nodes inside the passed container node that should be marked as
   * playing.
   *
   * @param {HTMLElement} containerNode The DOM node to search inside.
   * @param {Array.<HTMLElement>} contextNodes Array of DOM nodes that are already
   *     matched based on context URIs. These can be filtered if needed, but
   *     otherwise they should be included in the return value.
   *
   * @return {Array.<HTMLElement>} An array of DOM nodes.
   */
  function getPlayingNodes(containerNode, contextNodes) {
    const playerPageIndex = getPlayerPageIndex();
    const playerTrackUid = getPlayerTrackUid();
    const playerTrackUri = getPlayerTrackUri();
    const variantUri = getPlayerVariantUri();
  
    // Remove any matched context nodes that have a page specified and where
    // the current player page does not match.
    const filteredContextNodes = filterPages(playerPageIndex, contextNodes);
  
    const selectors = utils.getContextSelectorsWithVariants(variantUri);
    const rowNodes = getContextNodesForRows(containerNode, selectors, playerTrackUid, playerTrackUri) || [];
  
    const selectorsWithVariants = utils.getContextSelectorsWithVariants(variantUri);
    const pageNodes = getContextNodesForPages(containerNode, selectorsWithVariants, playerPageIndex) || [];
  
    // Only return button nodes whos closest context is one of the nodes in the
    // contextNodes array. Imagine this minimal node tree:
    //
    // [data-context][data-uri="spotify:root-context"]
    //   [data-button=play-context]
    //   [data-context][data-uri="spotify:nested-context"]
    //     [data-button=play-context]
    //
    // The first [data-button=play-context] should play "spotify:root-context".
    // Ths second [data-button=play-context] should play "spotify:nested-context".
    let buttonNodes = [];
    if (contextNodes.length) {
      const candidates = $(contextNodes).search('[data-button=play-context]');
      if (candidates) {
        buttonNodes = candidates.filter(candidate => {
          const closestContextNode = candidate.closest('[data-context]');
          return contextNodes.indexOf(closestContextNode) >= 0;
        });
      }
    }
  
    // Let `elements` remove any duplicate nodes
    const nodes = $([filteredContextNodes, rowNodes, pageNodes, buttonNodes]);
  
    return nodes ? Array.prototype.slice.call(nodes) : [];
  }
  
  /**
   * Get the nodes inside the passed container node that match what's currently
   * playing and are list rows.
   *
   * @param {HTMLElement} containerNode The DOM node to search inside.
   * @param {Array.<string>} selectors Array of CSS selectors for matching the
   *     currently playing context.
   * @param {string} trackUid The UID of the currently playing track.
   *
   * @return {Array.<HTMLElement>} Array of DOM nodes.
   */
  function getContextNodesForRows(containerNode, selectors, trackUid, trackUri) {
    // When playing from this client, we highlight the playing UIDs.
    const useTrackSelector = getPlayerIsPlayingFromOpen();
  
    const selector = selectors.map(function (s) {
      let newSelector = `${s} `;
      if (useTrackSelector) newSelector += `[data-uri=${trackUri}]`;
      if (!useTrackSelector) newSelector += `[data-uid=${trackUid}]`;
      return newSelector;
    }).join(', ');
  
    let nodes = $(containerNode).search(selector);
    if (nodes) {
      nodes = Array.prototype.slice.call(nodes);
  
      if (useTrackSelector) {
        // Gets UID of the first node and find all the nodes
        // with the same UID
        const firstUid = $(nodes[0]).data('uid');
        nodes = nodes.filter(function (row) {
          return firstUid === $(row).data('uid');
        });
      }
    } else {
      nodes = [];
    }
  
    return nodes;
  }
  
  /**
   * Get the nodes inside the passed container node that match what's currently
   * playing and have a matching page specified.
   *
   * @param {HTMLElement} containerNode The DOM node to search inside.
   * @param {Array.<string>} selectors Array of CSS selectors for matching the
   *     currently playing context.
   * @param {number} playerPageIndex The currently playing page index.
   *
   * @return {Array.<HTMLElement>} Array of DOM nodes.
   */
  function getContextNodesForPages(containerNode, selectors, playerPageIndex) {
    const pageAttribute = '[data-context-page]';
  
    // It needs to search for all range elements somewhere inside an element
    // marked up as a context, as well as range elements that themselves are
    // contexts.
    const newSelectors = selectors.map(function (selector) {
      const pageInContextSelector = `${selector} ${pageAttribute}`;
      const pagedContextSelector = selector + pageAttribute;
      const pageSelector = `${pageInContextSelector}, ${pagedContextSelector}`;
      return pageSelector;
    });
  
    let pageNodes = $(containerNode).search(newSelectors.join(', '));
    if (pageNodes) {
      pageNodes = Array.prototype.slice.call(pageNodes);
    } else {
      pageNodes = [];
    }
  
    return filterPages(playerPageIndex, pageNodes);
  }
  
  /**
   * Return a new array of nodes where nodes that specify a context page that
   * doesn't match the currently playing context page are removed.
   *
   * @param {number} playerPageIndex The currently playing page index.
   * @param {Array.<HTMLElement>} contextNodes Array of already matched nodes.
   *
   * @return {Array.<HTMLElement>} Array of filtered nodes.
   */
  function filterPages(playerPageIndex, contextNodes) {
    return contextNodes.filter(function (node) {
      const nodePageIndex = parseInt(node.getAttribute('data-context-page'), 10);
  
      // Node without a page should always be active
      if (isNaN(nodePageIndex)) return true;
  
      // If player has no page, but the node has a page, we can't be sure if it's
      // playing.
      if (isNaN(playerPageIndex)) return false;
  
      // Node that matches the player page should be active
      return playerPageIndex === nodePageIndex;
    });
  }
  
  },{"../../live":172,"../../spotify-elements":210,"./actions":267,"./updates":269,"./utils":270}],273:[function(require,module,exports){
  'use strict';
  
  const Range = require('../range2');
  
  const contains = require('mout/array/contains');
  
  const $ = require('../spotify-elements');
  
  const SYNC = require('../../libs/prime/emitter').EMIT_SYNC;
  const attentionHighlight = require('../../libs/glue').attentionHighlight;
  
  const throttle = require('./util/throttle');
  const ListManager = require('./util/ListManager.js');
  
  const center = require('./center');
  
  const doc = $(document);
  const win = $(window);
  
  let scrollContainers = [];
  let listManagers = [];
  let scrollHandlers = [];
  const pendingScrollContainers = [];
  const pendingHiddenIndices = [];
  let cleanupHandlers = [];
  
  const getBounds = function (view, container) {
    const dde = document.documentElement;
    let newView = view;
  
    if (newView === window || newView === document.body) newView = dde;
    const viewTop = newView === dde ? 0 : newView.getBoundingClientRect().top;
  
    const viewHeight = newView.clientHeight;
    const containerTop = container.getBoundingClientRect().top;
    const containerHeight = container.offsetHeight;
  
    const viewBox = {
      top: viewTop,
      height: viewHeight,
      bottom: viewTop + viewHeight
    };
  
    const containerBox = {
      top: containerTop,
      height: containerHeight,
      bottom: containerTop + containerHeight
    };
  
    return {
      view: viewBox,
      container: containerBox
    };
  };
  
  const getRange = function (view, container, height, hotZone = 0) {
    const bounds = getBounds(view, container);
    const top = -(bounds.container.top - bounds.view.top);
    let startsAt = Math.floor((top - hotZone) / height);
    const endsAt = Math.ceil((top + bounds.view.height + hotZone) / height);
    if (startsAt < 0) startsAt = 0;
    if (endsAt <= startsAt) return false;
    return new Range(startsAt, endsAt);
  };
  
  const init = function (view, container, length, height, tag) {
    const view0 = view[0];
    const container0 = container[0];
    let savedAttentionHighlightIndex = -1;
  
    container.addClass('scroll-container-enabled');
  
    const attentionHighlightIndex = (c, index) => {
      if ($(c) === container) {
        const row = container0.querySelector(`[data-index='${index}']`);
        if (!row) {
          savedAttentionHighlightIndex = index;
          return;
        }
  
        savedAttentionHighlightIndex = -1;
  
        let tableRowAttentionHighlight = document.querySelector('[data-table-row-attention-highlight]');
        if (!tableRowAttentionHighlight) {
          tableRowAttentionHighlight = document.createElement('div');
          tableRowAttentionHighlight.className = 'tl-attention-highlight';
          tableRowAttentionHighlight.setAttribute('data-table-row-attention-highlight', '');
          document.body.appendChild(tableRowAttentionHighlight);
        }
  
        const scrollY = window.pageYOffset;
        const rect = row.getBoundingClientRect();
  
        // Use document width instead of window width to handle the case when
        // there are scroll bars that are not overlay scroll bars.
        const viewportWidth = document.documentElement.offsetWidth;
  
        tableRowAttentionHighlight.classList.add('visible');
        tableRowAttentionHighlight.style.top = `${rect.top + scrollY}px`;
        tableRowAttentionHighlight.style.left = `${rect.left}px`;
        tableRowAttentionHighlight.style.right = `${viewportWidth - rect.right}px`;
  
        setTimeout(() => {
          tableRowAttentionHighlight.classList.remove('visible');
        }, attentionHighlight.HIDE_DELAY);
      }
    };
  
    const manager = new ListManager(container0, {
      height: height,
      length: length,
      tag: tag,
      debug: false,
      request: function (range, callback) {
        center.emit('scroll-request', container0, range, callback, SYNC);
      }
    });
    listManagers.push(manager);
  
    // Update the manager with previously saved hidden indices.
    // This allows the scroller to take hidden indices into account even for the
    // first render.
    const pendingIndex = pendingScrollContainers.indexOf(container);
    if (pendingIndex > -1) {
      manager.reset(length, pendingHiddenIndices[pendingIndex]);
      pendingScrollContainers.splice(pendingIndex, 1);
      pendingHiddenIndices.splice(pendingIndex, 1);
    }
  
    manager.on('before-show', function (range, nodes) {
      center.emit('scroll-show-before', {
        pageNode: container,
        container: container,
        range: range,
        nodes: nodes
      }, SYNC);
    });
  
    manager.on('after-show', function (range, nodes) {
      center.emit('scroll-show-after', {
        pageNode: container,
        container: container,
        range: range,
        nodes: nodes
      }, SYNC);
    });
  
    manager.on('index-change', function (index) {
      center.emit('scroll-index-change', {
        pageNode: container,
        container: container,
        ranges: index
      }, SYNC);
    });
  
    let hasRequestedMore = false;
  
    const scrollReset = function (c, scrollResetHeight, hiddenIndices, fromIndex) {
      if ($(c) === container) {
        const hasFromIndex = typeof fromIndex === 'number';
        if (hasFromIndex && fromIndex > 0) {
          manager.resetFrom(fromIndex, scrollResetHeight, hiddenIndices);
        } else {
          manager.reset(scrollResetHeight, hiddenIndices);
        }
        container._scrollEvent();
      }
    };
  
    const scroll = function () {
      const bounds = getBounds(view0, container0);
      let show = getRange(view0, container0, height);
  
      if (show) {
        manager.on('after-show', function onAfterShow() {
          manager.off('after-show', onAfterShow);
  
          if (savedAttentionHighlightIndex > -1 && savedAttentionHighlightIndex >= show.start && savedAttentionHighlightIndex < show.end) {
            attentionHighlightIndex(container, savedAttentionHighlightIndex);
          }
        });
  
        const padding = 2;
        show = new Range(show.start - padding, show.end + padding);
        manager.show(new Range(show.start, show.end));
      }
  
      // If the scroll position is close enough to the end, an event should be
      // emitted to allow for more content to be loaded. Not all lists will use this,
      // but some lists want to lazy load content by loading more when you reach the
      // bottom, and then they can listen for this event.
      if (!hasRequestedMore) {
        if (bounds.container.bottom < 2000) {
          hasRequestedMore = true;
  
          // Emit the event and also pass a callback to the event.
          // The receiver of this event should pass the new total length of the list
          // as the second argument to the callback.
          center.emit('scroll-request-more', container0, function (error, emitLength) {
            if (error) throw error;
            hasRequestedMore = false;
            scrollReset(container0, emitLength);
          });
        }
      }
    };
  
    scrollHandlers.push(scroll);
    container._scrollEvent = throttle(scroll);
  
    center.on('scroll-reset', scrollReset);
    container._scrollResetEvent = scrollReset;
  
    const scrollToIndex = function (c, index, options = {}) {
      if (view0 !== window) {
        throw new Error("Can't scroll to index if view is not `window`");
      }
  
      if ($(c) === container) {
        const bounds = getBounds(view0, container0);
  
        const offset = options.centerRowIfScrollIsNeeded ? (bounds.view.height - manager.height) / 2 : 0;
  
        const indexTopRelativeToContainer = manager.height * index;
        const position = bounds.container.top + indexTopRelativeToContainer;
  
        let scrollY = position + window.pageYOffset;
        let stickyOffset = (doc.search('[data-sticky-active]') || []).map(function (sticky) {
          return sticky.offsetHeight;
        }).reduce(function (a, b) {
          return a + b;
        }, 0);
  
        if (document.querySelector('[data-top-bar-height]')) {
          stickyOffset += parseInt(document.querySelector('[data-top-bar-height]').getAttribute('data-top-bar-height'), 10);
        }
  
        if (position <= stickyOffset) {
          // item is above viewport
          scrollY -= offset + stickyOffset;
        } else if (position + manager.height > bounds.view.height) {
          // item is below viewport
          scrollY += offset + manager.height - bounds.view.height;
        } else {
          // item is in viewport
          return;
        }
  
        view0.scrollTo(0, scrollY);
      }
    };
  
    const hideAttentionHighlight = () => {
      const tableRowAttentionHighlight = document.querySelector('[data-table-row-attention-highlight]');
      if (tableRowAttentionHighlight) {
        tableRowAttentionHighlight.parentNode.removeChild(tableRowAttentionHighlight);
      }
    };
  
    center.on('scroll-to-index', scrollToIndex);
    center.on('attention-highlight-index', attentionHighlightIndex);
    center.on('hide-attention-highlight', hideAttentionHighlight);
  
    view.on('scroll', scroll);
    win.on('resize', scroll);
    scroll();
  
    cleanupHandlers.push({
      container: container,
      clean: function () {
        center.off('scroll-reset', scrollReset);
        center.off('scroll-to-index', scrollToIndex);
        center.off('attention-highlight-index', attentionHighlightIndex);
        center.off('hide-attention-highlight', hideAttentionHighlight);
        view.off('scroll', scroll);
        win.off('resize', scroll);
  
        delete container._scrollEvent;
        delete container._scrollResetEvent;
  
        savedAttentionHighlightIndex = -1;
  
        container.removeClass('scroll-container-enabled');
      }
    });
  };
  
  function onScrollSetHidden(data) {
    const hiddenIndices = data.indices;
  
    const container = $(data.itemContainer);
    const index = scrollContainers.indexOf(container);
    if (index > -1) {
      const manager = listManagers[index];
      if (manager) {
        manager.reset(manager.length, hiddenIndices);
      }
      const scrollHandler = scrollHandlers[index];
      if (scrollHandler) {
        scrollHandler();
      }
    } else {
      pendingScrollContainers.push(container);
      pendingHiddenIndices.push(hiddenIndices);
    }
  }
  
  let isAttached = false;
  
  exports.update = function (node) {
    if (!isAttached) return;
  
    const newNode = $(node) || doc;
  
    let scrollNodes = doc.search('[data-scroll-container]');
  
    const handlers = [];
    const managers = [];
    scrollContainers = scrollContainers.filter(function (container, index) {
      if (!contains(scrollNodes, container[0])) {
        for (let i = 0; i < cleanupHandlers.length; i++) {
          if (cleanupHandlers[i].container === container) {
            cleanupHandlers[i].clean();
            cleanupHandlers.splice(i, 1);
            break;
          }
        }
        return false;
      }
      managers.push(listManagers[index]);
      handlers.push(scrollHandlers[index]);
      return true;
    });
    listManagers = managers;
    scrollHandlers = handlers;
  
    if (newNode !== doc) scrollNodes = newNode.search('[data-scroll-container]');
  
    if (scrollNodes) {
      scrollNodes.forEach(function (node0) {
        const container = $(node0);
  
        if (container._scrollEvent || node0.children.length) return;
  
        scrollContainers.push(container);
        const view = container.parent('[data-scroll-view]') || win;
  
        const callback = function (error, children, length) {
          if (error) throw error;
  
          let newChildren = children;
  
          // Only continue initialization if the container is still in the DOM
          if (contains(doc.search('[data-scroll-container]'), container[0])) {
            newChildren = $(newChildren);
            newChildren.insert(container);
  
            const second = newChildren[1];
            const height = second.offsetHeight;
            const tag = $(second).tag();
            newChildren.remove();
            init(view, container, length, height, tag);
          }
        };
  
        center.emit('scroll-request', container[0], new Range(0, 3), callback, SYNC);
      });
    }
  };
  
  exports.attach = function () {
    if (isAttached) return;
    isAttached = true;
  
    center.on('scroll-set-hidden', onScrollSetHidden);
  };
  
  exports.detach = function () {
    if (!isAttached) return;
    isAttached = false;
  
    center.off('scroll-set-hidden', onScrollSetHidden);
  
    scrollContainers = [];
    cleanupHandlers.forEach(function ({ clean }) {
      clean();
    });
    cleanupHandlers = [];
  };
  
  },{"../../libs/glue":82,"../../libs/prime/emitter":201,"../range2":204,"../spotify-elements":210,"./center":221,"./util/ListManager.js":287,"./util/throttle":293,"mout/array/contains":434}],274:[function(require,module,exports){
  'use strict';
  
  var _spotifyCosmosApi = require('spotify-cosmos-api');
  
  var _spotifyCosmosApi2 = _interopRequireDefault(_spotifyCosmosApi);
  
  var _tokens = require('@spotify/glue-tokens/tokens.json');
  
  var _tokens2 = _interopRequireDefault(_tokens);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  let isAttached = false;
  let controlMessageSubscription;
  let currentStyle = 'always-visible';
  
  function refreshStyle() {
    const isMac = window.navigator.platform.startsWith('Mac');
    const isWindows = window.navigator.platform.startsWith('Win');
  
    let os = 'linux';
    if (isMac) {
      os = 'mac';
    }
    if (isWindows) {
      os = 'windows';
    }
  
    if (currentStyle === 'always-visible') {
      document.documentElement.classList.add(`scrollbar-style-visible-${os}`);
  
      if (os === 'mac') {
        document.documentElement.classList.remove('scrollbar-style-when-scrolling-mac');
      }
    } else {
      document.documentElement.classList.remove(`scrollbar-style-visible-${os}`);
  
      if (os === 'mac') {
        document.documentElement.classList.add('scrollbar-style-when-scrolling-mac');
      }
    }
  }
  
  // Fix a rendering bug in Chromium. When scroller style changes in the
  // system (changing system preference, connecting/disconnecting a mouse
  // etc), Chromium will not always render properly. Sometimes it shows a
  // white area where the scroll bar is supposed to be and sometimes it
  // renders it with native styles even though the CSS styles should be
  // applied. By changing the scroll bar style temporarily it will force the
  // scroll bars to re-render. We use a background that will most likely not be
  // part of our color palette (with alpha 0 to not show it), to make sure we
  // actually trigger a re-render.
  //
  // https://jira.spotify.net/browse/KM-8285
  // http://crbug.com/538579
  function performFixWithScrollBarBackgroundColor() {
    const styleNode = document.createElement('style');
    styleNode.innerHTML = '::-webkit-scrollbar {background-color: rgba(1, 2, 3, 0) !important}';
    document.head.appendChild(styleNode);
    // Delay removal one tick so that it has time to render
    setTimeout(() => {
      document.head.removeChild(styleNode);
    }, 0);
  }
  
  // Fix a rendering bug in Chromium. When loading an iframe, the scroll bar color
  // is the wrong color (black scroll bar on black background). We can shake this
  // in place by changing the background color on body (the scrollable element).
  //
  // http://crbug.com/811725
  function performFixWithBodyBackgroundColor() {
    document.body.style.backgroundColor = _tokens2.default.GLUE_COLOR_GRAY_10;
  
    // Delay removal one tick so that it has time to render
    setTimeout(() => {
      document.body.style.removeProperty('background-color');
    }, 0);
  }
  
  exports.update = function () {};
  
  exports.attach = function () {
    if (isAttached) return;
    isAttached = true;
  
    const initialValue = window.__spotify && window.__spotify.scroller_style;
    currentStyle = initialValue === 'overlay' ? 'overlay' : 'always-visible';
    refreshStyle();
  
    performFixWithBodyBackgroundColor();
  
    // Sometimes when navigating back and forward in history navigation the
    // scroll bars disappear. They reappear when hovering some elements like the
    // play button on cards that have a scale animation. We can force show them
    // by performing the background color fix to trigger a re-render.
    window.addEventListener('message', event => {
      if (event.data.name === 'set_active' && event.data.active) {
        performFixWithScrollBarBackgroundColor();
      }
    });
  
    controlMessageSubscription = _spotifyCosmosApi2.default.resolver.subscribe({
      url: 'sp://messages/v1/container/control'
    }, (error, response) => {
      if (!error) {
        const data = response.getJSONBody();
        if (data) {
          let styleWasChanged = false;
          switch (data.type) {
            case 'set_scroller_style_always_visible':
              currentStyle = 'always-visible';
              styleWasChanged = true;
              break;
            case 'set_scroller_style_overlay':
              currentStyle = 'overlay';
              styleWasChanged = true;
              break;
            default:
              break;
          }
  
          if (styleWasChanged) {
            refreshStyle();
          }
  
          if (styleWasChanged) {
            // Perform the fix twice (once with a delay), since it sometimes might
            // be slow and won't apply the fix on the first try.
            performFixWithScrollBarBackgroundColor();
            setTimeout(performFixWithScrollBarBackgroundColor, 1000);
          }
        }
      } else {
        controlMessageSubscription.cancel();
      }
    });
  };
  
  exports.detach = function () {
    if (!isAttached) return;
    isAttached = false;
  
    if (controlMessageSubscription) {
      controlMessageSubscription.cancel();
      controlMessageSubscription = null;
    }
  };
  
  },{"@spotify/glue-tokens/tokens.json":317,"spotify-cosmos-api":543}],275:[function(require,module,exports){
  /**
   * @module spotify-events/select
   */
  'use strict';
  
  const $ = require('../spotify-elements');
  const center = require('./center');
  
  const live = require('../live');
  
  const ATTR_SELECT = 'select';
  const ATTR_PROPERTY = 'select-property';
  const SELECTOR_SELECT = `[data-${ATTR_SELECT}]`;
  
  // Stored nodes, used to not add listeners multiple times
  const storedNodes = [];
  
  /**
   * Handle a change on a select node.
   *
   * @param {MouseEvent} event Event object.
   * @param {Elements} elem An elements DOM node for the select.
   */
  function handleChange(event, elem) {
    const property = elem.data(ATTR_PROPERTY);
    const node = elem[0];
    // If a property is set, update the model. That will later trigger
    // an update of the view and a change event on center will be sent.
    if (property) {
      publish(node, property);
      // If no property is set, just emit a change event on center.
    } else {
      emitUpdate(node, selectedValue(node));
    }
  }
  
  /**
   * Return the value attribute of the selected option
   *
   * @param {Elements} elem An elements DOM node for the select
   */
  function selectedValue(node) {
    const selected = node.options[node.selectedIndex];
    return selected.value;
  }
  
  /**
   * Handle when a select-option event happens on center.
   *
   * @param {Object} event An object with data.
   */
  function onSelectSet(event) {
    if (!event.id) return;
  
    const elem = $(`[data-${ATTR_SELECT}=${event.id}]`);
    if (!elem) return;
  
    const node = elem[0];
    const value = event.selected;
  
    // updating the node will trigger a DOM event,
    // delegated to handleChange in this module.
    const property = elem.data(ATTR_PROPERTY);
  
    if (property) {
      publish(node, property, value);
    } else {
      setState(node, value);
    }
  }
  
  /**
   * Publish new data to the live model. If no data-uri is found from
   * the passed node, nothing happens.
   *
   * @param {HtmlElement} node A DOM node for the select
   * @param {string} property A property name on the live model
   * @param {string=} optValue Optional value to set the property to.
   *     If not provided, the selected option's value is fetched from the DOM.
   */
  function publish(node, property, optValue) {
    const uri = getUriFromNode(node);
    if (!uri) return;
  
    const model = live(uri);
  
    // If the model doesn't have a current value, we can't safely
    // set a new value without maybe being wrong.
    const value = model.get(property);
    if (value === undefined) return;
  
    const newValue = optValue === undefined ? selectedValue(node) : optValue;
  
    const data = {};
    data[property] = newValue;
  
    // Publishing the new value to the model will trigger the update
    // handler added here in this module to send an center event,
    // and update the DOM if needed.
    model.publish(data);
  }
  
  /**
   * Update the state of one select node.
   *
   * @param {HTMLElement} node A DOM node for the select.
   */
  function updateNode(node) {
    if (storedNodes.indexOf(node) > -1) return;
    storedNodes.push(node);
  
    const elem = $(node);
    const property = elem.data(ATTR_PROPERTY);
    if (!property) return;
  
    const uri = getUriFromNode(node);
    if (!uri) return;
  
    const model = live(uri);
  
    model.get(property, function (error, value) {
      if (error) throw error;
  
      setState(node, value);
  
      model.on('update', function (properties) {
        if (property in properties) {
          setState(node, properties[property]);
        }
      });
    });
  }
  
  /**
   * Update a select node based on a value.
   *
   * @param {HTMLElement} node A DOM node for a select.
   * @param {string} value The value attribute of the option to select
   */
  function setState(node, value) {
    updateNodeWithValue(node, value);
    emitUpdate(node, value);
  }
  
  /**
   * Update a select based on an option's value attribute
   *
   * @param {HTMLElement} node A DOM node for a select.
   * @param {string} value The value attribute of the option to select
   */
  function updateNodeWithValue(node, value) {
    const selectElement = $(node);
    const optionElement = selectElement.find(`option[value="${value}"]`);
    if (optionElement && !optionElement.attribute('selected')) {
      optionElement.select();
    }
  }
  
  /**
   * Emit a change event on center. It will have the following format:
   * {
   *   element: HTMLElement,
   *   id: 'id from data-select attribute' || null,
   *   selected: the new selected value
   * }
   *
   * @param {HTMLElement} node A DOM node for a select.
   * @param {string} value Attribute of the selected option
   */
  function emitUpdate(node, selected) {
    const id = $(node).data(ATTR_SELECT) || null;
    center.emit('select-change', {
      element: node,
      id: id,
      selected: selected
    });
  }
  
  /**
   * Get a URI from a node.
   *
   * @param {HTMLElement} node A DOM node.
   *
   * @return {string?} A URI string or null if not found.
   */
  function getUriFromNode(node) {
    const stateNode = node.closest('[data-uri]');
    if (!stateNode) return null;
    return stateNode.getAttribute('data-uri');
  }
  
  let isAttached = false;
  
  exports.update = function (optNode) {
    if (!isAttached) return;
  
    const nodes = $(optNode || document).search(SELECTOR_SELECT);
    if (!nodes) return;
  
    nodes.forEach(updateNode);
  };
  
  exports.attach = function () {
    if (isAttached) return;
    isAttached = true;
  
    const doc = $(document);
    doc.delegate('change', SELECTOR_SELECT, handleChange);
  
    center.on('select-set', onSelectSet);
  };
  
  exports.detach = function () {
    if (!isAttached) return;
    isAttached = false;
  
    const doc = $(document);
    doc.undelegate('change', SELECTOR_SELECT, handleChange);
  
    center.off('select-set', onSelectSet);
  };
  
  },{"../live":172,"../spotify-elements":210,"./center":221}],276:[function(require,module,exports){
  /**
   * @module spotify-events/selection/containers
   * @private
   */
  'use strict';
  
  const $ = require('../../spotify-elements');
  const live = require('../../live');
  
  /**
   * Container DOM nodes.
   *
   * @type {Array.<HTMLElement>}
   */
  const containers = [];
  
  /**
   * Forget the known containers.
   */
  function reset() {
    containers.length = 0;
  }
  
  /**
   * Update container storage based on the current DOM.
   */
  function update() {
    const nodes = $(document).search(':not(.sticky-clone) > [data-list]');
    if (!nodes) return;
  
    // Don't drop any containers. If they are removed from the DOM we just keep
    // the reference along with the selection state at the same index. Clearing
    // has to be done manually instead.
  
    nodes.forEach(function (container) {
      if (!containers.includes(container)) {
        containers.push(container);
        containers.sort((a, b) => {
          let n;
          if (a === b) {
            n = 0;
          } else if (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_PRECEDING) {
            n = 1;
          } else {
            n = -1;
          }
          return n;
        });
      }
    });
  }
  
  /**
   * Get the number of rows in the container at the specified index.
   *
   * @param {number} index The container index.
   *
   * @return {number} The number of rows.
   */
  function getContainerLength(index) {
    const container = $(containers[index]);
    if (!container) return 0;
  
    const uri = container.data('uri');
    if (!uri) return 0;
  
    const list = live(uri).get('rows');
    if (!list) return 0;
  
    return list.length;
  }
  
  /**
   * Get the index of a container based on URI.
   *
   * @param {string} uri The URI of the container.
   *
   * @return {number} The index of the container. Returns -1 if not found.
   */
  function getContainerIndex(uri) {
    for (let i = 0, l = containers.length; i < l; i++) {
      const containerUri = getUri(i);
      if (containerUri === uri) return i;
    }
  
    return -1;
  }
  
  /**
   * Get the URI for a container.
   *
   * @param {number} index The container index.
   *
   * @return {string?} The URI or null if not found.
   */
  function getUri(index) {
    const element = containers[index];
    const uri = element && element.getAttribute('data-uri');
    return uri || null;
  }
  
  /**
   * Get the URIs for all containers.
   *
   * @return {Array.<string?>} Array of URIs.
   */
  function getUris() {
    const uris = [];
    for (let i = 0, l = containers.length; i < l; i++) {
      uris.push(getUri(i));
    }
    return uris;
  }
  
  /**
   * Get the live list for a container.
   *
   * @param {number} index The container index.
   *
   * @return {LiveList?} A live list or null if not found.
   */
  function getLiveList(index) {
    const uri = getUri(index);
    const list = live(uri).get('rows');
    return list || null;
  }
  
  exports.elements = containers;
  exports.reset = reset;
  exports.update = update;
  exports.getContainerLength = getContainerLength;
  exports.getContainerIndex = getContainerIndex;
  exports.getUri = getUri;
  exports.getUris = getUris;
  exports.getLiveList = getLiveList;
  
  },{"../../live":172,"../../spotify-elements":210}],277:[function(require,module,exports){
  (function (global){
  /**
   * spotify-events/selection/controller
   * @private
   */
  'use strict';
  
  const $ = require('../../spotify-elements');
  const intersection = require('mout/array/intersection');
  const live = require('../../live');
  const liburi = require('spotify-liburi');
  
  const center = require('../center');
  const selection = require('./index');
  const model = require('./model');
  const rows = require('./rows');
  const containers = require('./containers');
  const positions = require('./positions');
  const Position = require('../util/Position');
  const cosmos = require('../util/cosmos');
  
  let focusEventValid = false;
  let mouseDownTarget = null;
  
  // Listeners per URI (key is URI)
  const listListeners = {};
  const listListenerUris = [];
  
  const SELECTABLE = '[data-list-item]';
  const CLIPBOARD_ENDPOINT = 'sp://desktop/v1/clipboard';
  
  /**
   * Check whether the event occured on the documentElement.
   *
   * @param {Event} event A event object.
   *
   * @return {boolean}
   */
  function isOnDocument(event) {
    return event.target === document.documentElement;
  }
  
  /**
   * Get the row node from a mouse event.
   *
   * @param {MouseEvent} event A mouse event object.
   *
   * @return {HTMLElement} A DOM node.
   */
  function getNodeFromEvent(event) {
    return event.target !== document && event.target.closest(SELECTABLE);
  }
  
  /**
   * Check if a target is inside a filter component.
   *
   * @param {HTMLElement} target A DOM node.
   *
   * @return {boolean} Whether the target was inside a filter component.
   */
  function isFilterTarget(target) {
    return target !== document && !!target.closest('[data-filter]');
  }
  
  /**
   * Handle a mousedown event.
   *
   * @param {MouseEvent|TouchEvent} event A mouse or touch event.
   * @param {boolean=} isMouseUp Pass true if this is a simulated mousedown
   *     triggered by a mouseup event.
   */
  function handleMouseDown(event, isMouseUp) {
    if (isOnDocument(event)) {
      return;
    }
  
    focusEventValid = false;
    const node = getNodeFromEvent(event);
  
    if (isMouseUp) {
      if (event.target !== mouseDownTarget) {
        mouseDownTarget = null;
        return;
      }
      mouseDownTarget = null;
    } else {
      mouseDownTarget = event.target;
    }
  
    if (node) {
      handleRowMouseDown(event, node, isMouseUp);
    } else {
      handleOutsideMouseDown(event);
    }
  }
  
  /**
   * Handle when the mouse is pressed down on a list row.
   *
   * @param {MouseEvent|TouchEvent} event A mouse or touch event.
   * @param {HTMLElemtn} rowNode The DOM node for the clicked row.
   * @param {boolean=} isMouseUp Pass true if this is a simulated mousedown
   *     triggered by a mouseup event.
   */
  function handleRowMouseDown(event, rowNode, isMouseUp) {
    const button = getButtonFromEvent(event);
    const isPrimaryButton = button === 1;
  
    // If we can't find a valid position for the DOM node, we can't handle
    // selection correctly.
    const position = positions.getFromNode(rowNode);
    if (!position) return;
  
    const isSelected = model.isSelected(position);
    let singleMultiSelect = isMac() ? event.metaKey : event.ctrlKey;
    let multiselect = event.shiftKey || singleMultiSelect;
    let singleClick = !event.shiftKey;
  
    if (!isMultiSelectEnabled()) {
      singleMultiSelect = multiselect = false;
      singleClick = true;
    }
  
    // Remove selection if a selected item is clicked with cmd/ctrl
    if (multiselect && singleClick && isSelected && isPrimaryButton) {
      // For multiselect, make sure we do the deselection only on mouseup
      if (!isMouseUp) return;
      handleDeselectOnSingleMultiSelect(position);
  
      // Select multiple items if clicked with shift key
    } else if (!singleClick) {
      // Optimize by doing this only on mousedown
      if (isMouseUp) return;
      handleShiftMultiSelect(position);
  
      // Select a single item
    } else {
      // Handle the case when the primary button is pressed down on a selected item.
      // The method will be called once again in that case, on mouse up. This is
      // to allow the drag and drop to abort the selection change.
  
      // So we do the actual selection on mouseup, where we know it's not a drag n drop thing.
      if (isPrimaryButton && isSelected && !isMouseUp) {
        return;
      }
      // For multiselect, make sure we do the selection only on mouseup
      if (multiselect && !isMouseUp) {
        return;
      }
  
      handleSingleSelect(position, event, isMouseUp);
    }
    rows.update();
  }
  
  /**
   * Handle when the mouse is pressed down outside a list row.
   *
   * @param {MouseEvent} event A mouse event.
   */
  function handleOutsideMouseDown(event) {
    const isSort = event.target !== document && !!event.target.closest('[data-sort]');
    const isFilter = isFilterTarget(event.target);
    const hasSelection = model.hasSelection();
  
    // Deselect if clicked outside the list.
    // Clicking sort headers should just sort, not clear selection.
    if (!(isSort || isFilter) && hasSelection) {
      model.clear();
      rows.update();
    }
  }
  
  /**
   * Handle a focus event.
   *
   * @param {MouseEvent} event A mouse event.
   */
  function handleFocus(event) {
    if (!focusEventValid) return;
    const node = getNodeFromEvent(event);
    if (node) {
      handleRowFocus(event, node);
    } else {
      handleOutsideFocus(event);
    }
    focusEventValid = false;
  }
  
  /**
   * Handle when a row receives focus.
   *
   * @param {MouseEvent} event A mouse event.
   * @param {HTMLElement} rowNode The DOM node for the focused row.
   */
  function handleRowFocus(event, rowNode) {
    const position = positions.getFromNode(rowNode);
  
    // Invalid positions and already selected rows don't need to be handled
    if (!position || model.isSelected(position)) {
      return;
    }
  
    model.clear();
    model.add(position);
    model.setFocus(position);
    model.setOrigin(position);
    rows.update();
  }
  
  /**
   * Handle when an element outside the table receives focus.
   *
   * @param {MouseEvent} event A mouse event.
   */
  function handleOutsideFocus(event) {
    if (isFilterTarget(event.target)) return;
    model.clear();
    rows.update();
  }
  
  /**
   * Handle when the mouse button is released.
   *
   * @param {MouseEvent|TouchEvent} event A mouse or touch event.
   */
  function handleMouseUp(event) {
    if (isOnDocument(event)) {
      return;
    }
  
    handleMouseDown(event, true);
  }
  
  /**
   * Handle deselecting a currently selected row in a multi-select situation
   * (cmd/ctrl). This might mean that we need to adjust origin and focus as well.
   *
   * @param {Position} position Position clicked on.
   */
  function handleDeselectOnSingleMultiSelect(position) {
    const origin = model.getOrigin();
    const focus = model.getFocus();
    let dir = positions.getDirection(origin, focus);
  
    // When removing the node that is both origin and focus, there will be no
    // direction in which the origin/focus could be moved. Like here:
  
    // Click A, CMD click C, CMD click B, CMD click B again.
  
    // Native desktop will now still have origin on B. MacOS Finder does move
    // origin but it's hard to know how it decides.
  
    // For a predictable behaviour, default to look downwards, and always reverse
    // the direction when no selected node is found.
  
    if (!dir) dir = 1;
  
    const isOrigin = position.isSame(origin);
    const isFocus = position.isSame(focus);
    let above;
    let below;
  
    if (isOrigin || isFocus) {
      above = getClosestSelected(position, 'up');
      below = getClosestSelected(position, 'down');
    }
  
    // If direction is down (1), the next after origin is found below
    if (isOrigin) {
      model.setOrigin(dir === 1 ? below || above : above || below);
    }
  
    // If direction is down (1), the next after focus is found above
    if (position.isSame(focus)) {
      model.setFocus(dir === 1 ? above || below : below || above);
    }
  
    model.remove(position);
  }
  
  /**
   * Handle multi-selecting a range from the current focus to the new position.
   * This also handles deselecting as needed.
   *
   * @param {Position} position Position clicked on.
   */
  function handleShiftMultiSelect(position) {
    let origin = model.getOrigin();
    const focus = model.getFocus();
  
    // Remove selection from origin to old focus.
    // If it's part of the new selection it will be re-added.
    if (origin && focus && !origin.isSame(focus)) {
      model.remove(origin, focus);
    }
  
    // If there is no origin, we will set the origin to the first selectable row
    if (!origin) {
      origin = new Position(0, 0);
      model.setOrigin(origin);
    }
  
    // Add selection from the current origin to the clicked position
    model.add(origin, position);
    model.setFocus(position);
  }
  
  /**
   * Handle a normal mousedown without any multi-select keys.
   *
   * @param {Position} position Position clicked on.
   * @param {MouseEvent|TouchEvent} event A mouse or touch event.
   */
  function handleSingleSelect(position, event) {
    const origin = model.getOrigin();
  
    const button = getButtonFromEvent(event);
    const isPrimaryButton = button === 1;
    const isSecondaryButton = button === 2;
  
    const isSelected = model.isSelected(position);
    let singleMultiSelect = isMac() ? event.metaKey : event.ctrlKey;
    let multiselect = event.shiftKey || singleMultiSelect;
  
    if (!isMultiSelectEnabled()) {
      singleMultiSelect = multiselect = false;
    }
  
    // Left click without cmd/ctrl/shift clears selection.
    // Also right click on something that wasn't selected clears.
    const isRegularPrimary = !multiselect && isPrimaryButton;
    const isSelectedSecondary = !isSelected && isSecondaryButton;
    if (isRegularPrimary || isSelectedSecondary) {
      model.clear();
    }
  
    // Add item to selection
    model.add(position);
    model.setFocus(position, { isTouch: isTouchEvent(event) });
  
    // When adding to an existing selection, leave origin alone.
    let isNextToSelection;
    if (origin && !origin.isSame(position)) {
      const next = positions.getClosest(position, 'down');
  
      isNextToSelection = next && model.isSelected(next);
      if (!isNextToSelection) {
        const prev = positions.getClosest(position, 'up');
        isNextToSelection = prev && model.isSelected(prev);
      }
    }
    if (!isNextToSelection) {
      model.setOrigin(position);
    }
  }
  
  /**
   * Handler for when a key is pressed down in the list. Depending on the key,
   * this will either move the selection (up and down arrows), shrink/grow the
   * selection (shift up and down arrows) or clear the selection (escape key).
   *
   * @param {KeyboardEvent} event The event object.
   */
  function handleKeyDown(event) {
    if (isFilterTarget(event.target)) return;
  
    focusEventValid = true;
    const isUp = event.keyCode === 38;
    const isDown = event.keyCode === 40;
  
    if (!isKeyboardNavEnabled()) return;
  
    if (!isUp && !isDown) return;
  
    const origin = model.getOrigin();
    const focus = model.getFocus();
    let next = focus;
  
    do {
      next = next && positions.getClosest(next, isDown ? 'down' : 'up');
    } while (next && model.isPositionHidden(next));
  
    // Only move the selection if the up or down key was pressed.
    // And only do it if the cmd/ctrl key is not also pressed.
    // The cmd/ctrl key will change the volume in the client.
    if ((isUp || isDown) && !event.metaKey && !event.ctrlKey && next) {
      // Multi-select must have a previous selection position
      if (event.shiftKey && isMultiSelectEnabled() && origin && focus) {
        const keyDirection = isDown ? 1 : -1;
        const direction = positions.getDirection(origin, focus);
        const isSingleRange = direction === 0;
        if (isSingleRange || keyDirection === direction) {
          model.add(next);
          scrollIntoView(next);
        } else {
          model.remove(focus);
        }
  
        // If multi-select can't be performed, just move the current selection
      } else {
        model.clear();
        model.add(next);
        model.setOrigin(next);
        scrollIntoView(next);
        event.preventDefault(); // Prevent normal scrolling
      }
  
      // Always set the new focus to the row above or below
      model.setFocus(next);
    }
  
    rows.update();
  }
  
  /**
   * Handle when a section of a list is scrolled into view.
   * This needs to update selection state of the row nodes.
   *
   * @param {Object} event Event object.
   */
  function handleScrollShowAfter(event) {
    const node = event.pageNode && event.pageNode[0];
    rows.update(node);
  }
  
  /**
   * Handle setting which indices are hidden.
   *
   * @param {Object} data Object with properties `hiddenIndices` (array of
   *     indices) and `uri` (URI of the list).
   */
  function handleSetHidden(data) {
    model.setHiddenIndices(data.indices, data.uri);
  }
  
  /**
   * Add a row to the selection based on a list URI.
   * TODO: Remove logic to handle global lists.
   *
   * @param {string} listUri The list URI.
   * @param {number} index The index within the list.
   * @param {boolean=} doNotScrollIntoView Optionally skip scrolling added index into view
   */
  function handleAddByUri(listUri, index, doNotScrollIntoView) {
    const uriNodes = $(`[data-uri=${listUri}]`);
    const matches = intersection(containers.elements, uriNodes);
    let newIndex = index;
  
    if (!matches || matches.length < 1) {
      return;
    }
  
    const container = $(matches[0]);
    let containerIndex = container && containers.elements.indexOf(container[0]);
    const listIsPlayContext = container && container.matches('[data-context]');
  
    if (listIsPlayContext) {
      // Selection is not based on the global container that is defined via data-context,
      // so the indices need to be resolved to match the selection containers.
      const localPosition = positions.getPositionFromGlobalIndex(newIndex);
      containerIndex = localPosition.containerIndex;
      newIndex = localPosition.index;
    } else if (containerIndex === -1) {
      console.warn("No container matching this uri was found or selection doesn't know " + 'about the container. Possibly you need to run events.update.');
      return;
    }
  
    if (!isMultiSelectEnabled()) model.clear();
  
    const position = new Position(containerIndex, newIndex);
    if (!model.getOrigin()) model.setOrigin(position);
    model.setFocus(position);
    model.add(position);
  
    rows.update();
  
    if (doNotScrollIntoView !== true) {
      scrollIntoView(position);
    }
  }
  
  /**
   * Handle a select_all event from Cosmos.
   */
  function handleSelectAll() {
    if (!isMultiSelectEnabled()) {
      return;
    }
  
    model.clear();
  
    const first = new Position(0, 0);
    model.setOrigin(first);
  
    const last = positions.getLastPositionForSelectAll();
    model.setFocus(last);
  
    model.add(first, last);
    rows.update();
  }
  
  function handleCopy() {
    cosmos.put({
      url: CLIPBOARD_ENDPOINT,
      body: handleGetUris().map(uri => {
        const uriObject = liburi.from(uri);
        return uriObject && uriObject.toOpenURL();
      }).filter(Boolean).join('\n')
    });
  }
  
  function handleCopyAdvanced() {
    cosmos.put({
      url: CLIPBOARD_ENDPOINT,
      body: handleGetUris().filter(Boolean).join('\n')
    });
  }
  
  /**
   * Handle a cut event from Cosmos.
   */
  function handleCut() {
    handleCopy();
  
    cosmos.post({
      url: 'sp://messages/v1/container/control',
      body: { type: 'delete' }
    });
  }
  
  /**
   * Gets list of uris for selected rows
   *
   * @return {Array.<string>} Array of URIs.
   */
  function handleGetUris() {
    const selections = model.selections;
    const uris = [];
  
    let containerSelection;
    const containerSelectionByKey = key => {
      return !!containerSelection[key];
    };
  
    for (let container = 0; container < selections.length; container++) {
      containerSelection = selections[container];
      if (!containerSelection) continue;
  
      let entity;
  
      const list = containers.getLiveList(container);
      if (!list) continue;
  
      // * Order the keys
      // Since the user can select tracks in any order,
      // the selection is an unordered set until we actually retrieve the
      // uris of the selection. In this case we want to get the uris
      // in the same order as they are shown in the list.
      const orderedSelectedKeys = list.keys.filter(containerSelectionByKey);
  
      for (let i = 0, l = orderedSelectedKeys.length; i < l; i++) {
        const item = list.valueOf(orderedSelectedKeys[i]);
        if (!item) continue;
  
        entity = item.get('track');
        if (!entity) entity = item.get('playlist');
        if (!entity) entity = item.get('album');
        if (!entity) entity = item.get('artist');
        if (!entity) entity = item.get('user');
        if (!entity) entity = item;
  
        uris.push(entity.get('uri'));
      }
    }
  
    return uris;
  }
  
  /**
   * Get the mouse button number identifier from an event object.
   *
   * @param {MouseEvent|TouchEvent} event A mouse event.
   *
   * @return {number} A number representing the pressed button:
   *     0: No button pressed
   *     1: Primary button (usually left)
   *     2: Secondary button (usually right)
   *     3: Middle (usually the wheel)
   */
  function getButtonFromEvent(event) {
    // Touches don't have buttons, so treat all touches as a primary button
    if (isTouchEvent(event)) {
      return 1;
    }
  
    switch (event.button) {
      case 0:
        return 1;
      case 2:
        return 2;
      case 1:
        return 3;
      default:
        return 0;
    }
  }
  
  /**
   * Check if the user agent is on a Mac.
   *
   * @return {boolean} True if Mac, false otherwise.
   */
  function isMac() {
    if (!global.window) return false;
    if (!global.window.navigator) return false;
    const userAgent = global.window.navigator.userAgent || '';
    return userAgent.indexOf('Mac') > -1;
  }
  
  /**
   * Check if multi-select is enabled.
   * We currently disable it for the Web Player, since not much can be done with
   * a multi-selection there.
   *
   * @return {boolean} True if enabled, false otherwise.
   */
  function isMultiSelectEnabled() {
    if ($('body').data('multi-select-disabled') !== null) return false;
    if (!global.window) return false;
    return !!global.window._getSpotifyModule;
  }
  
  /**
   * Check if keyboard selection is enabled.
   * If it's not enabled it is not possible to move the selection with arrow
   * keys or extend it with shift + arrow keys.
   *
   * @return {boolean} True if enabled, false otherwise.
   */
  function isKeyboardNavEnabled() {
    return $('body').data('keyboard-select-disabled') === null;
  }
  
  /**
   * Check if the event is a touch event.
   *
   * @param {Event} event Event object.
   *
   * @return {boolean} True if it is a touch event object.
   */
  function isTouchEvent(event) {
    return !!event.changedTouches;
  }
  
  /**
   * Get the closest selected position in the specified direction.
   *
   * @param {Position} position A position.
   * @param {string} direction The direction to search in: 'up' or 'down'.
   *
   * @return {Position?} A position or null if not found.
   */
  function getClosestSelected(position, direction) {
    let newPosition = position;
    while (newPosition) {
      newPosition = positions.getClosest(newPosition, direction);
      if (newPosition && model.isSelected(newPosition)) {
        return newPosition;
      }
    }
    return null;
  }
  
  /**
   * Scroll the position into view.
   *
   * @param {Position} position A position.
   */
  function scrollIntoView(position) {
    const container = containers.elements[position.containerIndex];
    const scrollContainer = container && container.querySelector('[data-scroll-container]');
    if (scrollContainer) {
      center.emit('scroll-to-index', scrollContainer, position.index);
    } else {
      const node = positions.getNodeFromPosition(position);
      if (!node) return;
  
      const offset = node.getBoundingClientRect().top;
      if (offset < 0 || offset >= window.innerHeight) {
        node.scrollIntoView(offset < 0);
      }
    }
  }
  
  /**
   * Update the list listeners for the current containers.
   */
  function updateListListeners() {
    const uris = containers.getUris();
  
    for (let i = 0, l = uris.length; i < l; i++) {
      const uri = uris[i];
      if (uri && listListenerUris.indexOf(uri) === -1) {
        const list = live(uri).get('rows');
        if (list) {
          const listener = createListListener(uri);
          list.on('update', listener);
          listListeners[uri] = listener;
          listListenerUris.push(uri);
        }
      }
    }
  }
  
  /**
   * Create an event listener for list updates.
   * When an update happens, the selection model will be updated for the right
   * container.
   *
   * @param {string} uri A URI for a list.
   *
   * @return {function} A listener function.
   */
  function createListListener(uri) {
    return function (operations) {
      const uris = containers.getUris();
      const containerIndex = uris.indexOf(uri);
      if (containerIndex > -1) {
        // If something was removed from the list, we must update the selection
        // state, as something that was selected might have been removed.
        // Insert and move operations don't affect the current selection state,
        // as everything is based on row IDs, which don't change.
        for (let i = 0, l = operations.length; i < l; i++) {
          const operation = operations[i];
          if (operation.type === 'remove') {
            model.updateContainer(containerIndex);
            break;
          }
        }
  
        // Update the rows visually based on the model state
        rows.update();
      }
    };
  }
  
  /**
   * Remove all list listeners.
   */
  function resetListListeners() {
    for (let i = 0, l = listListenerUris.length; i < l; i++) {
      const uri = listListenerUris[i];
      const listener = listListeners[uri];
      const list = live(uri).get('rows');
      if (list && listener) {
        list.off('update', listener);
        delete listListeners[uri];
        listListenerUris.splice(i, 1);
        i--;
        l--;
      }
    }
  }
  
  function handleListSwap(event) {
    const rowsListBefore = live(event.before).get('rows');
    if (!rowsListBefore) return;
  
    const selectionBefore = selection.getIndicesPerList().filter(function (indicesPerList) {
      return indicesPerList.uri === event.before;
    })[0];
  
    let selectedKeysBefore = [];
    if (selectionBefore) {
      selectedKeysBefore = selectionBefore.indices.map(function (index) {
        return rowsListBefore.keys[index];
      });
    }
  
    center.on('scroll-reset', function onScrollReset() {
      center.off('scroll-reset', onScrollReset);
  
      live(event.after).get('rows', function (error, rowsListAfter) {
        selectedKeysBefore.forEach(function (selectedKeyBefore) {
          const indexAfter = rowsListAfter.keys.indexOf(selectedKeyBefore);
          if (indexAfter !== -1) {
            handleAddByUri(event.after, indexAfter, true);
          }
        });
      }, live.ASAP);
    });
  }
  
  exports.handleMouseDown = handleMouseDown;
  exports.handleMouseUp = handleMouseUp;
  exports.handleKeyDown = handleKeyDown;
  exports.handleFocus = handleFocus;
  exports.handleScrollShowAfter = handleScrollShowAfter;
  exports.handleSetHidden = handleSetHidden;
  exports.handleAddByUri = handleAddByUri;
  exports.handleSelectAll = handleSelectAll;
  exports.handleCopy = handleCopy;
  exports.handleCopyAdvanced = handleCopyAdvanced;
  exports.handleCut = handleCut;
  exports.handleGetUris = handleGetUris;
  exports.updateListListeners = updateListListeners;
  exports.resetListListeners = resetListListeners;
  exports.handleListSwap = handleListSwap;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../../live":172,"../../spotify-elements":210,"../center":221,"../util/Position":288,"../util/cosmos":290,"./containers":276,"./index":278,"./model":279,"./positions":280,"./rows":281,"mout/array/intersection":441,"spotify-liburi":553}],278:[function(require,module,exports){
  /**
   * spotify-events/selection
   */
  'use strict';
  
  const $ = require('../../spotify-elements');
  const live = require('../../live');
  const getOriginUri = require('../../live-wrapped-uri').getOriginUri;
  
  const center = require('../center');
  const controller = require('./controller');
  const containers = require('./containers');
  const model = require('./model');
  const rows = require('./rows');
  const positions = require('./positions');
  const Position = require('../util/Position');
  const appUtil = require('../util/app');
  
  const cosmos = require('../util/cosmos');
  
  let controlMessageSubscription;
  
  /**
   * Attach event listeners etc.
   */
  exports.attach = function () {
    const doc = $(document);
    doc.on('mousedown', controller.handleMouseDown);
    doc.on('touchstart', controller.handleMouseDown);
    doc.on('mouseup', controller.handleMouseUp);
    doc.on('touchend', controller.handleMouseUp);
    doc.on('keydown', controller.handleKeyDown);
    doc.on('focus', controller.handleFocus, true);
  
    center.on('scroll-show-after', controller.handleScrollShowAfter);
    center.on('selection-set-hidden', controller.handleSetHidden);
    center.on('list-swap', controller.handleListSwap);
  
    // Creating an "intentional bug".. By switching to use row IDs internally
    // for the selection model, we no longer need to manually update indices
    // as long as the new list has the same IDs (which we require anyway for
    // other reasons). We stopped using this event a long time ago anyway,
    // because it never really worked without odd behaviors. I've done a
    // code search (indexed Nov 23 2014), and there is not a single place
    // where this event is used, except in old versions of spotify-events,
    // which is not a problem. Technically, not responding to this event
    // is a breaking change, but for simplicity's sake let's stop handling it.
    // Fixing code that never gets used just for the sake of it makes no sense.
    // If someone really needs this in the future, we can fix it then.
    // So, this line should be commented for now.
    //
    // center.on('update-indices', controller.handleUpdateIndices);
  
    controlMessageSubscription = cosmos.subscribe({ url: 'sp://messages/v1/container/control' }, function (error, response) {
      if (error) throw error;
      const data = response.body;
      if (data && appUtil.isActiveAndFocused()) {
        if (data.type === 'select_all') {
          controller.handleSelectAll();
        } else if (data.type === 'copy') {
          controller.handleCopy();
        } else if (data.type === 'copy_advanced') {
          controller.handleCopyAdvanced();
        } else if (data.type === 'cut') {
          controller.handleCut();
        }
      }
    });
  };
  
  /**
   * Detach event listeners etc.
   */
  exports.detach = function () {
    const doc = $(document);
    doc.off('mousedown', controller.handleMouseDown);
    doc.off('touchstart', controller.handleMouseDown);
    doc.off('mouseup', controller.handleMouseUp);
    doc.off('touchend', controller.handleMouseUp);
    doc.off('keydown', controller.handleKeyDown);
    doc.off('focus', controller.handleFocus, true);
  
    center.off('scroll-show-after', controller.handleScrollShowAfter);
    center.off('selection-set-hidden', controller.handleSetHidden);
    center.off('list-swap', controller.handleListSwap);
  
    // See comment in `attach` for the long story why this is commented.
    // center.off('update-indices', controller.handleUpdateIndices);
  
    if (controlMessageSubscription) {
      controlMessageSubscription.cancel();
      controlMessageSubscription = null;
    }
  };
  
  /**
   * Update the module based on the current DOM state.
   */
  exports.update = function () {
    containers.update();
    controller.updateListListeners();
  };
  
  /**
   * Add a row to the selection based on a list URI.
   *
   * @param {string} listUri The list URI.
   * @param {number} index The index within the list.
   */
  exports.add = function (listUri, index) {
    controller.handleAddByUri(listUri, index);
  };
  
  /**
   * Check if the selection has any holes (contains many ranges).
   *
   * @return {boolean} True if it has holes, false otherwise.
   */
  exports.hasHoles = function () {
    return model.hasHoles();
  };
  
  /**
   * Check if the row node is selected.
   *
   * @param {HTMLElement|Elements} node A DOM node for a row.
   *
   * @return {boolean} True if it is selected, false otherwise.
   */
  exports.isNodeSelected = function (node) {
    const newNode = 'innerHTML' in node ? node : node[0];
    if (!newNode) return false;
  
    const position = positions.getFromNode(newNode);
    if (!position) return false;
  
    return model.isSelected(position);
  };
  
  /**
   * Reset the selection state and its knowledge of any containers.
   */
  exports.reset = function () {
    controller.resetListListeners();
    model.reset();
    rows.update();
    containers.reset();
  };
  
  /**
   * Clear the current selection.
   */
  exports.clear = function () {
    model.clear();
    rows.update();
  };
  
  /**
   * Set the origin to a new position.
   *
   * @param {number} containerIndex The index of the container.
   * @param {number} index The index within the container.
   */
  exports.setOrigin = function (containerIndex, index) {
    model.setOrigin(new Position(containerIndex, index));
  };
  
  /**
   * Set the focus to a new position.
   *
   * @param {number} containerIndex The index of the container.
   * @param {number} index The index within the container.
   */
  exports.setFocus = function (containerIndex, index) {
    model.setFocus(new Position(containerIndex, index));
  };
  
  /**
   * Get the current origin position.
   *
   * @return {Object?} An object with numeric properties `containerIndex`
   *     and `index`, or null if no origin is set.
   */
  exports.getOrigin = function () {
    const origin = model.getOrigin();
    if (!origin) return null;
    return {
      containerIndex: origin.containerIndex,
      index: origin.index
    };
  };
  
  /**
   * Get the current focus position.
   *
   * @return {Object?} An object with numeric properties `containerIndex`
   *     and `index`, or null if no origin is set.
   */
  exports.getFocus = function () {
    const focus = model.getFocus();
    if (!focus) return null;
    return {
      containerIndex: focus.containerIndex,
      index: focus.index
    };
  };
  
  /**
   * Get all selected indices organized by list.
   *
   * @return {Array.<Object>} Array of objects of this structure:
   *     {
   *       containerIndex: 0,
   *
   *       // This is the list in the UI (could be sorted or unsorted)
   *       uri: 'spotify:internal:sortlist:asc:track(name):list',
   *       keys: ['c', 'd', 'e', 'j', 'o', 't'],
   *       indices: [3, 4, 5, 10, 15, 20], // Ordered indices in the list
   *
   *       // If all items between two selected items are hidden, they will be
   *       // included here to allow nice ranges to be created from this. It will
   *       // also include hidden items right after the last selected item.
   *       keysWithHidden: ['c', 'd', 'e', 'j', 'k', 'l', 'm', 'n', 'o', 't'],
   *       indicesWithHidden: [3, 4, 5, 10, 11, 12, 13, 14, 15, 20],
   *
   *       // Optional object for the unsorted list if the list is a sorted list
   *       origin: {
   *         uri: 'spotify:list',
   *         keys: ['c', 'd', 'e', 'j', 'o', 't'],
   *         indices: [7, 19, 8, 14, 0, 9] // Unordered indices in the original list
   *       }
   *     }
   */
  exports.getIndicesPerList = function () {
    const selections = model.selections;
    const indicesPerList = [];
  
    let list;
  
    const listKeys = selectedIndex => {
      return list.keys[selectedIndex];
    };
  
    for (let i = 0; i < selections.length; i++) {
      const containerSelection = selections[i];
      if (containerSelection && Object.keys(containerSelection).length) {
        const uri = containers.getUri(i);
        if (!uri) continue;
  
        list = containers.getLiveList(i);
        if (!list) continue;
  
        const indices = model.getIndicesForContainer(i);
        if (!indices) continue;
  
        const keys = indices.map(listKeys);
  
        const indicesWithHidden = model.getIndicesWithHiddenForContainer(i);
        const keysWithHidden = indicesWithHidden.map(listKeys);
  
        const context = {
          containerIndex: i,
          uri: uri,
          indices: indices,
          keys: keys,
          indicesWithHidden: indicesWithHidden,
          keysWithHidden: keysWithHidden
        };
  
        const originUri = getOriginUri(uri);
        if (originUri) {
          const wrappedList = live(uri).get('rows');
          const originList = live(originUri).get('rows');
  
          let originIndices;
          let originKeys;
          if (wrappedList && originList) {
            let key;
            const originListKeyIndexMap = {};
            originIndices = new Array(indices.length);
            originKeys = new Array(indices.length);
  
            const originListKeys = originList.keys;
            for (let j = 0; j < originListKeys.length; j++) {
              originListKeyIndexMap[originListKeys[j]] = j;
            }
  
            for (let j = 0; j < indices.length; j++) {
              key = wrappedList.keys[indices[j]];
              originIndices[j] = originListKeyIndexMap[key];
              originKeys[j] = originList.keys[originIndices[j]];
            }
          }
          context.origin = {
            uri: originUri,
            indices: originIndices || [],
            keys: originKeys || []
          };
        }
  
        indicesPerList.push(context);
      }
    }
  
    return indicesPerList;
  };
  
  /**
   * Get all selected indices relative to the full page (across all containers).
   *
   * @return {Object?} The value null if no global context is found or if found,
   *     an object of this structure:
   *     {
   *       uri: 'spotify:context:uri',
   *       indices: [0, 3, 56, 120],
   *
   *       // These elements are Elements instances from the 'elements' npm
   *       // package. This should be fixed in a future major version, to be
   *       // normal HTML elements.
   *       containers: [
   *         containerElement1,
   *         containerElement1,
   *         containerElement4,
   *         containerElement5
   *       ]
   *     }
   */
  exports.getIndicesGlobal = function () {
    const selections = model.selections;
    const elements = containers.elements;
    const indices = [];
    const selectionContainers = [];
  
    if (elements.length === 0) {
      return null;
    }
    const contextUriContainer = elements[0].closest('[data-context]');
    const contextUri = contextUriContainer && contextUriContainer.getAttribute('data-uri');
  
    if (!contextUri) return null;
  
    let containerSelection;
    const containerSelectionByKey = key => {
      return !!containerSelection[key];
    };
  
    for (let container = 0; container < selections.length; container++) {
      containerSelection = selections[container];
      const list = containers.getLiveList(container);
      if (list && containerSelection) {
        const orderedSelectedKeys = list.keys.filter(containerSelectionByKey);
  
        for (let i = 0, l = orderedSelectedKeys.length; i < l; i++) {
          const j = list.keys.indexOf(orderedSelectedKeys[i]);
          indices.push(positions.getGlobalIndexFromPosition(new Position(container, j)));
          selectionContainers.push($(elements[container]));
        }
      }
    }
  
    return {
      uri: contextUri,
      indices: indices,
      containers: selectionContainers
    };
  };
  
  /**
   * Get the URI of all selected rows.
   *
   * @return {Array.<string>} Array of URIs.
   */
  exports.getUris = function () {
    return controller.handleGetUris();
  };
  
  /**
   * Get the nodes for all selected rows that can be found.
   *
   * @return {Array.<HTMLElement>} Array of DOM nodes.
   */
  exports.getNodes = function () {
    return rows.getSelectedRows();
  };
  
  },{"../../live":172,"../../live-wrapped-uri":171,"../../spotify-elements":210,"../center":221,"../util/Position":288,"../util/app":289,"../util/cosmos":290,"./containers":276,"./controller":277,"./model":279,"./positions":280,"./rows":281}],279:[function(require,module,exports){
  /**
   * @module spotify-events/selection/model
   * @private
   */
  'use strict';
  
  const live = require('../../live');
  const Range = require('../../range2');
  const containers = require('./containers');
  const positions = require('./positions');
  const Position = require('../util/Position');
  
  /**
   * Selections for all known containers.
   * Each container's selection space is represented as an object where keys are
   * row IDs. This allows for quick lookups.
   *
   * @type {Array.<Object.<string, boolean>>}
   */
  const selections = [];
  
  /**
   * Hidden items for all known containers.
   * Each container is represented as an object where keys are row IDs. This
   * allows for quick lookups.
   *
   * @type {Object.<number, Object.<string, boolean>>}
   */
  let hiddenKeysPerContainer = {};
  
  let originContainerIndex = null;
  let originKey = null;
  let focusContainerIndex = null;
  let focusKey = null;
  let focusIsTouch = false;
  
  /**
   * Get the current origin.
   *
   * @return {Position?} The origin position or null if not set.
   */
  function getOrigin() {
    if (originContainerIndex === null) return null;
    if (originKey === null) return null;
  
    const list = containers.getLiveList(originContainerIndex);
    if (list) {
      const index = list.keys.indexOf(originKey);
      if (index === -1) return null;
      const position = new Position(originContainerIndex, index);
      return position;
    }
    return null;
  }
  
  /**
   * Get the current focus. This is the position for the keyboard focus.
   *
   * @return {Position?} The focus position or null if not set.
   */
  function getFocus() {
    if (focusContainerIndex === null) return null;
    if (focusKey === null) return null;
  
    const list = containers.getLiveList(focusContainerIndex);
    if (list) {
      const index = list.keys.indexOf(focusKey);
      const position = new Position(focusContainerIndex, index);
      return position;
    }
    return null;
  }
  
  /**
   * Set the current origin.
   *
   * @param {Position?} position A position or null if removing the origin.
   */
  function setOrigin(position) {
    if (position) {
      const list = containers.getLiveList(position.containerIndex);
      if (list) {
        originContainerIndex = position.containerIndex;
        originKey = list.keys[position.index] || null;
        return;
      }
    }
  
    originContainerIndex = null;
    originKey = null;
  }
  
  /**
   * Set the current focus.
   *
   * @param {Position?} position A position or null if removing the focus.
   */
  function setFocus(position, options) {
    focusIsTouch = !!(options && options.isTouch);
  
    if (position) {
      const list = containers.getLiveList(position.containerIndex);
      if (list) {
        focusContainerIndex = position.containerIndex;
        focusKey = list.keys[position.index];
        return;
      }
    }
  
    focusContainerIndex = null;
    focusKey = null;
  }
  
  /**
   * Check if the current focus was triggered by a touch.
   * This is done since we might want to have different styles for selection focus
   * when triggered by a touch event.
   *
   * @return {boolean} True if triggered by touch.
   */
  function wasFocusTriggeredByTouch() {
    return focusIsTouch;
  }
  
  /**
   * Handle setting which indices are hidden.
   *
   * @param {Array.<number>} hiddenIndices Array of indices that are hidden.
   * @param {string} containerUri The URI of the container.
   */
  function setHiddenIndices(hiddenIndices, containerUri) {
    const list = live(containerUri).get('rows');
    if (list) {
      const keys = list.keys;
      const hiddenKeys = {};
      for (let i = 0, l = hiddenIndices.length; i < l; i++) {
        const key = keys[hiddenIndices[i]];
        if (key) hiddenKeys[key] = true;
      }
  
      const containerIndex = containers.getContainerIndex(containerUri);
  
      hiddenKeysPerContainer[containerIndex] = hiddenKeys;
  
      updateContainer(containerIndex);
    }
  }
  
  /**
   * Check if given position is hidden.
   *
   * @param {Position} from The position to check.
   * @return {boolean} True if position is hidden.
   */
  function isPositionHidden(pos) {
    const idsPerContainer = getIds(pos, pos);
    const id = idsPerContainer[0][0];
    const hiddenKeys = hiddenKeysPerContainer[pos.containerIndex];
    return hiddenKeys && hiddenKeys[id];
  }
  
  /**
   * Add rows to the selection.
   *
   * @param {Position} from The start position to add.
   * @param {Position=} optTo The end position to add. If not provided, it will
   *     only add a single row to the selection.
   */
  function add(from, optTo) {
    let to = optTo || from;
    let newFrom = from;
  
    // Flip the positions if the direction is up.
    if (positions.getDirection(newFrom, to) === -1) {
      const temp = newFrom;
      newFrom = to;
      to = temp;
    }
  
    const idsPerContainer = getIds(newFrom, to);
  
    for (let i = 0, l = idsPerContainer.length; i < l; i++) {
      const containerIndex = newFrom.containerIndex + i;
      const selected = selections[containerIndex] || (selections[containerIndex] = Object.create(null));
      const ids = idsPerContainer[i];
      const hiddenKeys = hiddenKeysPerContainer[containerIndex];
  
      // Set all new items as selected, unless they're hidden
      for (let j = 0; j < ids.length; j++) {
        const id = ids[j];
        if (!hiddenKeys || !hiddenKeys[id]) {
          selected[id] = true;
        }
      }
    }
  }
  
  /**
   * Remove rows from the selection.
   *
   * @param {Position} from The start position to remove.
   * @param {Position=} optTo The end position to remove. If not provided, it will
   *     only remove a single row from the selection.
   */
  function remove(from, optTo) {
    let to = optTo || from;
    let newFrom = from;
  
    // Flip the positions if the direction is up.
    if (positions.getDirection(newFrom, to) === -1) {
      const temp = newFrom;
      newFrom = to;
      to = temp;
    }
  
    const idsPerContainer = getIds(newFrom, to);
  
    for (let i = 0, l = idsPerContainer.length; i < l; i++) {
      const containerIndex = newFrom.containerIndex + i;
      const selected = selections[containerIndex] || (selections[containerIndex] = Object.create(null));
      const ids = idsPerContainer[i];
  
      // Loop the row ids that are to be removed and remove them from the selection.
      // That's usually faster than looping the selection since there's no UI for
      // removing a big chunk of selection in one go.
      for (let j = 0; j < ids.length; j++) {
        const id = ids[j];
        delete selected[id];
      }
    }
  }
  
  /**
   * Clear the current selection.
   */
  function clear() {
    selections.length = 0;
    setOrigin(null);
    setFocus(null);
  }
  
  /**
   * Reset the current selection state.
   */
  function reset() {
    clear();
    hiddenKeysPerContainer = {};
  }
  
  /**
   * Update the model state based on the current state of the list.
   * If items were removed from the list, removed items that were selected
   * will be removed from selection state.
   */
  function updateContainer(containerIndex) {
    const keys = selections[containerIndex];
    if (!keys) return;
  
    const list = containers.getLiveList(containerIndex);
    if (!list) return;
  
    // TODO: Fix for-in statement so it doesn't REQUIRE
    // prototype chain iteration
    // eslint-disable-next-line guard-for-in
    for (const key in keys) {
      const hasKey = list.hasKey(key);
      let shouldDelete = !hasKey;
      if (!shouldDelete) {
        const hiddenKeys = hiddenKeysPerContainer[containerIndex];
        shouldDelete = hiddenKeys ? hiddenKeys[key] : false;
      }
      if (shouldDelete) {
        delete keys[key];
      }
    }
  }
  
  /**
   * Get IDs for the provided global range.
   *
   * @param {Position} from The start position.
   * @param {Position} to The end position. This position must be below the
   *     `from` position.
   *
   * @return {Array.<Array.<string>>} Array of IDs per container.
   */
  function getIds(from, to) {
    const idsPerContainer = [];
  
    const fromContainer = from.containerIndex;
    const toContainer = to.containerIndex;
  
    for (let i = fromContainer; i <= toContainer; i++) {
      const fromIndex = i === fromContainer ? from.index : 0;
      let toIndex;
      if (i === toContainer) {
        toIndex = to.index;
      } else {
        const containerLength = containers.getContainerLength(i);
        if (containerLength > 0) {
          toIndex = containerLength - 1;
        }
      }
  
      if (toIndex === undefined) {
        idsPerContainer.push([]);
      } else {
        const ids = [];
        idsPerContainer.push(ids);
  
        const containerUri = containers.getUri(i);
        const list = live(containerUri).get('rows');
        if (list) {
          const keys = list.keys;
  
          for (let n = fromIndex; n < toIndex + 1; n++) {
            ids.push(keys[n]);
          }
        }
      }
    }
  
    return idsPerContainer;
  }
  
  /**
   * Check if the position is selected.
   *
   * @param {Position} position A position.
   *
   * @return {boolean} True if it is selected, false otherwise.
   */
  function isSelected(position) {
    // Get the ranges of the according container
    const keys = selections[position.containerIndex];
    if (!keys) return false;
  
    // Check if the row is within the selected ranges for the container
    const list = containers.getLiveList(position.containerIndex);
    if (list) {
      const key = list.keys[position.index];
      return !!keys[key];
    }
  
    return false;
  }
  
  /**
   * Check if the selection has any holes (contains many ranges).
   *
   * @return {boolean} True if it has holes, false otherwise.
   */
  function hasHoles() {
    let rangeCount = 0;
  
    for (let containerIndex = 0; containerIndex < selections.length; containerIndex++) {
      const containerSelection = selections[containerIndex];
      const list = containers.getLiveList(containerIndex);
  
      const keys = Object.keys(containerSelection);
  
      // A selection in more than one container means the selection has holes
      if (keys.length > 0) {
        rangeCount++;
  
        // Also more than one range in only one container means it has holes.
        if (list) {
          const indices = [];
          for (let j = 0, len = keys.length; j < len; j++) {
            indices.push(list.indexOf(keys[j]));
          }
          indices.sort(function (a, b) {
            return a - b;
          });
          let previousIndex;
          for (let i = 0, l = indices.length; i < l; i++) {
            const index = indices[i];
            if (i > 0 && index > previousIndex + 1) {
              rangeCount++;
              break;
            }
            previousIndex = index;
          }
        }
      }
      if (rangeCount > 1) return true;
    }
  
    return false;
  }
  
  /**
   * Check if there is any selection in any container.
   *
   * @return {boolean} True if there is a selection.
   */
  function hasSelection() {
    for (let i = 0, l = selections.length; i < l; i++) {
      if (selections[i] && Object.keys(selections[i]).length > 0) {
        return true;
      }
    }
  
    return false;
  }
  
  /**
   * Get the selected indices for a container.
   *
   * @param {number} containerIndex The container index.
   *
   * @return {Array.<number>?} An array of indices or null if nothing is selected.
   */
  function getIndicesForContainer(containerIndex) {
    const unorderedKeys = selections[containerIndex];
    if (!unorderedKeys) return null;
    let indices = [];
  
    const list = containers.getLiveList(containerIndex);
  
    const orderedKeys = list.keys.filter(function (key) {
      return !!unorderedKeys[key];
    });
  
    if (list && orderedKeys.length) {
      if (orderedKeys.length > 1) {
        // indexOfMany() is much more perfomant for big selections
        indices = list.indexOfMany(orderedKeys);
      } else {
        // for a single selected item, indexOf() is a little bit more perfomant
        indices = [list.indexOf(orderedKeys[0])];
      }
    }
  
    return indices.length ? indices : null;
  }
  
  /**
   * Get the selected indices for a container, including hidden items that join
   * two ranges. For example, in a list [A, B, C, D, E, F, G, H], everything is
   * selected except C, D, F and G. There are hidden items, C, D, F and G. This
   * method would then return the indices of all items except H,
   * [0, 1, 2, 3, 4, 5, 6]. It does include hidden items right after the last
   * selected one.
   *
   * @param {number} containerIndex The container index.
   *
   * @return {Array.<number>?} An array of indices or null if nothing is selected.
   */
  function getIndicesWithHiddenForContainer(containerIndex) {
    let indices = getIndicesForContainer(containerIndex);
    if (!indices) return null;
  
    const hiddenKeys = hiddenKeysPerContainer[containerIndex];
    if (!hiddenKeys) return indices;
  
    const ranges = Range.fromIndices(indices);
    if (ranges.length === 1) return indices;
  
    const list = containers.getLiveList(containerIndex);
    if (!list) return indices;
  
    const hiddenIds = Object.keys(hiddenKeys);
    let hiddenIndices = [];
  
    if (hiddenIds.length) {
      if (hiddenIds.length > 1) {
        // indexOfMany() is much more perfomant for big selections
        hiddenIndices = list.indexOfMany(hiddenIds);
      } else {
        // for a single selected item, indexOf() is a little bit more perfomant
        hiddenIndices = [list.indexOf(hiddenIds[0])];
      }
    }
  
    hiddenIndices = hiddenIndices.filter(function (index) {
      return index > -1;
    });
  
    const hiddenRanges = Range.fromIndices(hiddenIndices);
    const hiddenStarts = hiddenRanges.map(function (range) {
      return range.start;
    });
    const hiddenEnds = hiddenRanges.map(function (range) {
      return range.end;
    });
  
    for (let i = 0, l = ranges.length; i < l; i++) {
      const range = ranges[i];
      const nextRange = ranges[i + 1];
  
      const indexOfRange = hiddenStarts.indexOf(range.end);
      if (indexOfRange > -1) {
        const hiddenEnd = hiddenEnds[indexOfRange];
        if (!nextRange || hiddenEnd === nextRange.start) {
          const hiddenIndicesInRange = hiddenRanges[indexOfRange].toIndices();
          if (hiddenIndicesInRange.length > 0) {
            indices = indices.concat(hiddenIndicesInRange);
          }
        }
      }
    }
  
    indices.sort(function (a, b) {
      return a - b;
    });
  
    return indices.length ? indices : null;
  }
  
  exports.selections = selections;
  exports.getOrigin = getOrigin;
  exports.getFocus = getFocus;
  exports.setOrigin = setOrigin;
  exports.setFocus = setFocus;
  exports.wasFocusTriggeredByTouch = wasFocusTriggeredByTouch;
  exports.setHiddenIndices = setHiddenIndices;
  exports.isPositionHidden = isPositionHidden;
  exports.add = add;
  exports.remove = remove;
  exports.clear = clear;
  exports.reset = reset;
  exports.updateContainer = updateContainer;
  exports.isSelected = isSelected;
  exports.hasHoles = hasHoles;
  exports.hasSelection = hasSelection;
  exports.getIndicesForContainer = getIndicesForContainer;
  exports.getIndicesWithHiddenForContainer = getIndicesWithHiddenForContainer;
  
  },{"../../live":172,"../../range2":204,"../util/Position":288,"./containers":276,"./positions":280}],280:[function(require,module,exports){
  /**
   * @module spotify-events/selection/positions
   * @private
   */
  'use strict';
  
  const $ = require('../../spotify-elements');
  
  const containers = require('./containers');
  const Position = require('../util/Position');
  
  /**
   * Get the closest position in the specified direction.
   *
   * @param {Position} position A position.
   * @param {string} direction The direction to search in: 'up' or 'down'.
   *
   * @return {Position?} A position or null if not found.
   */
  function getClosest(position, direction) {
    const isDown = direction === 'down';
    let containerIndex = position.containerIndex;
    const index = position.index;
  
    // Find next position within the current container
    let containerLength = containers.getContainerLength(containerIndex);
    let nextIndex = isDown ? index + 1 : index - 1;
    if (nextIndex < containerLength && nextIndex >= 0) {
      return new Position(containerIndex, nextIndex);
    }
  
    // Out of bounds, traverse containers
    containerIndex = isDown ? containerIndex + 1 : containerIndex - 1;
    containerLength = containers.getContainerLength(containerIndex);
    if (containerLength) {
      nextIndex = isDown ? 0 : containerLength - 1;
      return new Position(containerIndex, nextIndex);
    }
  
    return null;
  }
  
  /**
   * Get a position from a row node.
   *
   * @param {HTMLElement} node A DOM node for a list row.
   *
   * @return {Position?} A position or null if not found.
   */
  function getFromNode(node) {
    // We need a parent node, since we will be checking if the row is inside
    // one of the known selection containers.
    if (!node.parentNode) return null;
  
    // We also need a data-index attribute on the row to get the index for the
    // row within the current selection container.
    if (!node.hasAttribute('data-index')) return null;
  
    const elements = containers.elements;
    if (elements.length === 0) return null;
  
    for (let i = 0, l = elements.length; i < l; i++) {
      if (elements[i] && containsNode(elements[i], node)) {
        const row = +node.getAttribute('data-index');
        return new Position(i, row);
      }
    }
  
    return null;
  }
  
  /**
   * Check if an element contains another node.
   *
   * @param {HTMLElement} element The container element.
   * @param {HTMLElement} child The potential child node.
   *
   * @return {boolean} True if element contains the child, false otherwise.
   */
  function containsNode(element, child) {
    let newChild = child;
    while (newChild && newChild.parentNode !== element) {
      newChild = newChild.parentNode;
    }
    return !!newChild;
  }
  
  /**
   * Get the row node that the specified position represents.
   *
   * @param {Position} position A position.
   *
   * @return {HTMLElement?} A DOM node or null if not found.
   */
  function getNodeFromPosition(position) {
    const container = containers.elements[position.containerIndex];
    if (!container) return null;
    const node = $(container).find(`[data-index=${position.index}]`);
    return node ? node[0] : null;
  }
  
  /**
   * Get the direction from one position to another.
   *
   * @param {Position} from First position.
   * @param {Position} to Second position.
   *
   * @return {number} A number representing the direction.
   *     -1 is up
   *      0 is same
   *      1 is down
   */
  function getDirection(from, to) {
    if (!from || !to) return 0;
  
    // With different containers we can just compare the containers
    if (from.containerIndex !== to.containerIndex) {
      return from.containerIndex < to.containerIndex ? 1 : -1;
    }
  
    // Within the same container, compare the row indices
    if (from.index === to.index) {
      return 0;
    }
    return from.index < to.index ? 1 : -1;
  }
  
  /**
   * Get data about in which container and where inside that the provided
   * global index is.
   *
   * @param {number} globalIndex An index relative to the full page (all
   *     selection containers).
   *
   * @return {Position} A position.
   */
  function getPositionFromGlobalIndex(globalIndex) {
    let resolvedIndex = globalIndex;
    let resolvedContainerIndex = 0;
    let containerLength;
    let countIndices = 0;
  
    // Subtract the amount of tracks in containers until passing globalIndex
    for (let i = 0, l = containers.elements.length; i < l; i++) {
      containerLength = containers.getContainerLength(i);
      countIndices += containerLength;
  
      if (countIndices > globalIndex) break;
  
      resolvedContainerIndex = i + 1;
      resolvedIndex -= containerLength;
    }
  
    return new Position(resolvedContainerIndex, resolvedIndex);
  }
  
  /**
   * Get the global index relative to the full page (across all selection
   * containers) from a position object.
   *
   * @param {Position} position A position.
   *
   * @return {number} A global index.
   */
  function getGlobalIndexFromPosition(position) {
    let resolvedIndex = position.index;
  
    // Subtract length of previous lists
    for (let i = position.containerIndex - 1; i >= 0; i--) {
      resolvedIndex += containers.getContainerLength(i);
    }
  
    return resolvedIndex;
  }
  
  /**
   * Get last position on page.
   *
   * @return {Position} A position.
   */
  function getLastPosition() {
    const lastContainerIndex = containers.elements.length - 1;
    const lastIndex = containers.getContainerLength(lastContainerIndex) - 1;
  
    return new Position(lastContainerIndex, lastIndex);
  }
  
  /**
   * Get last position for "select all" on page, disregarding containers with
   * [data-select-all="false"].
   *
   * @return {Position} A position.
   */
  function getLastPositionForSelectAll() {
    let lastContainerIndex;
    containers.elements.forEach(function (element, index) {
      if (element.getAttribute('data-list-may-select-all') !== 'false') {
        lastContainerIndex = index;
      }
    });
  
    const lastIndex = containers.getContainerLength(lastContainerIndex) - 1;
  
    return new Position(lastContainerIndex, lastIndex);
  }
  
  exports.getClosest = getClosest;
  exports.getFromNode = getFromNode;
  exports.getNodeFromPosition = getNodeFromPosition;
  exports.getDirection = getDirection;
  exports.getPositionFromGlobalIndex = getPositionFromGlobalIndex;
  exports.getGlobalIndexFromPosition = getGlobalIndexFromPosition;
  exports.getLastPosition = getLastPosition;
  exports.getLastPositionForSelectAll = getLastPositionForSelectAll;
  
  },{"../../spotify-elements":210,"../util/Position":288,"./containers":276}],281:[function(require,module,exports){
  /**
   * @module spotify-events/selection/rows
   * @private
   */
  'use strict';
  
  const $ = require('../../spotify-elements');
  const difference = require('mout/array/difference');
  
  const model = require('./model');
  const containers = require('./containers');
  const positions = require('./positions');
  
  const SELECTOR_ROW = '[data-list-item]';
  const CLASSNAME_SELECTED = 'selected';
  const CLASSNAME_FOCUSED = 'selection-focus';
  const CLASSNAME_FOCUSED_TOUCH = 'selection-focus-touch';
  let focusTimeout;
  
  /**
   * Update the selection state of rows.
   *
   * @param {HTMLElement=} node Optional DOM node. If provided, the update
   *     will only affect rows found inside this node.
   */
  function update(node) {
    const currentlySelectedRows = getSelectedRows(node);
    const rowsToBeSelected = getRowsToSelect(node);
  
    const rowsToDeselect = difference(currentlySelectedRows, rowsToBeSelected);
    const rowsToSelect = difference(rowsToBeSelected, currentlySelectedRows);
  
    currentlySelectedRows.forEach(function (row) {
      $(row).removeClass(CLASSNAME_FOCUSED);
      $(row).removeClass(CLASSNAME_FOCUSED_TOUCH);
    });
  
    // Remove styles for selected rows that should now be deselected
    rowsToDeselect.forEach(function (row) {
      $(row).removeClass(CLASSNAME_SELECTED);
    });
  
    // Add styles for unselected rows that should now be selected
    rowsToSelect.forEach(function (row) {
      $(row).addClass(CLASSNAME_SELECTED);
    });
  
    // Add styles for the row with the selection focus
    const focus = model.getFocus();
    if (focus) {
      const focusNode = positions.getNodeFromPosition(focus);
      if (focusNode) {
        // If the focusNode is not yet in the viewport and we `focus()` it, the
        // browser will bluntly scroll it into view, which looks bad. (KM-6508)
        clearTimeout(focusTimeout);
        focusTimeout = setTimeout(function () {
          const isInputFocused = document.activeElement && $(document.activeElement).matches('input, textarea');
          const isButtonFocused = document.activeElement && $(document.activeElement).matches('button');
          if (isElementInViewport(focusNode) && !isInputFocused && !isButtonFocused) {
            focusNode.focus();
          }
        }, 0);
        $(focusNode).addClass(CLASSNAME_FOCUSED);
  
        if (model.wasFocusTriggeredByTouch()) {
          $(focusNode).addClass(CLASSNAME_FOCUSED_TOUCH);
        }
      }
    }
  }
  
  /**
   * Get the rows that are currently selected, found inside the known containers.
   *
   * @param {HTMLElement=} node Optional DOM node. If provided, it will only
   *     search inside this node.
   *
   * @return {Array.<HTMLElement>} Array of DOM nodes.
   */
  function getSelectedRows(node) {
    const elements = node ? [node] : containers.elements;
    const rows = [];
  
    elements.forEach(function (container) {
      const nodes = $(container).search(`${SELECTOR_ROW}.${CLASSNAME_SELECTED}`);
      if (nodes) Array.prototype.push.apply(rows, nodes);
    });
  
    return rows;
  }
  
  /**
   * Get the rows that should be selected, found inside the known containers.
   *
   * @param {HTMLElement=} node Optional DOM node. If provided, it will only
   *     search inside this node.
   *
   * @return {Array.<HTMLElement>} Array of DOM nodes.
   */
  function getRowsToSelect(node) {
    const rows = [];
    const containerElements = containers.elements;
  
    model.selections.forEach(function (keys, index) {
      if (!keys) return;
  
      const list = containers.getLiveList(index);
      if (!list) return;
  
      let container = containerElements[index];
      // If a node was passed, only check the container if it contains the node
      if (node && !containerElements[index].contains(node)) {
        container = null;
      }
      if (!container) {
        return;
      }
  
      const listNodes = $(container).search(SELECTOR_ROW);
      if (!listNodes) return;
  
      const nodes = listNodes.filter(function (listNode) {
        const nodeIndex = +listNode.getAttribute('data-index');
        const nodeRowId = list.keys[nodeIndex];
        return !!keys[nodeRowId];
      });
  
      Array.prototype.push.apply(rows, nodes);
    });
    return rows;
  }
  
  function isElementInViewport(el) {
    const rect = el.getBoundingClientRect();
    return rect.top >= 0 && rect.bottom <= document.documentElement.clientHeight;
  }
  
  exports.update = update;
  exports.getSelectedRows = getSelectedRows;
  
  },{"../../spotify-elements":210,"./containers":276,"./model":279,"./positions":280,"mout/array/difference":435}],283:[function(require,module,exports){
  'use strict';
  
  const live = require('../live');
  const center = require('./center');
  const sortUriUtils = require('../live-sort-uri');
  const liburi = require('spotify-liburi');
  const $ = require('../spotify-elements');
  
  const ASC = 'tl-sort-asc';
  const DESC = 'tl-sort-desc';
  
  const setClassNames = function (list, optQuery, optDirection) {
    const uri = list.data('uri');
    const query = optQuery !== undefined ? optQuery : sortUriUtils.getQuery(uri);
    const direction = optDirection !== undefined ? optDirection : sortUriUtils.getDirection(uri);
    const headers = list.search('[data-sort]');
    if (!headers) {
      return;
    }
  
    for (let i = 0; i < headers.length; i++) {
      const header = $(headers[i]);
      header.removeClass(`${ASC} ${DESC}`);
      if (header.data('sort') === query) {
        header.addClass(direction === 'asc' ? ASC : DESC);
      }
    }
  };
  
  const handleSort = function (event, node) {
    const listNode = node.parent('[data-list]');
    if (!listNode) return;
  
    const uri = listNode.data('uri');
    if (!uri) return;
  
    const queryCurrent = sortUriUtils.getQuery(uri);
    const directionCurrent = sortUriUtils.getDirection(uri);
  
    let query = node.data('sort');
  
    const reverse = directionCurrent === 'asc' ? 'desc' : 'asc';
    let direction = queryCurrent === query ? reverse : 'asc';
    const reset = queryCurrent === query && directionCurrent === 'desc';
  
    const origin = sortUriUtils.getOriginUri(uri) || uri;
  
    let nextUri;
    if (reset) {
      nextUri = origin;
      query = null;
      direction = null;
    } else {
      nextUri = sortUriUtils.create(origin, direction, query);
    }
  
    // The first time a live model for the sorted URI is created, the sortlist
    // model will set up the sorting queries based on the URI.
    const nextList = live(nextUri);
  
    // We then need to trigger the sort to happen by asking for rows
    // The app itself will most likely request rows anyway, but by doing it
    // early might make it slightly faster.
    nextList.get('rows', function () {});
  
    const player = live('spotify:player');
    player.get('variant', 'context', function (error, variant, context) {
      if (error) throw error;
      if (variant && variant.get('uri') === uri) {
        if (liburi.from(context.get('uri')).type !== liburi.Type.SHOW) {
          player.emit('update-context', {
            context: nextUri
          });
        }
      }
    });
  
    setClassNames(listNode, query, direction);
  
    center.emit('sort-header-click', {
      uri: origin,
      requested: {
        query: query || null,
        direction: direction || null
      },
      current: {
        query: queryCurrent || null,
        direction: directionCurrent || null
      }
    });
  
    center.emit('list-swap', { before: uri, after: nextUri });
  };
  
  const handlePreviousProxyClick = function (event, node) {
    handleSort(null, node.previousSibling());
  };
  
  let isAttached = false;
  
  exports.update = function (node) {
    if (!isAttached) return;
  
    const newNode = $(node) || $(document);
  
    const lists = newNode.matches('[data-list]') ? [newNode] : newNode.search('[data-list]');
    if (!lists) return;
  
    for (let i = 0; i < lists.length; i++) {
      const list = $(lists[i]);
      const headers = list.search('[data-sort]');
      if (!headers) {
        continue;
      }
      setClassNames(list);
    }
  };
  
  exports.attach = function attach() {
    if (isAttached) return;
    isAttached = true;
  
    $(document).delegate('click', '[data-sort]', handleSort);
    $(document).delegate('click', '[data-sort-previous]', handlePreviousProxyClick);
  };
  
  exports.detach = function detach() {
    if (!isAttached) return;
    isAttached = false;
  
    $(document).undelegate('click', '[data-sort]', handleSort);
    $(document).undelegate('click', '[data-sort-previous]', handlePreviousProxyClick);
  };
  
  },{"../live":172,"../live-sort-uri":170,"../spotify-elements":210,"./center":221,"spotify-liburi":553}],284:[function(require,module,exports){
  (function (global){
  'use strict';
  
  var _frameUpdater = require('../frame-updater');
  
  const center = require('./center');
  const sort = require('./sort');
  
  const DEFAULT_STICKY_CLASS_NAME = 'sticky-top';
  
  let isAttached = false;
  let scrollListenerId = 0;
  let nodesGBCR = null;
  let rootGBCR = null;
  let timestamp;
  let lastTotalStickyHeight = 0;
  let isDragScrolling = false;
  let dragScrollingSpeed = 0;
  
  let scrollTopOffset = 0;
  let currentScrollX = 0;
  let currentScrollY = 0;
  let rects = null;
  let scrollNode = null;
  
  const getStickyClassName = function (stickyNode) {
    const root = document.documentElement;
    let classSticky;
    let node;
  
    for (node = stickyNode; node !== root; node = node.parentNode) {
      classSticky = node.getAttribute('data-class-sticky');
  
      if (classSticky) {
        return classSticky;
      }
    }
  
    return DEFAULT_STICKY_CLASS_NAME;
  };
  
  const getRects = function (ttl) {
    const reference = timestamp + ttl;
    const nodes = document.querySelectorAll('[data-sticky]');
  
    // Refresh timestamp (always).
    timestamp = Date.now();
  
    // If the reference time is still valid, and nodes are the same, then return data.
    if (reference > timestamp && nodesGBCR && nodes.length === nodesGBCR.length) {
      let equal = true;
  
      for (let i = 0; i < nodes.length; i++) {
        equal = equal && nodes[i] === nodesGBCR[i].node;
      }
  
      if (equal) {
        return {
          nodes: nodesGBCR,
          root: rootGBCR
        };
      }
    }
  
    // Otherwise, compute and cache.
    if (scrollNode === document) {
      rootGBCR = document.documentElement.getBoundingClientRect();
    } else {
      rootGBCR = scrollNode.getBoundingClientRect();
      rootGBCR = {
        top: rootGBCR.top - currentScrollY,
        right: rootGBCR.right,
        bottom: rootGBCR.bottom,
        left: rootGBCR.left,
        width: rootGBCR.width,
        height: rootGBCR.height
      };
    }
    nodesGBCR = Array.prototype.map.call(document.querySelectorAll('[data-sticky]'), function (node) {
      const parentRect = node.parentNode.getBoundingClientRect();
      const nodeRect = node.getBoundingClientRect();
  
      // Return a structure per node. Only some fields are stored in the rect object (the ones used).
      return {
        stickyClassName: getStickyClassName(node),
        node: node,
        rect: {
          top: parentRect.top,
          height: nodeRect.height,
          left: parentRect.left,
          right: parentRect.right
        }
      };
    });
  
    return {
      nodes: nodesGBCR,
      root: rootGBCR
    };
  };
  
  const removeClone = function (node) {
    const stickyClone = node.stickyClone;
  
    if (stickyClone.parentNode) {
      stickyClone.parentNode.removeChild(stickyClone);
    }
  
    if (node.stickyListSwapListener) {
      center.off('list-swap', node.stickyListSwapListener);
    }
  
    node.style.removeProperty('visibility');
  
    delete node.stickyClone;
    delete node.stickyListSwapListener;
  };
  
  const resetSticky = function (node) {
    // <THEAD>s can't be made sticky, so verify if we are using a clone and remove it from the DOM.
    if (node.nodeName.toLowerCase() === 'thead' && node.stickyClone) {
      removeClone(node);
    }
  
    node.classList.remove(getStickyClassName(node));
  
    const headerNode = node.closest('[data-glue-page-header]');
    if (headerNode) {
      headerNode.classList.remove('glue-page-header--with-active-sticky-header');
      if (global.onPageHeaderToggleStickyMode) {
        global.onPageHeaderToggleStickyMode(false);
      }
    }
  
    // Update styles accordingly.
    node.removeAttribute('data-sticky-active', '');
    node.style.right = '';
    node.style.left = '';
    node.style.position = '';
    node.style.top = '';
  };
  
  const resetOldStickies = function () {
    const selector = '[data-sticky-active]:not([data-sticky])';
    const nodes = document.querySelectorAll(selector);
    for (let i = 0, l = nodes.length; i < l; i++) {
      if (!nodes[i].isStickyClone) {
        resetSticky(nodes[i]);
      }
    }
  };
  
  const readValuesWithTTL = function (ttl) {
    const node = scrollNode === document ? document.documentElement : scrollNode;
    const topBarHeight = node.getAttribute('data-top-bar-height');
    scrollTopOffset = parseInt(topBarHeight, 10) || 0;
  
    currentScrollY = scrollNode === document ? window.scrollY : scrollNode.scrollTop;
  
    rects = getRects(ttl);
  };
  
  const createClone = function (node) {
    const div = document.createElement('div');
    const thead = node.cloneNode(true);
    const table = node.parentNode.cloneNode(false);
  
    table.appendChild(thead).removeAttribute('data-sticky');
    div.appendChild(table);
    div.className = 'sticky-clone';
    div.isStickyClone = true;
  
    const onListSwap = function (data) {
      const theadParentTable = thead.parentNode;
      const uri = theadParentTable && theadParentTable.getAttribute('data-uri');
  
      // Update the "data-uri" of the cloned table, and the sort state of the original one.
      if (uri) {
        theadParentTable.setAttribute('data-uri', data.after);
        sort.update(node.parentNode);
      }
    };
  
    center.on('list-swap', onListSwap);
  
    node.parentNode.parentNode.appendChild(div);
    node.style.visibility = 'hidden';
    node.stickyListSwapListener = onListSwap;
    node.stickyClone = div;
  
    return div;
  };
  
  const updateStickies = function () {
    const scrollTop = currentScrollY + scrollTopOffset;
    const width = rects.root.width;
    let top = rects.root.top;
    let height = scrollTopOffset;
    const stickyNodes = rects.nodes;
  
    resetOldStickies();
  
    // This is to reset the video header for P2S if no stickies are found in the
    // DOM. Not finding any stickies means if we previously had a sticky P2S
    // video, it should not be sticky anymore. If a sticky header node re-appears
    // in the DOM, the next update of sticky events will call out to the video
    // with the new value.
    if (stickyNodes.length === 0) {
      if (global.onPageHeaderToggleStickyMode) {
        global.onPageHeaderToggleStickyMode(false);
      }
    }
  
    stickyNodes.forEach(function (nodeInfo, i) {
      const node = nodeInfo.node;
      const rect = nodeInfo.rect;
      const stickyClone = node.stickyClone;
      const stickyClassName = nodeInfo.stickyClassName;
      let stickyNode = stickyClone || node;
  
      // Check if the node should be sticked or not (if true, it should be).
      if (rect.top - top < scrollTop) {
        const previousNodeInfo = stickyNodes[i - 1];
        if (previousNodeInfo) {
          const previousNode = previousNodeInfo.node;
          const previousRect = previousNodeInfo.rect;
          if (previousNode.hasAttribute('data-sticky-single') && previousNode.classList.contains(stickyClassName)) {
            // This is the position of the current node relative to the bottom
            // edge of the stickies.
            const currentTopToEdge = Math.abs(rect.top - rects.root.top - (scrollTop + height));
  
            if (currentTopToEdge < previousRect.height) {
              const previousTop = height - previousRect.height;
              const newPreviousTop = previousTop - currentTopToEdge;
              previousNode.style.top = `${newPreviousTop}px`;
              top -= currentTopToEdge;
              height -= currentTopToEdge;
            } else {
              // Unstick the sticky above if it's pushed past its original spot
              resetSticky(previousNode);
              top -= previousRect.height;
              height -= previousRect.height;
            }
          }
        }
  
        // Offset the position by the offset of the root layer (an issue when a
        // custom scroll node is used).
        const left = rect.left - rects.root.left;
        const right = rect.right - rects.root.left;
  
        if (!stickyNode.classList.contains(stickyClassName)) {
          // <THEAD>s can't be made sticky, so we clone it and add it to the DOM.
          if (node.nodeName.toLowerCase() === 'thead' && !node.stickyClone) {
            stickyNode = createClone(node);
          }
  
          stickyNode.classList.add(stickyClassName);
  
          const headerNode = stickyNode.closest('[data-glue-page-header]');
          if (headerNode) {
            headerNode.classList.add('glue-page-header--with-active-sticky-header');
            if (global.onPageHeaderToggleStickyMode) {
              global.onPageHeaderToggleStickyMode(true);
            }
          }
  
          // Update styles accordingly.
          stickyNode.setAttribute('data-sticky-active', '');
          stickyNode.setAttribute('data-ta-id', 'sticky-node');
          stickyNode.style.right = `${width - right}px`;
          stickyNode.style.left = `${left}px`;
          stickyNode.style.position = 'fixed';
          stickyNode.style.top = `${height}px`;
        } else {
          stickyNode.style.right = `${width - right}px`;
          stickyNode.style.left = `${left}px`;
          stickyNode.style.top = `${height}px`;
        }
  
        // Increment the top reference and the height. Do it even if the element is already sticked.
        height += rect.height;
        top += rect.height;
      } else {
        if (stickyNode.classList.contains(stickyClassName)) {
          resetSticky(node);
        }
      }
    });
  
    lastTotalStickyHeight = height;
  };
  
  const handleResize = function () {
    readValuesWithTTL(0);
    updateStickies();
  };
  
  const dragScrollingTick = function () {
    if (isDragScrolling && currentScrollY > 0) {
      (0, _frameUpdater.requestFrame)(() => {
        currentScrollX = window.scrollX;
        currentScrollY = window.scrollY;
      }, () => {
        dragScrollingTick();
      });
  
      window.scrollTo(currentScrollX, currentScrollY - dragScrollingSpeed);
    }
  };
  
  const onPointerDragMove = function (event) {
    const distanceFromContentEdge = event.clientY - lastTotalStickyHeight;
    const scrollEdgeThickness = 20;
    const isBelowContentEdge = distanceFromContentEdge > 0;
    const isAboveScrollEdge = distanceFromContentEdge < scrollEdgeThickness;
  
    if (isBelowContentEdge && isAboveScrollEdge) {
      const speedFactor = (scrollEdgeThickness - Math.max(0, distanceFromContentEdge)) / scrollEdgeThickness;
      dragScrollingSpeed = speedFactor * 20;
  
      if (!isDragScrolling) {
        isDragScrolling = true;
        dragScrollingTick();
      }
    } else {
      isDragScrolling = false;
    }
  };
  
  const onPointerDragEnd = function () {
    isDragScrolling = false;
  
    document.removeEventListener('dragover', onPointerDragMove);
    document.removeEventListener('dragend', onPointerDragEnd);
    document.removeEventListener('mouseup', onPointerDragEnd);
  };
  
  const handleMouseDown = function () {
    document.addEventListener('dragover', onPointerDragMove);
    document.addEventListener('dragend', onPointerDragEnd);
  
    // Ideally we shouldn't need `mouseup`, but for some reason `dragend` is not
    // being triggered while scrolling. We can also not solely use mouseup, since
    // that is only triggered when releasing the pointer inside the iframe.
    document.addEventListener('mouseup', onPointerDragEnd);
  };
  
  exports.DEFAULT_STICKY_CLASS_NAME = DEFAULT_STICKY_CLASS_NAME;
  
  exports.attach = function attach() {
    if (!isAttached) {
      isAttached = true;
  
      if (!scrollNode) {
        scrollNode = document;
      }
  
      scrollListenerId = (0, _frameUpdater.addDebouncedScrollListener)(scrollNode, () => {
        readValuesWithTTL(4000);
      }, () => {
        updateStickies();
      });
  
      window.addEventListener('resize', handleResize);
      document.addEventListener('mousedown', handleMouseDown);
      if (global.top && global.top.document) {
        // Register mousedown events from top document to catch cases when
        // drag was initiated from top document (ex. dragging track from now playing
        // to playlist)
        global.top.document.addEventListener('mousedown', handleMouseDown);
      }
    }
  };
  
  exports.detach = function detach() {
    if (isAttached) {
      isAttached = false;
  
      (0, _frameUpdater.removeDebouncedScrollListener)(scrollListenerId);
      window.removeEventListener('resize', handleResize);
      document.removeEventListener('mousedown', handleMouseDown);
      if (global.top && global.top.document) {
        global.top.document.removeEventListener('mousedown', handleMouseDown);
      }
  
      nodesGBCR = null;
      rootGBCR = null;
      lastTotalStickyHeight = 0;
      isDragScrolling = false;
      dragScrollingSpeed = 0;
      scrollNode = null;
    }
  };
  
  exports.update = function () {
    if (isAttached) {
      nodesGBCR = null;
      rootGBCR = null;
  
      readValuesWithTTL(4000);
      updateStickies();
    }
  };
  
  // A way to let an app override the node to listen for scroll events on.
  // This has to be called before `attach` is called.
  exports.setScrollNode = function setScrollNode(node) {
    scrollNode = node;
  };
  
  exports._getStickyClassName = getStickyClassName;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../frame-updater":59,"./center":221,"./sort":283}],286:[function(require,module,exports){
  'use strict';
  
  const $ = require('../spotify-elements');
  
  const tooltipMargin = 8; // We want some spacing around the tooltip
  
  const tooltipNode = document.createElement('div');
  tooltipNode.id = 'tooltip';
  
  const tooltipTextNode = document.createElement('span');
  
  const tooltipArrowTop = document.createElement('div');
  tooltipArrowTop.className = 'tooltip-arrow-top';
  
  const tooltipArrowBottom = document.createElement('div');
  tooltipArrowBottom.className = 'tooltip-arrow-bottom';
  
  tooltipNode.appendChild(tooltipArrowTop);
  tooltipNode.appendChild(tooltipArrowBottom);
  tooltipNode.appendChild(tooltipTextNode);
  
  let attachedNode = null;
  let tooltipContainer = null;
  
  // tooltipArrow will point to the current visible tooltipArrow
  // that can be either tooltipArrowTop or tooltipArrowBottom
  let tooltipArrow = null;
  
  const addTooltipToDOM = function (container) {
    tooltipContainer = container;
    tooltipContainer.appendChild(tooltipNode);
  };
  
  const removeTooltipFromDOM = function () {
    attachedNode = null;
    if (tooltipNode.parentNode) {
      tooltipContainer.removeChild(tooltipNode);
    }
  
    tooltipContainer = null;
  };
  
  const getTooltipContainer = function (node) {
    const containerSelector = node.getAttribute('data-tooltip-container');
    let containerNode;
  
    if (containerSelector) {
      containerNode = document.querySelector(containerSelector);
    }
  
    return containerNode || document.body;
  };
  
  const setText = function (text) {
    tooltipTextNode.innerHTML = text;
  };
  
  const showTooltip = function (event, node) {
    const realNode = node[0];
    let top = 0;
    let left = 0;
  
    const tooltipText = node.data('tooltip') || node.getAttribute('title');
    if (!tooltipText) return;
  
    // Remember the node we set the text from so that text can be updated.
    attachedNode = node;
  
    if (!node.tooltipCheck) {
      node.removeAttribute('title');
      node.setAttribute('data-tooltip', tooltipText);
      node.tooltipCheck = true;
    }
  
    const container = getTooltipContainer(node);
    if (!tooltipContainer || container !== tooltipContainer) {
      addTooltipToDOM(container);
    }
  
    setText(tooltipText);
  
    const tooltipHeight = tooltipNode.clientHeight;
    const tooltipWidth = tooltipNode.offsetWidth;
  
    // Don't use window.scrollY because cross browser issues.
    // https://developer.mozilla.org/en-US/docs/Web/API/Window.scrollY
    const scrollY = window.pageYOffset;
  
    const bounds = realNode.getBoundingClientRect();
    const targetHeight = realNode.offsetHeight;
    const targetWidth = realNode.offsetWidth;
    const targetTop = parseInt(bounds.top, 10) + scrollY;
    const targetLeft = parseInt(bounds.left, 10);
    const targetCenter = targetLeft + targetWidth / 2;
    const targetBottom = targetTop + targetHeight;
    const viewportWidth = document.documentElement.clientWidth;
    const viewportHeight = document.documentElement.clientHeight;
  
    // Decide if the tooltip should be over or under the node
    if (targetBottom + tooltipHeight + tooltipMargin - scrollY > viewportHeight) {
      // On top
      top = targetTop - tooltipHeight - tooltipMargin;
      tooltipArrowTop.classList.remove('visible');
      tooltipArrowBottom.classList.add('visible');
      tooltipArrow = tooltipArrowBottom;
    } else {
      top = targetTop + targetHeight + tooltipMargin;
      if (top - scrollY > viewportHeight) {
        top = viewportHeight - tooltipHeight;
      }
      tooltipArrowTop.classList.add('visible');
      tooltipArrowBottom.classList.remove('visible');
      tooltipArrow = tooltipArrowTop;
    }
  
    const distanceToTheRight = viewportWidth - tooltipMargin - targetCenter;
    const distanceToTheLeft = targetCenter - tooltipMargin;
    const tooltipOffsetRight = Math.max(0, tooltipWidth / 2 - distanceToTheRight);
    const tooltipOffsetLeft = Math.max(0, tooltipWidth / 2 - distanceToTheLeft);
  
    left = targetCenter - tooltipWidth / 2 - (tooltipOffsetRight || -tooltipOffsetLeft);
  
    tooltipNode.style.top = `${top}px`;
    tooltipNode.style.left = `${left}px`;
    tooltipArrow.style.left = `${targetCenter - left}px`;
  
    if (node.hasAttribute('data-tooltip-instant')) {
      tooltipNode.classList.add('instant');
    } else {
      tooltipNode.classList.remove('instant');
    }
  
    tooltipNode.classList.add('visible');
  };
  
  const hideTooltip = function () {
    if (!attachedNode) {
      return;
    }
    attachedNode = null;
    setText('');
    tooltipNode.classList.remove('visible');
    tooltipNode.style.left = 0;
    tooltipNode.style.top = 0;
    if (tooltipArrow) {
      tooltipArrow.style.left = 0;
    }
  };
  
  let isAttached = false;
  
  exports.attach = function attach() {
    if (isAttached) return;
    isAttached = true;
  
    const doc = $(document);
    doc.delegate('mouseover', '[data-tooltip]', showTooltip);
    doc.delegate('mouseout', '[data-tooltip]', hideTooltip);
    doc.on('keydown', hideTooltip);
    doc.on('mousedown', hideTooltip);
  };
  
  exports.detach = function detach() {
    if (!isAttached) return;
    isAttached = false;
  
    removeTooltipFromDOM();
    const doc = $(document);
    doc.undelegate('mouseover', '[data-tooltip]', showTooltip);
    doc.undelegate('mouseout', '[data-tooltip]', hideTooltip);
    doc.off('keydown', hideTooltip);
    doc.off('mousedown', hideTooltip);
  };
  
  /** Forces an update of the tooltip text. */
  exports.update = function update() {
    if (!isAttached) return;
  
    if (!attachedNode) return;
  
    if (attachedNode.data('tooltip')) {
      showTooltip(null, attachedNode);
    } else {
      // The tooltip no longer has text and should be hidden.
      hideTooltip();
    }
  };
  
  // Export for testing purposes
  exports._setText = setText;
  
  },{"../spotify-elements":210}],287:[function(require,module,exports){
  'use strict';
  
  const prime = require('../../../libs/prime');
  const Emitter = require('../../../libs/prime/emitter');
  const Range = require('../../range2');
  
  const SYNC = Emitter.EMIT_SYNC;
  
  module.exports = prime({
    mixin: Emitter,
  
    constructor: function (container, params) {
      this.container = container;
  
      this.height = params.height || 0;
      this.length = params.length || 0;
  
      this.tag = params.tag || 'li';
      this.request = params.request;
  
      // Unique call identificator.
      this._id = 0;
  
      this.reset();
    },
  
    resetFrom: function (fromIndex, length, hiddenIndices) {
      const range = this._range;
  
      // Redirect the call to reset.
      if (fromIndex < range.end) {
        this.reset(length, hiddenIndices);
        this.show(range);
      }
    },
  
    reset: function (length, hiddenIndices) {
      const tag = this.tag;
  
      if (typeof length !== 'undefined') {
        this.length = length;
      }
  
      const totalHeight = this.length * this.height;
  
      // Hidden indices are not supported anymore (legacy).
      if (hiddenIndices) {
        throw new TypeError('Hidden indices are not supported anymore!');
      }
  
      this.container.innerHTML = `<${tag} style="height:${totalHeight}px"></${tag}>` + `<${tag} style="height:0px"></${tag}>`;
  
      this._range = new Range(0, 0);
      this._waitingRange = new Range(0, 0);
  
      // An array of ranges representing the visible nodes.
      this.emit('index-change', [], SYNC);
    },
  
    show: function (range) {
      const length = this.length;
      const actual = this._range;
      const waiting = this._waitingRange;
  
      // Limit range to the [0, length) interval.
      const bound = new Range(Math.max(0, range.start), Math.min(length, range.end));
  
      // If the range requested is the same, do nothing.
      if (bound.start === actual.start && bound.end === actual.end) {
        return;
      }
  
      // If the range requested is the same, do nothing.
      if (bound.start === waiting.start && bound.end === waiting.end) {
        return;
      }
  
      this._drawRange(bound);
    },
  
    _drawRange: function (range) {
      const container = this.container;
      const oldRange = this._range;
      const height = this.height;
      const length = this.length;
      const id = ++this._id;
  
      this._waitingRange = range;
  
      // Request the whole range. We will manually intersect later with the current one.
      this.request(range, (err, nodes) => {
        let diff = 0;
        let i;
  
        // If a new range has been requested in the meantime, don't draw/modify anything.
        if (id !== this._id) {
          return;
        }
  
        // Cache the range requested.
        this._range = range;
        this._waitingRange = new Range(0, 0);
  
        // Get first and last nodes as references. We will use them for removing/adding nodes.
        const first = container.firstChild;
        const last = container.lastChild;
  
        // Change heights of the paddings.
        first.style.height = `${range.start * height}px`;
        last.style.height = `${(length - range.end) * height}px`;
  
        // Remove old nodes. Since nodes change their position when removing them one by one,
        // we have to keep the cumulative sum of nodes removed cached.
        oldRange.subtract(range).forEach(function (subrange) {
          const subrangeLength = subrange.length;
  
          for (i = 0; i < subrangeLength; i++) {
            // We add one to the children index because the first child is not a row but the top padding.
            container.removeChild(container.children[subrange.start - oldRange.start - diff + 1]);
          }
  
          diff += subrangeLength;
        });
  
        // Add new nodes. If the new nodes are below the old range, insert them at the top;
        // otherwise, insert them at the bottom.
        range.subtract(oldRange).forEach(subrange => {
          const subrangeLength = subrange.length;
          const start = subrange.start - range.start;
          const subnodes = nodes.slice(start, start + subrangeLength);
          let ref;
  
          // Emit a change before.
          this.emit('before-show', subrange, subnodes, SYNC);
  
          // Get the reference node. If the range is below, then insert at the top.
          // Otherwise, insert at the bottom.
          if (subrange.below(oldRange)) {
            ref = first.nextSibling;
          } else {
            ref = last;
          }
  
          for (i = 0; i < subrangeLength; i++) {
            container.insertBefore(subnodes[i], ref);
          }
  
          // Emit a change after.
          this.emit('after-show', subrange, subnodes, SYNC);
        });
  
        this.emit('index-change', [range], SYNC);
      });
    }
  });
  
  },{"../../../libs/prime":202,"../../../libs/prime/emitter":201,"../../range2":204}],288:[function(require,module,exports){
  'use strict';
  
  const Position = function (containerIndex, index) {
    this.containerIndex = containerIndex;
    this.index = index;
  };
  
  Position.prototype.isSame = function (position) {
    return this.containerIndex === position.containerIndex && this.index === position.index;
  };
  
  module.exports = Position;
  
  },{}],289:[function(require,module,exports){
  'use strict';
  
  const live = require('../../live');
  
  function checkIfActive() {
    return live('spotify:application').get('active') || false;
  }
  
  function checkIfFocused() {
    // If the active element of the top frame has a contentWindow that matches
    // this window, it means the iframe of this app is in focus.
    const topActiveElement = window.top.document.activeElement;
    const activeWindow = topActiveElement && topActiveElement.contentWindow;
    if (activeWindow) {
      return activeWindow === window;
    }
  
    // Top frame (zlink)
    if (window.top === window) {
      return true;
    }
  
    return false;
  }
  
  exports.isActive = function () {
    return checkIfActive();
  };
  
  exports.isActiveAndFocused = function () {
    return checkIfActive() && checkIfFocused();
  };
  
  },{"../../live":172}],290:[function(require,module,exports){
  /**
   * @module spotify-events/util/cosmos
   * @private
   */
  
  'use strict';
  
  const live = require('../../live');
  const cosmos = require('spotify-cosmos-api');
  const liburi = require('spotify-liburi');
  
  const ASAP = live.ASAP;
  
  function DELETE(options, callback) {
    options.method = exports.cosmos.Action.DELETE;
    return request(options, callback);
  }
  
  function GET(options, callback) {
    options.method = exports.cosmos.Action.GET;
    return request(options, callback);
  }
  
  function SUB(options, callback) {
    options.method = exports.cosmos.Action.SUB;
    return request(options, callback);
  }
  
  function POST(options, callback) {
    options.method = exports.cosmos.Action.POST;
    return request(options, callback);
  }
  
  function PUT(options, callback) {
    options.method = exports.cosmos.Action.PUT;
    return request(options, callback);
  }
  
  function HEAD(options, callback) {
    options.method = exports.cosmos.Action.HEAD;
    return request(options, callback);
  }
  
  function request(options, callback) {
    const method = options.method;
    delete options.method;
  
    let subscription;
    let canceled;
  
    sanitizeURL(options.url, function (error, url) {
      if (error) {
        callback(error);
        return;
      }
  
      // if you cancel before the userName is replaced.
      if (canceled) return;
  
      const cosmosRequest = new exports.cosmos.Request(method || exports.cosmos.Action.GET, url, options.headers, options.body);
      subscription = exports.cosmos.resolver.resolve(cosmosRequest, function (cosmosError, response) {
        if (!callback) return;
        if (cosmosError) {
          callback(cosmosError);
          return;
        }
        try {
          callback(null, {
            body: JSON.parse(response.getBody() || '{}'),
            headers: response.getHeaders(),
            status: response.getStatusCode()
          });
        } catch (parseError) {
          parseError.response = response;
          callback(parseError);
        }
      });
    });
  
    return {
      cancel: function () {
        if (subscription && subscription.cancel) {
          // we already subscribed, cancel it.
          subscription.cancel();
          subscription = null;
        } else if (!canceled) {
          // not subscribed yet, don't even subscribe.
          canceled = true;
        }
        return null;
      }
    };
  }
  
  function sanitizeURL(url, callback) {
    if (url.indexOf('@') > -1) {
      live('spotify:client').query('currentUser(username)', function (error, data) {
        if (error) {
          callback(error);
          return;
        }
        callback(null, url.replace('@', liburi.getCanonicalUsername(data.currentUser.username)));
      }, ASAP);
    } else {
      callback(null, url);
    }
  }
  
  exports.request = request;
  exports.get = GET;
  exports.post = POST;
  exports.subscribe = SUB;
  exports.delete = DELETE;
  exports.put = PUT;
  exports.head = HEAD;
  exports.cosmos = cosmos;
  
  exports.sanitizeURL = sanitizeURL;
  
  },{"../../live":172,"spotify-cosmos-api":543,"spotify-liburi":553}],291:[function(require,module,exports){
  'use strict';
  
  /**
   * @public
   */
  
  /**
   * Get the context URI from the specified node or its parents.
   *
   * @param {HTMLElement} node A DOM node.
   *
   * @return {string} The context URI or empty string if not found.
   */
  
  function getContextFromNodeOrParent(node) {
    const contextNode = node.closest('[data-context]');
    return contextNode && contextNode.getAttribute('data-uri') || '';
  }
  
  /**
   * Get the value for the passed attribute name, either from the passed node
   * or from any parent node.
   *
   * @param {HTMLElement} node The node to start searching from.
   * @param {string} attributeName The name of the attribute.
   *
   * @return {string} The value, or empty string if not found.
   */
  function getAttributeFromNodeOrParent(node, attributeName) {
    const selector = `[${attributeName}]`;
    const attributeNode = node.closest(selector);
    return attributeNode && attributeNode.getAttribute(attributeName) || '';
  }
  
  exports.getContextFromNodeOrParent = getContextFromNodeOrParent;
  exports.getAttributeFromNodeOrParent = getAttributeFromNodeOrParent;
  
  },{}],292:[function(require,module,exports){
  'use strict';
  
  module.exports = function getLogContext(target) {
    let currentNode = target;
    const logContext = [];
    while (currentNode) {
      if (currentNode.matches && currentNode.matches('[data-log-context]')) {
        logContext.unshift(currentNode.getAttribute('data-log-context'));
      }
      currentNode = currentNode.parentNode;
    }
    return logContext.join('/') || null;
  };
  
  },{}],293:[function(require,module,exports){
  'use strict';
  
  const rAF = window.requestAnimationFrame || function (fn) {
    setTimeout(fn, 16);
  };
  
  /**
   * Throttle the function calls to only trigger once per tick.
   * This will return a new function that you will use when you want
   * to call your function. Calling it multiple times in the same
   * run loop will only really call it once.
   *
   * @param {function} fn A function.
   * @param {Object=} ctx The context to run the function in ('this').
   *
   * @return {function} A new function.
   */
  function throttle(fn, ctx) {
    let queued = false;
    let args;
  
    return function () {
      args = arguments;
      if (!queued) {
        queued = true;
        rAF(function () {
          queued = false;
          fn.apply(ctx, args);
        });
      }
    };
  }
  
  module.exports = throttle;
  
  },{}],294:[function(require,module,exports){
  'use strict';
  
  // Usage
  // Data {{#compare 'myvalue' value}}has{{else}}doesn't have{{/compare}} myvalue.
  // Data {{#compare '5' tracks.length true}}has{{else}}doesn't have{{/compare}} 5 tracks.
  // Data {{#compare tracks.length 5 operator='>'}}has more than{{else}}has less than or equal to{{/compare}} 5 tracks.
  
  const operators = {
    '==': function (a, b) {
      /* eslint eqeqeq: 0 */return a == b;
    },
    '===': function (a, b) {
      return a === b;
    },
    '!=': function (a, b) {
      /* eslint eqeqeq: 0 */return a != b;
    },
    '<': function (a, b) {
      return a < b;
    },
    '>': function (a, b) {
      return a > b;
    },
    '<=': function (a, b) {
      return a <= b;
    },
    '>=': function (a, b) {
      return a >= b;
    },
    'typeof': function (a, b) {
      return typeof a == b;
    }
  };
  
  function compare(a, b, s) {
    const options = arguments[arguments.length - 1];
    const soft = s !== options ? s : false;
    const operator = soft ? '==' : options.hash.operator || '===';
  
    const match = operators[operator](a, b);
  
    return match ? options.fn(this) : options.inverse(this);
  }
  
  compare.displayName = 'compare';
  
  module.exports = compare;
  
  },{}],295:[function(require,module,exports){
  'use strict';
  
  const duration = function (ms) {
    if (isNaN(ms)) return ms;
  
    const s = Math.round(ms / 1000);
    const oneMinute = 60;
    const oneHour = 60 * oneMinute;
  
    let hours = Math.floor(s / oneHour);
    let minutes = Math.floor(s % oneHour / oneMinute);
    let seconds = Math.round(s % oneHour % oneMinute);
  
    // Include hours if needed
    if (hours) {
      hours += ':';
  
      // If hours are specified, minutes should always be with two digits
      if (minutes < 10) minutes = `0${minutes}`;
    } else {
      hours = '';
    }
  
    // Since minutes should always be displayed, seconds should always be with two digits
    if (seconds < 10) seconds = `0${seconds}`;
  
    return `${hours + minutes}:${seconds}`;
  };
  
  duration.displayName = 'duration';
  
  module.exports = duration;
  
  },{}],296:[function(require,module,exports){
  'use strict';
  
  const nameListTooltip = require('./name-list-tooltip');
  
  const facepile = function (list, options) {
    if (!list) {
      return '';
    }
  
    const len = list.length;
    const thresh = Math.min(3, len);
    let ret = '';
  
    // Display faces.
    for (let i = 0; i < thresh; i++) {
      const obj = list[i];
      ret += options.fn(obj);
    }
  
    // Display badge count for hidden faces.
    if (len > thresh) {
      ret += options.inverse({
        badgeCount: len - thresh,
        badgeTooltip: nameListTooltip(list.slice(thresh), len - thresh)
      });
    }
    return ret;
  };
  
  facepile.displayName = 'facepile';
  
  module.exports = facepile;
  
  },{"./name-list-tooltip":303}],297:[function(require,module,exports){
  (function (global){
  'use strict';
  
  const getHTTPLink = require('../util/link');
  
  const isDesktop = !!global._getSpotifyModule;
  
  const href = function (uri) {
    if (isDesktop) return uri;
    return getHTTPLink(uri, 'https://play.spotify.com');
  };
  
  href.displayName = 'href';
  
  module.exports = href;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"../util/link":310}],298:[function(require,module,exports){
  'use strict';
  
  const loc = require('./loc');
  
  const humanizeDuration = function (ms) {
    if (isNaN(ms)) return ms;
  
    const s = Math.round(ms / 1000);
    const oneMinute = 60;
    const oneHour = 60 * oneMinute;
  
    const hours = Math.floor(s / oneHour);
    const minutes = Math.floor(s % oneHour / oneMinute);
    const seconds = Math.round(s % oneHour % oneMinute);
  
    let formattedDuration = '';
  
    if (hours) {
      formattedDuration += loc('HoursShort', hours, {});
    }
  
    if (minutes) {
      formattedDuration += ' ';
      formattedDuration += loc('MinutesShort', minutes, {});
    }
  
    if (!hours && !minutes || hours === 0 && minutes < 10) {
      formattedDuration += ' ';
      formattedDuration += loc('SecondsShort', seconds, {});
    }
  
    return formattedDuration.trim();
  };
  
  humanizeDuration.displayName = 'humanizeDuration';
  
  module.exports = humanizeDuration;
  
  },{"./loc":302}],299:[function(require,module,exports){
  'use strict';
  
  exports.compare = require('./compare');
  exports.duration = require('./duration');
  exports.facepile = require('./facepile');
  exports.href = require('./href');
  exports.humanizeDuration = require('./humanize-duration');
  exports.list = require('./list');
  exports.loc = require('./loc');
  exports.nameListTooltip = require('./name-list-tooltip');
  exports.numeral = require('./numeral');
  exports.share = require('./share');
  exports.slice = require('./slice');
  exports.type = require('./type');
  exports.userReaction = require('./user-reaction');
  exports.json = require('./json');
  
  },{"./compare":294,"./duration":295,"./facepile":296,"./href":297,"./humanize-duration":298,"./json":300,"./list":301,"./loc":302,"./name-list-tooltip":303,"./numeral":304,"./share":305,"./slice":306,"./type":307,"./user-reaction":308}],300:[function(require,module,exports){
  'use strict';
  
  const json = function (context) {
    return JSON.stringify(context);
  };
  
  json.displayName = 'json';
  
  module.exports = json;
  
  },{}],301:[function(require,module,exports){
  'use strict';
  
  const map = require('mout/array/map');
  
  const Handlebars = require('handlebars/dist/cjs/handlebars.runtime').default;
  
  const lists = {};
  
  const list = function (array) {
    if (!array) {
      return '';
    }
  
    const args = Array.prototype.slice.call(arguments);
    const options = args.pop();
    const listID = args[1];
  
    if (array === 'reset') {
      delete lists[listID];
      return '';
    }
  
    if (listID && lists[listID] === undefined) {
      lists[listID] = -1;
    }
  
    return map(array, function (item, i) {
      const data = Handlebars.createFrame(options.data || {});
      data.index = listID ? ++lists[listID] : i;
      data.number = data.index + 1;
      data.localIndex = i;
      data.localNumber = i + 1;
      return options.fn(item, { data: data });
    }).join(options.hash && options.hash.join || '');
  };
  
  list.displayName = 'list';
  
  module.exports = list;
  
  },{"handlebars/dist/cjs/handlebars.runtime":349,"mout/array/map":442}],302:[function(require,module,exports){
  'use strict';
  
  var _mixIn = require('mout/object/mixIn');
  
  var _mixIn2 = _interopRequireDefault(_mixIn);
  
  var _locales = require('../../locales');
  
  var _locales2 = _interopRequireDefault(_locales);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  const slice_ = Array.prototype.slice;
  
  let locale = 'en';
  let numberFormat = new Intl.NumberFormat(locale);
  let pluralRules = new Intl.PluralRules(locale);
  const storage = {};
  
  const loc = function (key) {
    let args = slice_.call(arguments, 1);
    let options = {};
  
    const lastArg = args[args.length - 1];
    if (typeof lastArg === 'object' && 'hash' in lastArg) {
      options = args.pop();
    }
  
    const hash = options.hash;
  
    const value = typeof storage[key] === 'object' ? storage[key][pluralRules.select(args[0])] || storage[key].other : storage[key];
  
    if (!value) return '';
  
    // If we are passed an array, use that for replacements
    if (Array.isArray(args[0])) {
      args = args[0];
    }
  
    return value.replace(/\{([\w-]+)\}/g, function (full, match) {
      const n = +match;
      let interpolated;
  
      if (isNaN(n)) {
        interpolated = hash[match];
      } else {
        interpolated = args[n];
      }
  
      if (typeof interpolated === 'number') {
        interpolated = numberFormat.format(interpolated);
      }
  
      return interpolated !== null ? interpolated : '';
    });
  };
  
  loc.displayName = 'loc';
  
  loc.register = function (object) {
    (0, _mixIn2.default)(storage, object);
    return this;
  };
  
  loc.setLocale = function (newLocale) {
    locale = newLocale;
    numberFormat = new Intl.NumberFormat((0, _locales2.default)(locale, 'intl'));
    pluralRules = new Intl.PluralRules((0, _locales2.default)(locale, 'intl'));
  };
  
  module.exports = loc;
  
  },{"../../locales":177,"mout/object/mixIn":471}],303:[function(require,module,exports){
  'use strict';
  
  const loc = require('./loc');
  
  const nameListTooltip = function (users, totalCount) {
    if (totalCount === 0) {
      return undefined;
    }
  
    const len = users.length;
    const textTokens = users.map(function (user) {
      return user.name;
    });
  
    if (totalCount - len > 0) {
      const andMoreText = loc('AndMore', totalCount - len);
      textTokens.push(andMoreText);
    }
  
    return textTokens.join('<br>\n').replace(/'/g, '&#39;');
  };
  
  nameListTooltip.displayName = 'nameListTooltip';
  
  module.exports = nameListTooltip;
  
  },{"./loc":302}],304:[function(require,module,exports){
  'use strict';
  
  const isNumber = require('mout/lang/isNumber');
  
  /**
   * If the first argument is a number, pipe it through spotify-numeral with an
   * optional format propety in the options hash, otherwise return it untouched.
   *
   * Example usage – results when using fr locale:
   *
   * {{numeral 1000}} -> '1 000'
   * {{numeral 1000 format='0,0.000'}} -> '1 000,000'
   * {{numeral 1000 format='$0,0.00'}} -> '€1 000,00'
   *
   * @param {Number|String} number - The number to format (or string to leave
   *     untouched).
   * @param {Object} [options] - The handlebars options object.
   * @param {Object} [options.hash] - The handlebars options hash object.
   * @param {String} [options.hash.format] - The optional format to pass to
   *     numeraljs.
   * @return {String} The formatted (or untouched) string.
   */
  const numeralHelper = function (number, options) {
    const format = options && options.hash && options.hash.format;
    const numeral = numeralHelper._numeral;
    return isNumber(number) ? numeral(number).format(format) : number;
  };
  
  numeralHelper.displayName = 'numeral';
  
  module.exports = numeralHelper;
  
  // This is tricky, but is a way to allow for the locale to be injected
  // from the consuming app instead of from within spotify-numeral, removing
  // the dependency on spotify-quickstart.
  module.exports.setLocale = function (locale) {
    numeralHelper._numeral = require('../../spotify-numeral')(locale);
  };
  
  },{"../../spotify-numeral":314,"mout/lang/isNumber":457}],305:[function(require,module,exports){
  'use strict';
  
  const getHTTPLink = require('../util/link');
  
  const share = function (uri) {
    return getHTTPLink(uri, 'https://open.spotify.com');
  };
  
  share.displayName = 'share';
  
  module.exports = share;
  
  },{"../util/link":310}],306:[function(require,module,exports){
  'use strict';
  
  const kindOf = require('mout/lang/kindOf');
  const map = require('mout/array/map');
  const forEach = require('mout/array/forEach');
  
  const Handlebars = require('handlebars/dist/cjs/handlebars.runtime').default;
  const slice_ = Array.prototype.slice;
  
  const slice = function (arr) {
    let array = arr;
  
    if (!array) {
      return '';
    }
  
    let sep;
    let begin;
    let end;
    let options;
  
    forEach(slice_.call(arguments, 1), function (arg) {
      switch (kindOf(arg)) {
        case 'String':
          sep = arg;
          break;
  
        case 'Number':
          if (begin === null) {
            begin = arg;
          } else if (end === null) {
            end = arg;
          }
          break;
  
        case 'Object':
          options = arg;
          break;
  
        default:
        // Do nothing.
      }
    });
  
    if (begin !== null) {
      array = array.slice(begin, end !== null ? end : array.length);
    }
  
    return map(array, function (item, index) {
      const data = Handlebars.createFrame(options.data || {});
      data.index = index;
  
      return options.fn(item, { data: data });
    }).join(sep || '');
  };
  
  slice.displayName = 'slice';
  
  module.exports = slice;
  
  },{"handlebars/dist/cjs/handlebars.runtime":349,"mout/array/forEach":439,"mout/array/map":442,"mout/lang/kindOf":461}],307:[function(require,module,exports){
  'use strict';
  
  const getType = require('../util/type');
  
  // This needs at least the uri param to get the type:
  // {{type uri}}
  //
  // If you pass more parameters you can use it like a matcher:
  // {{#type uri "track" "album"}}YAY{{/type}}
  // This will print 'YAY' for tracks or albums.
  
  const type = function (uri) {
    if (arguments.length <= 2) {
      // Simple get type.
      return getType(uri);
    }
  
    // Match type.
    const context = arguments[arguments.length - 1];
    let success = false;
    const uriType = getType(uri);
  
    for (let i = 1; i < arguments.length - 1; i++) {
      if (uriType === arguments[i]) {
        success = true;
        break;
      }
    }
  
    return success ? context.fn(this) : context.inverse(this);
  };
  
  type.displayName = 'type';
  
  module.exports = type;
  
  },{"../util/type":311}],308:[function(require,module,exports){
  'use strict';
  
  const loc = require('./loc');
  
  const userReaction = function (users, totalCount, reactionType) {
    if (!totalCount) return '';
    const key = `${reactionType}Reaction${totalCount > 1 ? 'Multi' : 'Single'}`;
  
    if (totalCount > 1) {
      return loc(key, totalCount);
    }
  
    if (!users.length) {
      return '';
    }
  
    const firstListener = users[0].name;
    return loc(key, firstListener);
  };
  
  userReaction.displayName = 'userReaction';
  
  module.exports = userReaction;
  
  },{"./loc":302}],309:[function(require,module,exports){
  'use strict';
  
  const kindOf = require('mout/lang/kindOf');
  
  const Handlebars = require('handlebars/dist/cjs/handlebars.runtime').default;
  
  // Expose the runtime to make partials easier to register.
  exports.runtime = Handlebars;
  
  exports.register = function (helpers) {
    let list = {};
  
    if (kindOf(helpers) === 'Function') {
      list[helpers.displayName] = helpers;
    } else {
      list = helpers;
    }
  
    for (const key in list) {
      if (list.hasOwnProperty(key)) {
        Handlebars.registerHelper(key, list[key]);
      }
    }
  
    return this;
  };
  
  },{"handlebars/dist/cjs/handlebars.runtime":349,"mout/lang/kindOf":461}],310:[function(require,module,exports){
  'use strict';
  
  module.exports = function (uri, base) {
    const matches = (uri || '').match(/^spotify:(.+)$/);
  
    if (!matches) {
      return uri || '';
    }
  
    let parts = matches.pop().replace(/:$/, '').split(/:/);
    const type = parts.shift();
  
    if (type === 'search') {
      parts = [parts.join(':')];
    }
  
    parts.unshift(base, type);
  
    return parts.join('/');
  };
  
  },{}],311:[function(require,module,exports){
  'use strict';
  
  module.exports = function (uri) {
    if (!uri || !uri.split) {
      return null;
    }
    if (uri == 'youtube') {
      return 'youtube'
    }
    const parts = uri.split(':');
    const result = null;
    switch (parts[1]) {
      case 'album':
        // spotify:album:<id>:<disc>
        if (parts.length === 4) {
          return 'disc';
        } else if (parts.length === 3) {
          return 'album';
        }
        break;
  
      case 'artist':
        if (parts.length === 3) {
          return 'artist';
        }
        break;
  
      case 'track':
        if (parts.length === 3) {
          return 'track';
        }
        break;
  
      case 'playlist':
        // spotify:playlist:xxx
        if (parts.length === 3) {
          return 'playlist';
        }
        break;
  
      case 'genre':
        if (parts.length === 3) {
          return 'genre';
        }
        break;
  
      // spotify:show:<id>
      case 'show':
        if (parts.length === 3) {
          return 'show';
        }
        break;
  
      case 'station':
        // spotify:station:<station-type>:<id>
        if (parts.length > 3) {
          return 'station';
        }
        break;
  
      case 'episode':
        // spotify:episode:<id>
        if (parts.length === 3) {
          return 'episode';
        }
        break;
  
      case 'local':
        if (parts.length === 6) {
          return 'track';
        } else if (parts.length === 4) {
          return 'album';
        } else if (parts.length === 3) {
          return 'artist';
        }
        break;
  
      case 'user':
        // spotify:user:<username>:collection:album:<id>
        if (parts.length > 3 && parts[3] === 'collection' && parts[4] === 'album') {
          return 'collection-album';
        }
        // spotify:user:<username>:collection:artist:<id>
        if (parts.length > 3 && parts[3] === 'collection' && parts[4] === 'artist') {
          return 'collection-artist';
        }
        // spotify:user:<username>:collection
        if (parts.length > 3 && parts[3] === 'collection') {
          return 'collection';
        }
  
        // spotify:user:<username>:folder:<id>
        if (parts.length === 5 && parts[3] === 'folder') {
          return 'playlist-folder';
        }
  
        // spotify:user:<username>:cluster:<id>
        if (parts.length === 5 && parts[3] === 'cluster') {
          return 'cluster';
        }
  
        // spotify:user:<username>:playlist:<id>|starred|toplist>
        if (parts.length > 3 && parts.length <= 5 && parts[2] !== 'facebook') {
          return 'playlist';
        } else if (parts.length === 3) {
          return 'user';
        }
        break;
  
      case 'internal':
        // spotify:internal:local-files
        return parts[2];
  
      case 'app':
        // spotify:app:collection:albums, spotify:app:radio
        return parts.slice(1).join('-');
  
      default:
      // Do nothing.
    }
  
    return result;
  };
  
  },{}],312:[function(require,module,exports){
  'use strict';
  
  var _strings = require('../glue/strings');
  
  var _strings2 = _interopRequireDefault(_strings);
  
  var _interpolateString = require('./src/interpolate-string');
  
  var _interpolateString2 = _interopRequireDefault(_interpolateString);
  
  var _locales = require('../locales');
  
  var _locales2 = _interopRequireDefault(_locales);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  module.exports = function (appStringsByLocale) {
    if (!appStringsByLocale || typeof appStringsByLocale !== 'object') {
      throw new Error('`appStringsByLocale` must be a plain object. See spotify-i18n/README.md');
    }
  
    function getClientLocale() {
      const preference = typeof window !== 'undefined' && window && window.__spotify && window.__spotify.locale;
      return preference in appStringsByLocale ? preference : 'en';
    }
  
    // Intl.PluralRules instances aren't super fast to create, so cache and re-use.
    const pluralRulesByLocale = {};
    function selectPluralRule(locale, count) {
      const pluralRules = pluralRulesByLocale[locale] || (pluralRulesByLocale[locale] = new Intl.PluralRules((0, _locales2.default)(locale, 'intl')));
      return pluralRules.select(count);
    }
  
    function get(stringsByLocale, key, ...parameters) {
      const locale = getClientLocale();
      const localised = stringsByLocale[locale][key];
  
      const string = typeof localised !== 'object' ? localised : localised[selectPluralRule(locale, parameters[0])] ||
      // This will mainly happen while waiting for translations to come back,
      // during which time the Polish lang file (for example) will contain the
      // English plural forms. Falling back to "other" covers this quite nicely.
      localised.other;
  
      if (typeof string === 'undefined') {
        return key;
      }
      return (0, _interpolateString2.default)(string, parameters, locale);
    }
  
    return {
      locale: getClientLocale,
  
      get: get.bind(null, appStringsByLocale),
  
      getGlueString: get.bind(null, _strings2.default),
  
      glueStrings: function () {
        return _strings2.default[getClientLocale()];
      },
  
      appStrings: function () {
        return appStringsByLocale[getClientLocale()];
      }
    };
  };
  
  },{"../glue/strings":86,"../locales":177,"./src/interpolate-string":313}],313:[function(require,module,exports){
  'use strict';
  
  /**
   * Replaces placeholders with their corresponding parameters
   *
   * This used to be a part of the spotify-translations lib. That package was
   * absorbed into spotify-i18n during the addition of pluralization.
   */
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.default = interpolateString;
  function interpolateString(string, parameters = [], locale) {
    const numberFormat = new Intl.NumberFormat(locale || 'en');
  
    return string.replace(/\{(\d+?)\}/g, function (str, num) {
      const value = parameters[num];
  
      if (typeof value === 'undefined') {
        return str;
      }
  
      if (typeof value === 'number') {
        return numberFormat.format(value);
      }
  
      return value;
    });
  }
  
  },{}],314:[function(require,module,exports){
  /* eslint-disable no-nested-ternary */
  
  'use strict';
  
  var _locales = require('../locales');
  
  var _locales2 = _interopRequireDefault(_locales);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function getOrdinalFunction(currentLanguage) {
    switch (currentLanguage) {
      case 'cs':
      case 'de':
      case 'el':
      case 'fi':
      case 'hu':
      case 'ja':
      case 'pl':
      case 'sv':
      case 'zsm':
        return n => `${n}.`;
  
      case 'it':
      case 'pt-BR':
        return n => `${n}º`;
  
      case 'fr':
      case 'fr-CA':
        return n => `${n}${n === 1 ? 'er' : 'e'}`;
  
      case 'es':
      case 'es-419':
        return (n, b = n % 10) => `${n}${b === 1 || b === 3 ? 'er' : b === 2 ? 'do' : b === 7 || b === 0 ? 'mo' : b === 8 ? 'vo' : b === 9 ? 'no' : 'to'}`;
  
      case 'id':
        // According to Spotify's internal Indonesian specialist,
        // there is no ordinal symbol in Indonesian (like st, nd, th, º, ª),
        // they just use the word (equivalent to writing: first, second,
        // third, instead of 1st, 2nd, 3rd). And as we don't have such
        // capability to translate all numbers to words, this function
        // returns the cardinal number as a string.
        return n => `${n}`;
  
      case 'nl':
        return (n, b = n % 100) => `${n}${n !== 0 && b <= 1 || b === 8 || b >= 20 ? 'ste' : 'de'}`;
  
      case 'tr':
        return n => {
          if (n === 0) {
            // special case for zero
            return `${n}'ıncı`;
          }
          const suffixes = {
            1: "'inci",
            5: "'inci",
            8: "'inci",
            70: "'inci",
            80: "'inci",
  
            2: "'nci",
            7: "'nci",
            20: "'nci",
            50: "'nci",
  
            3: "'üncü",
            4: "'üncü",
            100: "'üncü",
  
            6: "'ncı",
  
            9: "'uncu",
            10: "'uncu",
            30: "'uncu",
  
            60: "'ıncı",
            90: "'ıncı"
          };
          const a = n % 10;
          const b = n % 100 - a;
          const c = n >= 100 ? 100 : null;
  
          return `${n}${suffixes[a] || suffixes[b] || suffixes[c]}`;
        };
  
      case 'zh-Hant':
        return n => `第${n}`;
  
      case 'en':
        return (n, b = n % 10) => `${n}${~~(n % 100 / 10) === 1 ? 'th' : b === 1 ? 'st' : b === 2 ? 'nd' : b === 3 ? 'rd' : 'th'}`;
  
      case 'th':
        // There is no ordinal symbol in Thai (like st, nd, th, º, ª),
        // they just numbers 1,2,3. Suppose the '1st' album will be 'album number 1'.
        // And as we don't have such capability to reorder words,
        // this function just returns the cardinal number as a string.
        return n => `${n}`;
  
      case 'vi':
        return n => {
          if (n === 1) {
            // special case for 1st
            return 'thứ nhất';
          }
          return `thứ ${n}`;
        };
  
      default:
        console.warn(`No explicit ordinal formatter specified for "${currentLanguage}" locale – please add to spotify-numeral`);
        return n => `${n}.`;
    }
  }
  
  module.exports = function (currentLanguage) {
    const getOrdinal = getOrdinalFunction(currentLanguage);
    const numberFormat = new Intl.NumberFormat((0, _locales2.default)(currentLanguage, 'intl'));
  
    const getFormattedNumber = (number, format = '0,0') => {
      if (format === '0,0') return numberFormat.format(number);else if (format === '0o') return getOrdinal(number);
      throw new Error(`Unsupported format ${format}`);
    };
  
    return number => ({
      format: format => getFormattedNumber(number, format)
    });
  };
  
  },{"../locales":177}],315:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.watchSessionChange = exports.dispatchQBEUpdate = exports.dispatchQBEShowSuccessMessage = exports.dispatchQBEShowHighlight = exports.dispatchQBEShowModal = exports.dispatchUriAdded = exports.dispatchAppOpened = exports.subscribeToMessages = exports.sendQBEAction = exports.sendMessages = exports.QBE_BROWSE = exports.QBE_FOLLOW_FRIENDS = exports.QBE_SAVE_ENTITY = exports.QBE_ADD_TO_PLAYLIST = exports.QBE_CREATE_PLAYLIST = exports.QBE_SEARCH = exports.QBE_SIGNUP = exports.QBE_SHOW_SUCCESS_MESSAGE = exports.QBE_UPDATE = exports.QBE_SHOW_HIGHLIGHT = exports.QBE_SHOW_MODAL = exports.SESSION_ONLINE_STATUS_CHANGE = exports.ADD_URI = exports.OPEN_PLAYLIST = exports.OPEN_APP = undefined;
  
  var _bridge = require('../../libs/bridge');
  
  var _bridge2 = _interopRequireDefault(_bridge);
  
  var _live = require('../../libs/live');
  
  var _live2 = _interopRequireDefault(_live);
  
  var _kernel = require('./kernel');
  
  var _kernel2 = _interopRequireDefault(_kernel);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  const sideEffects = {
    bridge: _bridge2.default,
    live: _live2.default
  };
  const shell = (0, _kernel2.default)(sideEffects);
  
  const {
    OPEN_APP,
    OPEN_PLAYLIST,
    ADD_URI,
    SESSION_ONLINE_STATUS_CHANGE,
  
    QBE_SHOW_MODAL,
    QBE_SHOW_HIGHLIGHT,
    QBE_UPDATE,
    QBE_SHOW_SUCCESS_MESSAGE,
    QBE_SIGNUP,
    QBE_SEARCH,
    QBE_CREATE_PLAYLIST,
    QBE_ADD_TO_PLAYLIST,
    QBE_SAVE_ENTITY,
    QBE_FOLLOW_FRIENDS,
    QBE_BROWSE,
  
    sendMessages,
    sendQBEAction,
    subscribeToMessages,
    dispatchAppOpened,
    dispatchUriAdded,
    dispatchQBEShowModal,
    dispatchQBEShowHighlight,
    dispatchQBEShowSuccessMessage,
    dispatchQBEUpdate,
    watchSessionChange
  } = shell;
  exports.OPEN_APP = OPEN_APP;
  exports.OPEN_PLAYLIST = OPEN_PLAYLIST;
  exports.ADD_URI = ADD_URI;
  exports.SESSION_ONLINE_STATUS_CHANGE = SESSION_ONLINE_STATUS_CHANGE;
  exports.QBE_SHOW_MODAL = QBE_SHOW_MODAL;
  exports.QBE_SHOW_HIGHLIGHT = QBE_SHOW_HIGHLIGHT;
  exports.QBE_UPDATE = QBE_UPDATE;
  exports.QBE_SHOW_SUCCESS_MESSAGE = QBE_SHOW_SUCCESS_MESSAGE;
  exports.QBE_SIGNUP = QBE_SIGNUP;
  exports.QBE_SEARCH = QBE_SEARCH;
  exports.QBE_CREATE_PLAYLIST = QBE_CREATE_PLAYLIST;
  exports.QBE_ADD_TO_PLAYLIST = QBE_ADD_TO_PLAYLIST;
  exports.QBE_SAVE_ENTITY = QBE_SAVE_ENTITY;
  exports.QBE_FOLLOW_FRIENDS = QBE_FOLLOW_FRIENDS;
  exports.QBE_BROWSE = QBE_BROWSE;
  exports.sendMessages = sendMessages;
  exports.sendQBEAction = sendQBEAction;
  exports.subscribeToMessages = subscribeToMessages;
  exports.dispatchAppOpened = dispatchAppOpened;
  exports.dispatchUriAdded = dispatchUriAdded;
  exports.dispatchQBEShowModal = dispatchQBEShowModal;
  exports.dispatchQBEShowHighlight = dispatchQBEShowHighlight;
  exports.dispatchQBEShowSuccessMessage = dispatchQBEShowSuccessMessage;
  exports.dispatchQBEUpdate = dispatchQBEUpdate;
  exports.watchSessionChange = watchSessionChange;
  
  },{"../../libs/bridge":53,"../../libs/live":172,"./kernel":316}],316:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.default = kernel;
  
  var _spotifyLiburi = require('spotify-liburi');
  
  var _spotifyLiburi2 = _interopRequireDefault(_spotifyLiburi);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function kernel({ bridge, live }) {
    const OPEN_APP = 'open_app';
    const OPEN_PLAYLIST = 'open_playlist';
    const ADD_URI = 'add_uri'; // add/follow an URI
    const QBE_SHOW_MODAL = 'qbe_show_modal';
    const QBE_SHOW_HIGHLIGHT = 'qbe_show_highlight';
    const QBE_UPDATE = 'qbe_update';
    const QBE_SHOW_SUCCESS_MESSAGE = 'qbe_show_success_message';
    const SESSION_ONLINE_STATUS_CHANGE = 'session_online_status_change';
  
    // string IDs are the same as on the backend (Activity Manager)
    const QBE_SIGNUP = 'signup';
    const QBE_SEARCH = 'search';
    const QBE_CREATE_PLAYLIST = 'create_playlist';
    const QBE_ADD_TO_PLAYLIST = 'add_to_playlist';
    const QBE_SAVE_ENTITY = 'save_entity';
    const QBE_FOLLOW_FRIENDS = 'follow_friends';
    const QBE_BROWSE = 'browse';
  
    // QBE specific messages
  
    function sendMessages(messages) {
      bridge.cosmosJSON({
        method: 'POST',
        uri: 'sp://messages/v1/surround',
        body: messages
      });
    }
  
    // The seconds parameter passed to `callback` is an array of messages that may
    // contain 1 or more messages.
    function subscribeToMessages(callback) {
      bridge.cosmosJSON({
        method: 'SUB',
        uri: 'sp://messages/v1/surround'
      }, callback);
    }
  
    function dispatchAppOpened(appId, appArguments) {
      const messages = [{
        action: OPEN_APP,
        appId: appId
      }];
  
      switch (appId) {
        case 'playlist':
        case 'chart':
          {
            const openPlaylistMessage = function createOpenPlaylistMessageFromAppArguments() {
              // args are the arguments passed to the playlist app and chart app.
              const args = appArguments;
              let playlistUri = null;
  
              if (args.length === 1) {
                if (args[0] === 'local-files') {
                  playlistUri = 'spotify:internal:local-files';
                } else {
                  playlistUri = _spotifyLiburi2.default.playlistV2URI(args[0]).toString();
                }
              } else if (args.length >= 2) {
                playlistUri = _spotifyLiburi2.default.playlistURI(args[0], args[1]).toString();
              }
  
              if (playlistUri) {
                return { action: OPEN_PLAYLIST, playlistUri: playlistUri };
              }
              return null;
            }();
  
            if (openPlaylistMessage) {
              messages.push(openPlaylistMessage);
            }
            break;
          }
  
        default:
          break;
      }
  
      sendMessages(messages);
    }
  
    function dispatchUriAdded(uri, data) {
      if (!data.added) {
        return;
      }
  
      sendMessages([{
        action: ADD_URI,
        uri: uri
      }]);
    }
  
    function dispatchQBEShowModal(title, content, id, buttons) {
      sendMessages([{
        action: QBE_SHOW_MODAL,
        modal: { title, content, id },
        buttons
      }]);
    }
  
    function dispatchQBEShowHighlight(highlight, taskId) {
      sendMessages([{
        action: QBE_SHOW_HIGHLIGHT,
        highlight,
        taskId
      }]);
    }
  
    function dispatchQBEShowSuccessMessage(text) {
      sendMessages([{
        action: QBE_SHOW_SUCCESS_MESSAGE,
        text
      }]);
    }
  
    function dispatchQBEUpdate({ enabled, tasks = [] }) {
      sendMessages([{
        action: QBE_UPDATE,
        enabled,
        tasks
      }]);
    }
  
    function watchSessionChange() {
      function subscribe() {
        live('spotify:client').get('session').on('update', data => {
          if ('online' in data) {
            sendMessages([{
              action: SESSION_ONLINE_STATUS_CHANGE,
              online: data.online
            }]);
          }
        });
      }
      live('spotify:client').query('session(online)', (error, data) => {
        sendMessages([{
          action: SESSION_ONLINE_STATUS_CHANGE,
          online: data.session.online
        }]);
        subscribe();
      });
    }
  
    function sendQBEAction(action) {
      sendMessages([{ action }]);
    }
  
    return {
      OPEN_APP,
      OPEN_PLAYLIST,
      ADD_URI,
      SESSION_ONLINE_STATUS_CHANGE,
  
      QBE_SHOW_MODAL,
      QBE_SHOW_HIGHLIGHT,
      QBE_UPDATE,
      QBE_SHOW_SUCCESS_MESSAGE,
      QBE_SIGNUP,
      QBE_SEARCH,
      QBE_CREATE_PLAYLIST,
      QBE_ADD_TO_PLAYLIST,
      QBE_SAVE_ENTITY,
      QBE_FOLLOW_FRIENDS,
      QBE_BROWSE,
  
      sendMessages,
      sendQBEAction,
      subscribeToMessages,
      dispatchAppOpened,
      dispatchUriAdded,
      dispatchQBEShowModal,
      dispatchQBEShowHighlight,
      dispatchQBEShowSuccessMessage,
      dispatchQBEUpdate,
      watchSessionChange
    };
  }
  
  },{"spotify-liburi":553}],317:[function(require,module,exports){
  module.exports={
    "GLUE_COLOR_GREEN": "#1db954",
    "GLUE_COLOR_GREEN_LIGHT": "#1ed760",
    "GLUE_COLOR_GREEN_DARK": "#14833b",
    "GLUE_COLOR_BLACK": "#000000",
    "GLUE_COLOR_GRAY_7": "#121212",
    "GLUE_COLOR_GRAY_10": "#181818",
    "GLUE_COLOR_GRAY_15": "#282828",
    "GLUE_COLOR_GRAY_20": "#333333",
    "GLUE_COLOR_GRAY_25": "#404040",
    "GLUE_COLOR_GRAY_30": "#535353",
    "GLUE_COLOR_GRAY_60": "#a0a0a0",
    "GLUE_COLOR_GRAY_70": "#b3b3b3",
    "GLUE_COLOR_WHITE": "#ffffff",
    "GLUE_COLOR_UI_RED": "#cd1a2b",
    "GLUE_COLOR_UI_RED_LIGHT": "#e22134",
    "GLUE_COLOR_UI_RED_DARK": "#941320",
    "GLUE_COLOR_UI_BLUE": "#4687d6",
    "GLUE_COLOR_UI_BLUE_LIGHT": "#2e77d0",
    "GLUE_COLOR_UI_BLUE_DARK": "#1d4c85",
    "GLUE_COLOR_AQUAMARINE": "#9bf0e1",
    "GLUE_COLOR_AUBERGINE": "#503750",
    "GLUE_COLOR_AZURE": "#509bf5",
    "GLUE_COLOR_BOLE": "#c87d55",
    "GLUE_COLOR_BRIGHT_RED": "#eb1e32",
    "GLUE_COLOR_CHOCOLATE": "#7d4b32",
    "GLUE_COLOR_CITRIC": "#cdf564",
    "GLUE_COLOR_ELECTRIC_SEAFOAM": "#19e68c",
    "GLUE_COLOR_FACTORY_YELLOW": "#fae62d",
    "GLUE_COLOR_FOREST": "#006450",
    "GLUE_COLOR_FUCHSIA": "#f037a5",
    "GLUE_COLOR_GOLD": "#f59b23",
    "GLUE_COLOR_KLEIN_BLUE": "#4100f5",
    "GLUE_COLOR_LAVENDER": "#b49bc8",
    "GLUE_COLOR_MAROON": "#8c1932",
    "GLUE_COLOR_MIDNIGHT": "#1e3264",
    "GLUE_COLOR_NEON_GREEN": "#5ff550",
    "GLUE_COLOR_ORANGE": "#ff6437",
    "GLUE_COLOR_PINK": "#ffcdd2",
    "GLUE_COLOR_POWDER_GREEN": "#c3f0c8",
    "GLUE_COLOR_ROYAL_BLUE": "#2d46b9",
    "GLUE_COLOR_SALMON": "#dc2d73",
    "GLUE_COLOR_SPEARMINT": "#4b917d",
    "GLUE_COLOR_STORM": "#a0c3d2",
    "GLUE_COLOR_SUNFLOWER": "#ffc864",
    "GLUE_COLOR_TAN": "#c39687",
    "GLUE_COLOR_TANGERINE": "#ff4632",
    "GLUE_COLOR_VIOLET": "#af2896"
  }
  },{}],325:[function(require,module,exports){
  'use strict';
  
  exports.message = require('./src/message');
  exports.request = require('./src/request');
  exports.response = require('./src/response');
  exports.playerstate = require('./src/player_state');
  
  },{"./src/message":326,"./src/player_state":327,"./src/request":328,"./src/response":329}],326:[function(require,module,exports){
  /**
   * A set of Message headers.
   *
   * @name exports.Headers
   * @typedef {Object.<string, string>}
   */
  exports.Headers;
  
  /**
   * A body of a Request-Response.
   *
   * @name exports.Body
   * @typedef {*}
   */
  exports.Body;
  
  /**
   * A serialized Message object.
   *
   * @name Spotify.Cosmos.SerializedMessage
   * @typedef {{
   *   uri: Spotify.Cosmos.URI,
   *   headers: Spotify.Cosmos.Headers,
   *   body: Spotify.Cosmos.Body
   * }}
   */
  exports.SerializedMessage;
  
  /**
   * Encapsulates a message.
   *
   * A message is an entity that has a URI, headers and a body.
   *
   * @constructs Spotify.Cosmos.Message
   * @param {Spotify.Cosmos.URI} uri The URI of the message
   * @param {Spotify.Cosmos.Headers=} opt_headers The optional headers of the
   *     message.
   * @param {Spotify.Cosmos.Body=} opt_body The optional body of the message.
   */
  function Message(uri, opt_headers, opt_body) {
    if (uri == null)
      throw new TypeError('Invalid `uri` argument for Message.');
  
    /**
     * The URI of the Message.
     *
     * @type {Spotify.Cosmos.URI}
     * @protected
     */
    this._uri = uri;
  
    /**
     * The headers of the Message.
     *
     * @type {Spotify.Cosmos.Headers}
     * @protected
     */
    this._headers = {};
  
    /**
     * The body of the Message.
     *
     * @type {Spotify.Cosmos.Body}
     * @protected
     */
    this._body = this._encodeBody(opt_body || '');
  
    if (opt_headers) this._setHeaders(opt_headers);
  }
  exports.Message = Message;
  
  /**
   * Creates a new Message from a SerializedMessage object.
   *
   * @param {Spotify.Cosmos.SerializedMessage} object The serialized request.
   * @return {Spotify.Cosmos.Message|null} The new Message object or null.
   */
  Message.fromObject = function(object) {
    return (object && object.uri) ?
      new Message(
          object.uri,
          object.headers,
          object.body
      ) : null;
  };
  
  /**
   * Encodes a message body to a string.
   *
   * @param {*} body The value for the body.
   * @return {string} The body encoded as a string.
   */
  Message.prototype._encodeBody = function(body) {
    if (typeof body != 'string') {
      body = JSON.stringify(body);
    }
    return body;
  };
  
  /**
   * Returns the URI of the message.
   *
   * @return {Spotify.Cosmos.URI} The URI of the message.
   */
  Message.prototype.getURI = function() {
    return this._uri;
  };
  
  /**
   * Returns the mimetype of the message.
   *
   * @return {Spotify.Cosmos.MimeType} The mimetype of the message.
   */
  Message.prototype.getMimeType = function() {
    return this._headers['accept'];
  };
  
  /**
   * Returns the value of a message's headers.
   *
   * @param {string} name The name of the header.
   * @return {string|null} The header value or null if the header wasn't set.
   */
  Message.prototype.getHeader = function(name) {
    return this._headers[name.toLowerCase()] || null;
  };
  
  /**
   * Returns the headers of the message.
   *
   * @return {Spotify.Cosmos.Headers} The headers of the message.
   */
  Message.prototype.getHeaders = function() {
    var _headers = this._headers;
    var headers = {};
    for (var name in _headers) {
      if (!_headers.hasOwnProperty(name)) continue;
      headers[name] = _headers[name];
    }
    return headers;
  };
  
  /**
   * Sets a bunch of headers to the message's headers.
   *
   * @param {Spotify.Cosmos.Headers} headers The headers to set to the message.
   * @protected
   */
  Message.prototype._setHeaders = function(headers) {
    var _headers = this._headers;
    for (var name in headers) {
      if (!headers.hasOwnProperty(name)) continue;
      _headers[name.toLowerCase()] = headers[name];
    }
    return this;
  };
  
  /**
   * Returns the body of the message.
   *
   * @return {Spotify.Cosmos.Body} The body of the message.
   */
  Message.prototype.getBody = function() {
    return this._body;
  };
  
  /**
   * Returns the body as a JSON object.
   *
   * @return {Object|null} The body of the message parsed as a JSON value. Can
   *     be null if the body is not a proper JSON string.
   */
  Message.prototype.getJSONBody = function() {
    try {
      return JSON.parse(this._body);
    } catch(e) {
      return null;
    }
  };
  
  /**
   * Creates a new Message instance with data copied from the current instance.
   *
   * @param {Spotify.Cosmos.Headers=} opt_headers The optional headers of the
   *     message.
   * @param {Spotify.Cosmos.Body=} opt_body The optional body of the message.
   */
  Message.prototype.copy = function(opt_headers, opt_body) {
    return new Message(
        this._uri,
        this._copyHeaders(opt_headers),
        typeof opt_body != 'undefined' ? opt_body : this._body
    );
  };
  
  /**
   * Copies the headers of the message.
   *
   * @param {Spotify.Cosmos.Headers=} opt_headers The optional headers of the
   *     message.
   * @return {Spotify.Cosmos.Headers} The headers of the message.
   */
  Message.prototype._copyHeaders = function(opt_headers) {
    var headers;
    if (opt_headers) {
      var _headers = this._headers;
      var name;
      headers = {};
      for (name in _headers) {
        if (!_headers.hasOwnProperty(name)) continue;
        headers[name] = _headers[name];
      }
      for (name in opt_headers) {
        if (!opt_headers.hasOwnProperty(name)) continue;
        headers[name.toLowerCase()] = opt_headers[name];
      }
    } else {
      headers = this._headers;
    }
    return headers;
  };
  
  /**
   * Serializes the message into a plain object.
   *
   * @return {Spotify.Cosmos.SerializedMessage} The serialized object.
   */
  Message.prototype.serialize = function() {
    return this.toJSON();
  };
  
  /**
   * Returns a JSON-object representation of the message.
   *
   * @return {Object} The JSON representation of the message.
   */
  Message.prototype.toJSON = function() {
    return {
      uri: this._uri,
      headers: this._headers,
      body: this._body
    };
  };
  
  },{}],327:[function(require,module,exports){
  var inherit = require('spotify-inheritance').inherit;
  
  /**
   * PlayerState is used for two distinct purposes: Pushing new state to the
   * player (this is done when you request to play a completely new context with
   * 'play' method) and retrieving player state updates.
   *
   * Changing properties on PlayerState objects will not change the state of
   * the player unless you pass it to the 'play' method.
   *
   * @param {Object} stateData the data for the playerState.
   */
  function PlayerState(stateData) {
    Serializable.call(this, [
      'action',
      'context',
      'tracks',
      'index',
      'playing',
      'loading',
      'track',
      'position',
      'duration',
      'volume',
      'options',
      'play_origin',
      'next_page_url',
      'prev_page_url'
    ]);
  
    stateData = stateData || {};
    /**
     * What kind of the action player should perform.
     * It's set directly before sending the request.
     * @type {String}
     */
    this.action = stateData.action;
  
    /**
     * Spotify uri describing the context that
     * will be played e.g playlist, album or artist.
     * Example: spotify:artist:4XaUmUGjidSklcDHxv3XWf,
     * spotify:user:daftpunkofficial:playlist:5nrg0D90OlFyveVfrQD0zE,
     * spotify:album:2nXJkqkS1tIKIyhBcFMmwz
     *
     * @type {String}
     */
    this.context = stateData.context;
  
    /**
     * The list of tracks uris to play in the given context.
     * Example: [
     *    spotify:track:0bXpmJyHHYPk6QBFj25bYF,
     *    spotify:track:6DXFVsLcEvOTSrkG9G1Cb1,
     *    spotify:track:6rxEjkoar48SssZePbtb2x
     * ]
     *
     * @type {Array.<String>}
     */
    this.tracks = stateData.tracks;
  
    /**
     * Which element on the this.tracks list should
     * be played.
     *
     * @type {Number}
     */
    this.index = stateData.index;
  
    /**
     * Is the player currently playing
     */
    this.playing = stateData.playing;
    this.loading = stateData.loading;
  
    /**
     * Current track URI
     * @type {String}
     */
    this.track = stateData.track;
  
  
    this.position = stateData.position;
  
    this.volume = stateData.volume;
  
    /**
     * Current track duration in miliseconds?
     * @type {Number}
     */
    this.duration = stateData.duration;
  
    /**
     * See PlayOptions description
     * @type {cosmos-bindings-js/scripts/player_state~PlayOptions}
     */
    this.options = new PlayOptions(stateData.options);
  
    /**
     * See PlayOrigin description
     * @type {cosmos-bindings-js/scripts/player_state~PlayOrigin}
     */
    this.play_origin = new PlayOrigin(stateData.play_origin);
  
    /**
     * Before the list of tracks that are to be played ends, a request will be sent
     * to this URL, which is supposed to return a list of tracks. That list of
     * tracks will then be appended to the list of tracks in the context.
     *
     * The response payload of the next_page_url should look like this:
     *
     * {
     *   "tracks": [
     *     { "track": "spotify:track:$TRACK-ID1", "context": "spotify:album:$ALBUM-ID1" },
     *     { "track": "spotify:track:$TRACK-ID2", "context": "spotify:album:$ALBUM-ID1" },
     *     { "track": "spotify:track:$TRACK-ID3", "context": "spotify:album:$ALBUM-ID1" },
     *   ],
     *   "next_page_url": "...", // Optional
     *   "prev_page_url": "..." // Optional
     * }
     *
     * May be null, which is the same as a URL that would return an empty list
     * of tracks (but with null, no network request is made).
     */
    this.next_page_url = stateData.next_page_url;
  
    /**
     * Like `next_page_url`, but for going backwards in the context.
     */
    this.prev_page_url = stateData.prev_page_url;
  }
  inherit(PlayerState, Serializable);
  
  /**
   * Ovverides prototype method.
   * Converts play options and origin to
   * serializable to make sure only correct data
   * is returned.
   * @return {Object} Data associated with player state.
   */
  PlayerState.prototype.serialize = function() {
    if (this.options && !(this.options instanceof PlayOptions)) {
      this.options = new PlayOptions(this.options);
    }
  
    if (this.play_origin && !(this.play_origin instanceof PlayOrigin)) {
      this.play_origin = new PlayOrigin(this.play_origin);
    }
  
    return this.constructor.prototype.serialize.call(this);
  };
  
  /**
   * Possible player actions.
   */
  PlayerState.ACTIONS = {
    UNKNOWN: 'unknown',
    PLAY: 'play',
    UPDATE: 'update',
    STOP: 'stop',
    RESUME: 'resume',
    PAUSE: 'pause',
    SKIP_PREV: 'skip_prev',
    SKIP_NEXT: 'skip_next'
  };
  
  function PlayOrigin(data) {
    Serializable.call(this, [
      'source',
      'source_context',
      'reason',
      'referrer',
      'referrer_version',
      'referrer_vendor'
    ]);
  
    data = data || {};
  
    /**
     * What kind of playlist did we play from?
     *
     * The default value of this property is "unknown". Must have a value.
     *
     * Valid values include 'album', 'artist', 'extlink', 'playlist', 'playqueue',
     * 'radio', 'search', 'unknown'.
     *
     * For an up to date list of valid values, see `PLAY_SOURCES` in
     * https://git.spotify.net/cgit.cgi/log-parser.git/tree/spotify/log_parser/messages_specs.py
     */
    this.source = data.source || 'unknown';
  
    /**
     * The uri of the view that initiated the playback.
     */
    this.source_context = data.source_context || 'unknown';
  
    /**
     * Why was the song started?
     *
     * The default value of this property is "unknown". Must have a value.
     *
     * A list of valid values that might be used by features:
     *
     * unknown    = Client doesn't know
     * clickrow   = A row in the song list was clicked/opened
     * playbtn    = The play button was pressed
     * urlopen    = A Url was opened
     *
     * For a complete and up to date list of valid values, see `PLAY_REASONS` in
     * https://git.spotify.net/cgit.cgi/log-parser.git/tree/spotify/log_parser/messages_specs.py
     */
    this.reason = data.reason || 'unknown';
  
    /**
     * Either a remote site or a spotify app which initiated the request.
     *
     * NOTE: This normally should be a readonly property and not be set
     * explicitly in the PlayerState as the Player will overwrite it when sending the request.
     */
    this.referrer = data.referrer || 'unknown';
  
    /**
     * The version of the referrer, where applicable. It usually makes sense to
     * set this value to the version of the JS app version, for instance "0.7.5".
     *
     * NOTE: This normally should be a readonly property and not be set
     * explicitly in the PlayerState as the Player will overwrite it when sending the request.
     */
    this.referrer_version = data.referrer_version || 'unknown';
  
    /**
     * The vendor of the referrer, where applicable.
     * For example com.soundrop, com.spotify.
     *
     * NOTE: This normally should be a readonly property and not be set
     * explicitly in the PlayerState as the Player will overwrite it when sending the request.
     */
    this.referrer_vendor = data.referrer_vendor || 'unknown';
  }
  inherit(PlayOrigin, Serializable);
  
  /**
   * What kind of options user has
   * with the player. By default
   * all 'can_*' properties are set to true.
   * You might want to restrict some of them
   * in the special cases like ads (no skipping)
   * or radio no skipping prev.
   * @constructor
   * @param {Object} options The options data.
   */
  function PlayOptions(options) {
    Serializable.call(this, [
      'repeat',
      'shuffle',
      'can_repeat',
      'can_shuffle',
      'can_skip_prev',
      'can_skip_next',
      'can_seek',
      'use_dmca_rules'
    ]);
    options = options || {};
  
    /**
     * True if repeat (repeat all, not single track repeat) is (or is to be) enabled.
     *
     * Default value is false
     */
    this.repeat = options.repeat !== undefined ? options.repeat : false;
  
    /**
     * True if shuffle is (or is to be) enabled.
     *
     * Default value is false
     */
    this.shuffle = options.shuffle !== undefined ? options.shuffle : false;
  
    /**
     * True if this context can be repeated. This would be false for instance in the
     * case of radio.
     *
     * Default value is true.
     */
    this.can_repeat = options.can_repeat !== undefined ? options.can_repeat : true;
  
    /**
     * True if this context can be shuffled. This would be false for instance in the
     * case of radio.
     *
     * Default value is true.
     */
    this.can_shuffle = options.can_shuffle !== undefined ? options.can_shuffle : true;
  
    /**
     * True if the user is (or should be) allowed to skip to the previous track.
     *
     * Default value is true.
     */
    this.can_skip_prev = options.can_skip_prev !== undefined ? options.can_skip_prev : true;
  
    /**
     * True if the user is (or should be) allowed to skip to the next track.
     *
     * Default value is true.
     */
    this.can_skip_next = options.can_skip_next !== undefined ? options.can_skip_next : true;
  
    /**
     * True if the user is (or should be) allowed to seek to a certain time in the
     * currently playing track.
     *
     * Default value is true.
     */
    this.can_seek = options.can_seek !== undefined ? options.can_seek : true;
  
    /**
     * True if the track player should automatically apply DMCA rules when playing.
     * DMCA rules should be enabled for users that have free radio in the US, and
     * controls how many tracks the user are allowed to skip etc.
     *
     * Default value is false.
     */
    this.use_dmca_rules = options.use_dmca_rules !== undefined ? options.use_dmca_rules : false;
  }
  inherit(PlayOptions, Serializable);
  
  /**
   * The object accepting only defined properties.
   * To make sure only valid properties are passed
   * always use 'serialize()' when object value needed.
   * @constructor
   * @param {Array.<string>} allowedProps The list of the properties
   * that are supported for the object.
   */
  function Serializable(allowedProps) {
    this._props = allowedProps || [];
  }
  
  /**
   * The JSON representation of the object.
   * @return {Object} Data associated with current player state.
   */
  Serializable.prototype.serialize = function() {
    var data = {};
    var prop;
  
    for (var i = 0, l = this._props.length; i < l; i++) {
      prop = this._props[i];
      if (this[prop] !== undefined) {
        if (this[prop] instanceof Serializable) {
          data[prop] = this[prop].serialize();
        } else {
          data[prop] = this[prop];
        }
      }
    }
  
    return data;
  };
  
  exports.PlayerState = PlayerState;
  
  },{"spotify-inheritance":550}],328:[function(require,module,exports){
  var inherit = require('spotify-inheritance').inherit;
  
  var Message = require('./message').Message;
  
  /**
   * Request actions.
   *
   * @name exports.Action
   * @enum {string}
   */
  exports.Action = {
    DELETE: 'DELETE',
    GET: 'GET',
    HEAD: 'HEAD',
    POST: 'POST',
    PUT: 'PUT',
    SUB: 'SUB',
    PATCH: 'PATCH'
  };
  
  /**
   * A serialized Request object.
   *
   * @name Spotify.Cosmos.SerializedRequest
   * @typedef {{
   *   uri: Spotify.Cosmos.URI,
   *   headers: Spotify.Cosmos.Headers,
   *   body: Spotify.Cosmos.Body
   * }}
   */
  exports.SerializedRequest;
  
  /**
   * Encapsulates a request to the handlers.
   *
   * Instances of this class are "immutable" and should not be changed.
   *
   * @constructs Spotify.Cosmos.Request
   * @extends Spotify.Cosmos.Message
   * @param {Spotify.Cosmos.Action} action The action of the request.
   * @param {Spotify.Cosmos.URI} uri The URI of the request
   * @param {Spotify.Cosmos.Headers=} opt_headers The optional headers of the
   *     request.
   * @param {Spotify.Cosmos.Body=} opt_body The optional body of the request.
   */
  function Request(action, uri, opt_headers, opt_body) {
    if (!(this instanceof Request))
      return new Request(action, uri, opt_headers, opt_body);
    if (!action)
      throw new TypeError('Invalid `action` argument for Request.');
    Message.call(this, uri, opt_headers, opt_body);
  
    /**
     * The action of the request.
     *
     * @type {Spotify.Cosmos.Action}
     * @protected
     */
    this._action = action;
  }
  inherit(Request, Message);
  exports.Request = Request;
  
  /**
   * Creates a new Request from a SerializedRequest object.
   *
   * @param {Spotify.Cosmos.SerializedRequest} object The serialized request.
   * @return {Spotify.Cosmos.Request|null} The new Request object or null.
   */
  Request.fromObject = function(object) {
    return (object && object.action && object.uri) ?
      new Request(
          object.action,
          object.uri,
          object.headers,
          object.body
      ) : null;
  };
  
  /**
   * Returns the action of the request.
   *
   * @return {Spotify.Cosmos.Action} The action of the request.
   */
  Request.prototype.getAction = function() {
    return this._action;
  };
  
  /**
   * @inheritDoc
   */
  Request.prototype.copy = function(opt_headers, opt_body) {
    return new Request(
        this._action,
        this._uri,
        this._copyHeaders(opt_headers),
        typeof opt_body != 'undefined' ? opt_body : this._body
    );
  };
  
  /**
   * @inheritDoc
   */
  Request.prototype.toJSON = function() {
    return {
      action: this._action,
      uri: this._uri,
      headers: this._headers,
      body: this._body
    };
  };
  
  
  
  },{"./message":326,"spotify-inheritance":550}],329:[function(require,module,exports){
  var inherit = require('spotify-inheritance').inherit;
  
  var Message = require('./message').Message;
  
  /**
   * Statuscode.
   * The statuses with negative numbers are reserved for
   * errors that originate within the cosmos library.
   *
   * @name exports.StatusCode
   * @enum {number}
   */
  exports.StatusCode = {
    OK: 200,
    CREATED: 201,
    ACCEPTED: 202,
    BAD_REQUEST: 400,
    UNAUTHORIZED: 401,
    FORBIDDEN: 403,
    NOT_FOUND: 404,
    METHOD_NOT_ALLOWED: 405,
    TIMED_OUT: 408,
    CONFLICT: 409,
    GONE: 410,
    INTERNAL_SERVER_ERROR: 500,
    NOT_IMPLEMENTED: 501,
    BAD_GATEWAY: 502,
    SERVICE_UNAVAILABLE: 503,
  
    /** Something went wrong, but the exact reason is not known. */
    ERROR_UNKNOWN: -100,
  
    /** A resource allocation required to complete the request failed. */
    ERROR_ALLOCATION_FAILED: -101,
  
    /** The body could not be decoded because it does
     not conform to the encoding header field. */
    ERROR_INVALID_ENCODING: -102,
  
    /** The router detected an infinite loop while processing the request. */
    ERROR_INFINITE_LOOP: -103,
  
    /** No endpoint resolver that could handle the request was found. */
    ERROR_RESOLVER_NOT_FOUND: -104
  };
  
  /**
   * A serialized Response object.
   *
   * @name Spotify.Cosmos.SerializedResponse
   * @typedef {{
   *   uri: Spotify.Cosmos.URI,
   *   status: Spotify.Cosmos.StatusCode,
   *   headers: Spotify.Cosmos.Headers,
   *   body: Spotify.Cosmos.Body
   * }}
   */
  exports.SerializedResponse;
  
  /**
   * Encapsulates a response from the handlers.
   *
   * @constructs Spotify.Cosmos.Response
   * @extends Spotify.Cosmos.Message
   * @param {Spotify.Cosmos.URI} uri The URI of the response
   * @param {Spotify.Cosmos.StatusCode} status The status of the response.
   * @param {Spotify.Cosmos.Headers=} opt_headers The optional headers of the
   *     response.
   * @param {Spotify.Cosmos.Body=} opt_body The optional body of the response.
   */
  function Response(uri, status, opt_headers, opt_body) {
    if (!(this instanceof Response))
      return new Response(uri, status, opt_headers, opt_body, opt_requestURI);
    if (typeof status == 'undefined' || status == null)
      throw new TypeError('Invalid `status` argument for Response.');
  
    Message.call(this, uri, opt_headers, opt_body);
  
    /**
     * The Status of the Response.
     *
     * @type {Spotify.Cosmos.StatusCode}
     * @protected
     */
    this._status = status;
  }
  inherit(Response, Message);
  exports.Response = Response;
  
  /**
   * Creates a new Response from a SerializedResponse object.
   *
   * @param {Spotify.Cosmos.SerializedResponse} object The serialized response.
   * @return {Spotify.Cosmos.Response|null} The new Response object or null.
   */
  Response.fromObject = function(object) {
    return (object && object.uri && object.status) ?
      new Response(
          object.uri,
          object.status,
          object.headers,
          object.body
      ) : null;
  };
  
  /**
   * @inheritDoc
   */
  Response.prototype.getMimeType = function() {
    return this._headers['content-type'];
  };
  
  /**
   * Returns the status code of the Response.
   *
   * @return {Spotify.Cosmos.StatusCode} The status code of the response.
   */
  Response.prototype.getStatusCode = function() {
    return this._status;
  };
  
  /**
   * @inheritDoc
   */
  Response.prototype.copy = function(opt_headers, opt_body) {
    return new Response(
        this._uri,
        this._status,
        this._copyHeaders(opt_headers),
        typeof opt_body != 'undefined' ? opt_body : this._body
    );
  };
  
  /**
   * @inheritDoc
   */
  Response.prototype.toJSON = function() {
    return {
      uri: this._uri,
      status: this._status,
      headers: this._headers,
      body: this._body
    };
  };
  
  
  },{"./message":326,"spotify-inheritance":550}],330:[function(require,module,exports){
  (function (process){
  /**
   * This is the web browser implementation of `debug()`.
   *
   * Expose `debug()` as the module.
   */
  
  exports = module.exports = require('./debug');
  exports.log = log;
  exports.formatArgs = formatArgs;
  exports.save = save;
  exports.load = load;
  exports.useColors = useColors;
  exports.storage = 'undefined' != typeof chrome
                 && 'undefined' != typeof chrome.storage
                    ? chrome.storage.local
                    : localstorage();
  
  /**
   * Colors.
   */
  
  exports.colors = [
    'lightseagreen',
    'forestgreen',
    'goldenrod',
    'dodgerblue',
    'darkorchid',
    'crimson'
  ];
  
  /**
   * Currently only WebKit-based Web Inspectors, Firefox >= v31,
   * and the Firebug extension (any Firefox version) are known
   * to support "%c" CSS customizations.
   *
   * TODO: add a `localStorage` variable to explicitly enable/disable colors
   */
  
  function useColors() {
    // NB: In an Electron preload script, document will be defined but not fully
    // initialized. Since we know we're in Chrome, we'll just detect this case
    // explicitly
    if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') {
      return true;
    }
  
    // is webkit? http://stackoverflow.com/a/16459606/376773
    // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
    return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) ||
      // is firebug? http://stackoverflow.com/a/398120/376773
      (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||
      // is firefox >= v31?
      // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
      (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) ||
      // double check webkit in userAgent just in case we are in a worker
      (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/));
  }
  
  /**
   * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
   */
  
  exports.formatters.j = function(v) {
    try {
      return JSON.stringify(v);
    } catch (err) {
      return '[UnexpectedJSONParseError]: ' + err.message;
    }
  };
  
  
  /**
   * Colorize log arguments if enabled.
   *
   * @api public
   */
  
  function formatArgs(args) {
    var useColors = this.useColors;
  
    args[0] = (useColors ? '%c' : '')
      + this.namespace
      + (useColors ? ' %c' : ' ')
      + args[0]
      + (useColors ? '%c ' : ' ')
      + '+' + exports.humanize(this.diff);
  
    if (!useColors) return;
  
    var c = 'color: ' + this.color;
    args.splice(1, 0, c, 'color: inherit')
  
    // the final "%c" is somewhat tricky, because there could be other
    // arguments passed either before or after the %c, so we need to
    // figure out the correct index to insert the CSS into
    var index = 0;
    var lastC = 0;
    args[0].replace(/%[a-zA-Z%]/g, function(match) {
      if ('%%' === match) return;
      index++;
      if ('%c' === match) {
        // we only are interested in the *last* %c
        // (the user may have provided their own)
        lastC = index;
      }
    });
  
    args.splice(lastC, 0, c);
  }
  
  /**
   * Invokes `console.log()` when available.
   * No-op when `console.log` is not a "function".
   *
   * @api public
   */
  
  function log() {
    // this hackery is required for IE8/9, where
    // the `console.log` function doesn't have 'apply'
    return 'object' === typeof console
      && console.log
      && Function.prototype.apply.call(console.log, console, arguments);
  }
  
  /**
   * Save `namespaces`.
   *
   * @param {String} namespaces
   * @api private
   */
  
  function save(namespaces) {
    try {
      if (null == namespaces) {
        exports.storage.removeItem('debug');
      } else {
        exports.storage.debug = namespaces;
      }
    } catch(e) {}
  }
  
  /**
   * Load `namespaces`.
   *
   * @return {String} returns the previously persisted debug modes
   * @api private
   */
  
  function load() {
    var r;
    try {
      r = exports.storage.debug;
    } catch(e) {}
  
    // If debug isn't set in LS, and we're in Electron, try to load $DEBUG
    if (!r && typeof process !== 'undefined' && 'env' in process) {
      r = process.env.DEBUG;
    }
  
    return r;
  }
  
  /**
   * Enable namespaces listed in `localStorage.debug` initially.
   */
  
  exports.enable(load());
  
  /**
   * Localstorage attempts to return the localstorage.
   *
   * This is necessary because safari throws
   * when a user disables cookies/localstorage
   * and you attempt to access it.
   *
   * @return {LocalStorage}
   * @api private
   */
  
  function localstorage() {
    try {
      return window.localStorage;
    } catch (e) {}
  }
  
  }).call(this,require('_process'))
  },{"./debug":331,"_process":481}],331:[function(require,module,exports){
  
  /**
   * This is the common logic for both the Node.js and web browser
   * implementations of `debug()`.
   *
   * Expose `debug()` as the module.
   */
  
  exports = module.exports = createDebug.debug = createDebug['default'] = createDebug;
  exports.coerce = coerce;
  exports.disable = disable;
  exports.enable = enable;
  exports.enabled = enabled;
  exports.humanize = require('ms');
  
  /**
   * The currently active debug mode names, and names to skip.
   */
  
  exports.names = [];
  exports.skips = [];
  
  /**
   * Map of special "%n" handling functions, for the debug "format" argument.
   *
   * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
   */
  
  exports.formatters = {};
  
  /**
   * Previous log timestamp.
   */
  
  var prevTime;
  
  /**
   * Select a color.
   * @param {String} namespace
   * @return {Number}
   * @api private
   */
  
  function selectColor(namespace) {
    var hash = 0, i;
  
    for (i in namespace) {
      hash  = ((hash << 5) - hash) + namespace.charCodeAt(i);
      hash |= 0; // Convert to 32bit integer
    }
  
    return exports.colors[Math.abs(hash) % exports.colors.length];
  }
  
  /**
   * Create a debugger with the given `namespace`.
   *
   * @param {String} namespace
   * @return {Function}
   * @api public
   */
  
  function createDebug(namespace) {
  
    function debug() {
      // disabled?
      if (!debug.enabled) return;
  
      var self = debug;
  
      // set `diff` timestamp
      var curr = +new Date();
      var ms = curr - (prevTime || curr);
      self.diff = ms;
      self.prev = prevTime;
      self.curr = curr;
      prevTime = curr;
  
      // turn the `arguments` into a proper Array
      var args = new Array(arguments.length);
      for (var i = 0; i < args.length; i++) {
        args[i] = arguments[i];
      }
  
      args[0] = exports.coerce(args[0]);
  
      if ('string' !== typeof args[0]) {
        // anything else let's inspect with %O
        args.unshift('%O');
      }
  
      // apply any `formatters` transformations
      var index = 0;
      args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) {
        // if we encounter an escaped % then don't increase the array index
        if (match === '%%') return match;
        index++;
        var formatter = exports.formatters[format];
        if ('function' === typeof formatter) {
          var val = args[index];
          match = formatter.call(self, val);
  
          // now we need to remove `args[index]` since it's inlined in the `format`
          args.splice(index, 1);
          index--;
        }
        return match;
      });
  
      // apply env-specific formatting (colors, etc.)
      exports.formatArgs.call(self, args);
  
      var logFn = debug.log || exports.log || console.log.bind(console);
      logFn.apply(self, args);
    }
  
    debug.namespace = namespace;
    debug.enabled = exports.enabled(namespace);
    debug.useColors = exports.useColors();
    debug.color = selectColor(namespace);
  
    // env-specific initialization logic for debug instances
    if ('function' === typeof exports.init) {
      exports.init(debug);
    }
  
    return debug;
  }
  
  /**
   * Enables a debug mode by namespaces. This can include modes
   * separated by a colon and wildcards.
   *
   * @param {String} namespaces
   * @api public
   */
  
  function enable(namespaces) {
    exports.save(namespaces);
  
    exports.names = [];
    exports.skips = [];
  
    var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/);
    var len = split.length;
  
    for (var i = 0; i < len; i++) {
      if (!split[i]) continue; // ignore empty strings
      namespaces = split[i].replace(/\*/g, '.*?');
      if (namespaces[0] === '-') {
        exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
      } else {
        exports.names.push(new RegExp('^' + namespaces + '$'));
      }
    }
  }
  
  /**
   * Disable debug output.
   *
   * @api public
   */
  
  function disable() {
    exports.enable('');
  }
  
  /**
   * Returns true if the given mode name is enabled, false otherwise.
   *
   * @param {String} name
   * @return {Boolean}
   * @api public
   */
  
  function enabled(name) {
    var i, len;
    for (i = 0, len = exports.skips.length; i < len; i++) {
      if (exports.skips[i].test(name)) {
        return false;
      }
    }
    for (i = 0, len = exports.names.length; i < len; i++) {
      if (exports.names[i].test(name)) {
        return true;
      }
    }
    return false;
  }
  
  /**
   * Coerce `val`.
   *
   * @param {Mixed} val
   * @return {Mixed}
   * @api private
   */
  
  function coerce(val) {
    if (val instanceof Error) return val.stack || val.message;
    return val;
  }
  
  },{"ms":479}],332:[function(require,module,exports){
  var hasOwn = Object.prototype.hasOwnProperty;
  var toStr = Object.prototype.toString;
  var undefined;
  
  var isArray = function isArray(arr) {
    if (typeof Array.isArray === 'function') {
      return Array.isArray(arr);
    }
  
    return toStr.call(arr) === '[object Array]';
  };
  
  var isPlainObject = function isPlainObject(obj) {
    'use strict';
    if (!obj || toStr.call(obj) !== '[object Object]') {
      return false;
    }
  
    var has_own_constructor = hasOwn.call(obj, 'constructor');
    var has_is_property_of_method = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');
    // Not own constructor property must be Object
    if (obj.constructor && !has_own_constructor && !has_is_property_of_method) {
      return false;
    }
  
    // Own properties are enumerated firstly, so to speed up,
    // if last one is own, then all properties are own.
    var key;
    for (key in obj) {}
  
    return key === undefined || hasOwn.call(obj, key);
  };
  
  module.exports = function extend() {
    'use strict';
    var options, name, src, copy, copyIsArray, clone,
      target = arguments[0],
      i = 1,
      length = arguments.length,
      deep = false;
  
    // Handle a deep copy situation
    if (typeof target === 'boolean') {
      deep = target;
      target = arguments[1] || {};
      // skip the boolean and the target
      i = 2;
    } else if ((typeof target !== 'object' && typeof target !== 'function') || target == null) {
      target = {};
    }
  
    for (; i < length; ++i) {
      options = arguments[i];
      // Only deal with non-null/undefined values
      if (options != null) {
        // Extend the base object
        for (name in options) {
          src = target[name];
          copy = options[name];
  
          // Prevent never-ending loop
          if (target === copy) {
            continue;
          }
  
          // Recurse if we're merging plain objects or arrays
          if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
            if (copyIsArray) {
              copyIsArray = false;
              clone = src && isArray(src) ? src : [];
            } else {
              clone = src && isPlainObject(src) ? src : {};
            }
  
            // Never move original objects, clone them
            target[name] = extend(deep, clone, copy);
  
          // Don't bring in undefined values
          } else if (copy !== undefined) {
            target[name] = copy;
          }
        }
      }
    }
  
    // Return the modified object
    return target;
  };
  
  
  },{}],333:[function(require,module,exports){
  'use strict';
  
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @typechecks
   */
  
  var emptyFunction = require('./emptyFunction');
  
  /**
   * Upstream version of event listener. Does not take into account specific
   * nature of platform.
   */
  var EventListener = {
    /**
     * Listen to DOM events during the bubble phase.
     *
     * @param {DOMEventTarget} target DOM element to register listener on.
     * @param {string} eventType Event type, e.g. 'click' or 'mouseover'.
     * @param {function} callback Callback function.
     * @return {object} Object with a `remove` method.
     */
    listen: function listen(target, eventType, callback) {
      if (target.addEventListener) {
        target.addEventListener(eventType, callback, false);
        return {
          remove: function remove() {
            target.removeEventListener(eventType, callback, false);
          }
        };
      } else if (target.attachEvent) {
        target.attachEvent('on' + eventType, callback);
        return {
          remove: function remove() {
            target.detachEvent('on' + eventType, callback);
          }
        };
      }
    },
  
    /**
     * Listen to DOM events during the capture phase.
     *
     * @param {DOMEventTarget} target DOM element to register listener on.
     * @param {string} eventType Event type, e.g. 'click' or 'mouseover'.
     * @param {function} callback Callback function.
     * @return {object} Object with a `remove` method.
     */
    capture: function capture(target, eventType, callback) {
      if (target.addEventListener) {
        target.addEventListener(eventType, callback, true);
        return {
          remove: function remove() {
            target.removeEventListener(eventType, callback, true);
          }
        };
      } else {
        if ("production" !== 'production') {
          console.error('Attempted to listen to events during the capture phase on a ' + 'browser that does not support the capture phase. Your application ' + 'will not receive some events.');
        }
        return {
          remove: emptyFunction
        };
      }
    },
  
    registerDefault: function registerDefault() {}
  };
  
  module.exports = EventListener;
  },{"./emptyFunction":338}],334:[function(require,module,exports){
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   */
  
  'use strict';
  
  var canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement);
  
  /**
   * Simple, lightweight module assisting with the detection and context of
   * Worker. Helps avoid circular dependencies and allows code to reason about
   * whether or not they are in a Worker, even if they never include the main
   * `ReactWorker` dependency.
   */
  var ExecutionEnvironment = {
  
    canUseDOM: canUseDOM,
  
    canUseWorkers: typeof Worker !== 'undefined',
  
    canUseEventListeners: canUseDOM && !!(window.addEventListener || window.attachEvent),
  
    canUseViewport: canUseDOM && !!window.screen,
  
    isInWorker: !canUseDOM // For now, this is true - might change in the future.
  
  };
  
  module.exports = ExecutionEnvironment;
  },{}],335:[function(require,module,exports){
  "use strict";
  
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @typechecks
   */
  
  var _hyphenPattern = /-(.)/g;
  
  /**
   * Camelcases a hyphenated string, for example:
   *
   *   > camelize('background-color')
   *   < "backgroundColor"
   *
   * @param {string} string
   * @return {string}
   */
  function camelize(string) {
    return string.replace(_hyphenPattern, function (_, character) {
      return character.toUpperCase();
    });
  }
  
  module.exports = camelize;
  },{}],336:[function(require,module,exports){
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @typechecks
   */
  
  'use strict';
  
  var camelize = require('./camelize');
  
  var msPattern = /^-ms-/;
  
  /**
   * Camelcases a hyphenated CSS property name, for example:
   *
   *   > camelizeStyleName('background-color')
   *   < "backgroundColor"
   *   > camelizeStyleName('-moz-transition')
   *   < "MozTransition"
   *   > camelizeStyleName('-ms-transition')
   *   < "msTransition"
   *
   * As Andi Smith suggests
   * (http://www.andismith.com/blog/2012/02/modernizr-prefixed/), an `-ms` prefix
   * is converted to lowercase `ms`.
   *
   * @param {string} string
   * @return {string}
   */
  function camelizeStyleName(string) {
    return camelize(string.replace(msPattern, 'ms-'));
  }
  
  module.exports = camelizeStyleName;
  },{"./camelize":335}],337:[function(require,module,exports){
  'use strict';
  
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * 
   */
  
  var isTextNode = require('./isTextNode');
  
  /*eslint-disable no-bitwise */
  
  /**
   * Checks if a given DOM node contains or is another DOM node.
   */
  function containsNode(outerNode, innerNode) {
    if (!outerNode || !innerNode) {
      return false;
    } else if (outerNode === innerNode) {
      return true;
    } else if (isTextNode(outerNode)) {
      return false;
    } else if (isTextNode(innerNode)) {
      return containsNode(outerNode, innerNode.parentNode);
    } else if ('contains' in outerNode) {
      return outerNode.contains(innerNode);
    } else if (outerNode.compareDocumentPosition) {
      return !!(outerNode.compareDocumentPosition(innerNode) & 16);
    } else {
      return false;
    }
  }
  
  module.exports = containsNode;
  },{"./isTextNode":346}],338:[function(require,module,exports){
  "use strict";
  
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * 
   */
  
  function makeEmptyFunction(arg) {
    return function () {
      return arg;
    };
  }
  
  /**
   * This function accepts and discards inputs; it has no side effects. This is
   * primarily useful idiomatically for overridable function endpoints which
   * always need to be callable, since JS lacks a null-call idiom ala Cocoa.
   */
  var emptyFunction = function emptyFunction() {};
  
  emptyFunction.thatReturns = makeEmptyFunction;
  emptyFunction.thatReturnsFalse = makeEmptyFunction(false);
  emptyFunction.thatReturnsTrue = makeEmptyFunction(true);
  emptyFunction.thatReturnsNull = makeEmptyFunction(null);
  emptyFunction.thatReturnsThis = function () {
    return this;
  };
  emptyFunction.thatReturnsArgument = function (arg) {
    return arg;
  };
  
  module.exports = emptyFunction;
  },{}],339:[function(require,module,exports){
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   */
  
  'use strict';
  
  var emptyObject = {};
  
  if ("production" !== 'production') {
    Object.freeze(emptyObject);
  }
  
  module.exports = emptyObject;
  },{}],340:[function(require,module,exports){
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   */
  
  'use strict';
  
  /**
   * @param {DOMElement} node input/textarea to focus
   */
  
  function focusNode(node) {
    // IE8 can throw "Can't move focus to the control because it is invisible,
    // not enabled, or of a type that does not accept the focus." for all kinds of
    // reasons that are too expensive and fragile to test.
    try {
      node.focus();
    } catch (e) {}
  }
  
  module.exports = focusNode;
  },{}],341:[function(require,module,exports){
  'use strict';
  
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @typechecks
   */
  
  /* eslint-disable fb-www/typeof-undefined */
  
  /**
   * Same as document.activeElement but wraps in a try-catch block. In IE it is
   * not safe to call document.activeElement if there is nothing focused.
   *
   * The activeElement will be null only if the document or document body is not
   * yet defined.
   *
   * @param {?DOMDocument} doc Defaults to current document.
   * @return {?DOMElement}
   */
  function getActiveElement(doc) /*?DOMElement*/{
    doc = doc || (typeof document !== 'undefined' ? document : undefined);
    if (typeof doc === 'undefined') {
      return null;
    }
    try {
      return doc.activeElement || doc.body;
    } catch (e) {
      return doc.body;
    }
  }
  
  module.exports = getActiveElement;
  },{}],342:[function(require,module,exports){
  'use strict';
  
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @typechecks
   */
  
  var _uppercasePattern = /([A-Z])/g;
  
  /**
   * Hyphenates a camelcased string, for example:
   *
   *   > hyphenate('backgroundColor')
   *   < "background-color"
   *
   * For CSS style names, use `hyphenateStyleName` instead which works properly
   * with all vendor prefixes, including `ms`.
   *
   * @param {string} string
   * @return {string}
   */
  function hyphenate(string) {
    return string.replace(_uppercasePattern, '-$1').toLowerCase();
  }
  
  module.exports = hyphenate;
  },{}],343:[function(require,module,exports){
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @typechecks
   */
  
  'use strict';
  
  var hyphenate = require('./hyphenate');
  
  var msPattern = /^ms-/;
  
  /**
   * Hyphenates a camelcased CSS property name, for example:
   *
   *   > hyphenateStyleName('backgroundColor')
   *   < "background-color"
   *   > hyphenateStyleName('MozTransition')
   *   < "-moz-transition"
   *   > hyphenateStyleName('msTransition')
   *   < "-ms-transition"
   *
   * As Modernizr suggests (http://modernizr.com/docs/#prefixed), an `ms` prefix
   * is converted to `-ms-`.
   *
   * @param {string} string
   * @return {string}
   */
  function hyphenateStyleName(string) {
    return hyphenate(string).replace(msPattern, '-ms-');
  }
  
  module.exports = hyphenateStyleName;
  },{"./hyphenate":342}],344:[function(require,module,exports){
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   */
  
  'use strict';
  
  /**
   * Use invariant() to assert state which your program assumes to be true.
   *
   * Provide sprintf-style format (only %s is supported) and arguments
   * to provide information about what broke and what you were
   * expecting.
   *
   * The invariant message will be stripped in production, but the invariant
   * will remain to ensure logic does not differ in production.
   */
  
  var validateFormat = function validateFormat(format) {};
  
  if ("production" !== 'production') {
    validateFormat = function validateFormat(format) {
      if (format === undefined) {
        throw new Error('invariant requires an error message argument');
      }
    };
  }
  
  function invariant(condition, format, a, b, c, d, e, f) {
    validateFormat(format);
  
    if (!condition) {
      var error;
      if (format === undefined) {
        error = new Error('Minified exception occurred; use the non-minified dev environment ' + 'for the full error message and additional helpful warnings.');
      } else {
        var args = [a, b, c, d, e, f];
        var argIndex = 0;
        error = new Error(format.replace(/%s/g, function () {
          return args[argIndex++];
        }));
        error.name = 'Invariant Violation';
      }
  
      error.framesToPop = 1; // we don't care about invariant's own frame
      throw error;
    }
  }
  
  module.exports = invariant;
  },{}],345:[function(require,module,exports){
  'use strict';
  
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @typechecks
   */
  
  /**
   * @param {*} object The object to check.
   * @return {boolean} Whether or not the object is a DOM node.
   */
  function isNode(object) {
    var doc = object ? object.ownerDocument || object : document;
    var defaultView = doc.defaultView || window;
    return !!(object && (typeof defaultView.Node === 'function' ? object instanceof defaultView.Node : typeof object === 'object' && typeof object.nodeType === 'number' && typeof object.nodeName === 'string'));
  }
  
  module.exports = isNode;
  },{}],346:[function(require,module,exports){
  'use strict';
  
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @typechecks
   */
  
  var isNode = require('./isNode');
  
  /**
   * @param {*} object The object to check.
   * @return {boolean} Whether or not the object is a DOM text node.
   */
  function isTextNode(object) {
    return isNode(object) && object.nodeType == 3;
  }
  
  module.exports = isTextNode;
  },{"./isNode":345}],347:[function(require,module,exports){
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @typechecks
   * 
   */
  
  /*eslint-disable no-self-compare */
  
  'use strict';
  
  var hasOwnProperty = Object.prototype.hasOwnProperty;
  
  /**
   * inlined Object.is polyfill to avoid requiring consumers ship their own
   * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
   */
  function is(x, y) {
    // SameValue algorithm
    if (x === y) {
      // Steps 1-5, 7-10
      // Steps 6.b-6.e: +0 != -0
      // Added the nonzero y check to make Flow happy, but it is redundant
      return x !== 0 || y !== 0 || 1 / x === 1 / y;
    } else {
      // Step 6.a: NaN == NaN
      return x !== x && y !== y;
    }
  }
  
  /**
   * Performs equality by iterating through keys on an object and returning false
   * when any key has values which are not strictly equal between the arguments.
   * Returns true when the values of all keys are strictly equal.
   */
  function shallowEqual(objA, objB) {
    if (is(objA, objB)) {
      return true;
    }
  
    if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
      return false;
    }
  
    var keysA = Object.keys(objA);
    var keysB = Object.keys(objB);
  
    if (keysA.length !== keysB.length) {
      return false;
    }
  
    // Test for A's keys different from B.
    for (var i = 0; i < keysA.length; i++) {
      if (!hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
        return false;
      }
    }
  
    return true;
  }
  
  module.exports = shallowEqual;
  },{}],348:[function(require,module,exports){
  /**
   * Copyright (c) 2014-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   */
  
  'use strict';
  
  var emptyFunction = require('./emptyFunction');
  
  /**
   * Similar to invariant but only logs a warning if the condition is not met.
   * This can be used to log issues in development environments in critical
   * paths. Removing the logging code for production environments will keep the
   * same logic and follow the same code paths.
   */
  
  var warning = emptyFunction;
  
  if ("production" !== 'production') {
    var printWarning = function printWarning(format) {
      for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
        args[_key - 1] = arguments[_key];
      }
  
      var argIndex = 0;
      var message = 'Warning: ' + format.replace(/%s/g, function () {
        return args[argIndex++];
      });
      if (typeof console !== 'undefined') {
        console.error(message);
      }
      try {
        // --- Welcome to debugging React ---
        // This error was thrown as a convenience so that you can use this stack
        // to find the callsite that caused this warning to fire.
        throw new Error(message);
      } catch (x) {}
    };
  
    warning = function warning(condition, format) {
      if (format === undefined) {
        throw new Error('`warning(condition, format, ...args)` requires a warning ' + 'message argument');
      }
  
      if (format.indexOf('Failed Composite propType: ') === 0) {
        return; // Ignore CompositeComponent proptype check.
      }
  
      if (!condition) {
        for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
          args[_key2 - 2] = arguments[_key2];
        }
  
        printWarning.apply(undefined, [format].concat(args));
      }
    };
  }
  
  module.exports = warning;
  },{"./emptyFunction":338}],349:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  // istanbul ignore next
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  
  // istanbul ignore next
  
  function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
  
  var _handlebarsBase = require('./handlebars/base');
  
  var base = _interopRequireWildcard(_handlebarsBase);
  
  // Each of these augment the Handlebars object. No need to setup here.
  // (This is done to easily share code between commonjs and browse envs)
  
  var _handlebarsSafeString = require('./handlebars/safe-string');
  
  var _handlebarsSafeString2 = _interopRequireDefault(_handlebarsSafeString);
  
  var _handlebarsException = require('./handlebars/exception');
  
  var _handlebarsException2 = _interopRequireDefault(_handlebarsException);
  
  var _handlebarsUtils = require('./handlebars/utils');
  
  var Utils = _interopRequireWildcard(_handlebarsUtils);
  
  var _handlebarsRuntime = require('./handlebars/runtime');
  
  var runtime = _interopRequireWildcard(_handlebarsRuntime);
  
  var _handlebarsNoConflict = require('./handlebars/no-conflict');
  
  var _handlebarsNoConflict2 = _interopRequireDefault(_handlebarsNoConflict);
  
  // For compatibility and usage outside of module systems, make the Handlebars object a namespace
  function create() {
    var hb = new base.HandlebarsEnvironment();
  
    Utils.extend(hb, base);
    hb.SafeString = _handlebarsSafeString2['default'];
    hb.Exception = _handlebarsException2['default'];
    hb.Utils = Utils;
    hb.escapeExpression = Utils.escapeExpression;
  
    hb.VM = runtime;
    hb.template = function (spec) {
      return runtime.template(spec, hb);
    };
  
    return hb;
  }
  
  var inst = create();
  inst.create = create;
  
  _handlebarsNoConflict2['default'](inst);
  
  inst['default'] = inst;
  
  exports['default'] = inst;
  module.exports = exports['default'];
  
  
  },{"./handlebars/base":350,"./handlebars/exception":353,"./handlebars/no-conflict":363,"./handlebars/runtime":364,"./handlebars/safe-string":365,"./handlebars/utils":366}],350:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.HandlebarsEnvironment = HandlebarsEnvironment;
  // istanbul ignore next
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  
  var _utils = require('./utils');
  
  var _exception = require('./exception');
  
  var _exception2 = _interopRequireDefault(_exception);
  
  var _helpers = require('./helpers');
  
  var _decorators = require('./decorators');
  
  var _logger = require('./logger');
  
  var _logger2 = _interopRequireDefault(_logger);
  
  var VERSION = '4.0.11';
  exports.VERSION = VERSION;
  var COMPILER_REVISION = 7;
  
  exports.COMPILER_REVISION = COMPILER_REVISION;
  var REVISION_CHANGES = {
    1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
    2: '== 1.0.0-rc.3',
    3: '== 1.0.0-rc.4',
    4: '== 1.x.x',
    5: '== 2.0.0-alpha.x',
    6: '>= 2.0.0-beta.1',
    7: '>= 4.0.0'
  };
  
  exports.REVISION_CHANGES = REVISION_CHANGES;
  var objectType = '[object Object]';
  
  function HandlebarsEnvironment(helpers, partials, decorators) {
    this.helpers = helpers || {};
    this.partials = partials || {};
    this.decorators = decorators || {};
  
    _helpers.registerDefaultHelpers(this);
    _decorators.registerDefaultDecorators(this);
  }
  
  HandlebarsEnvironment.prototype = {
    constructor: HandlebarsEnvironment,
  
    logger: _logger2['default'],
    log: _logger2['default'].log,
  
    registerHelper: function registerHelper(name, fn) {
      if (_utils.toString.call(name) === objectType) {
        if (fn) {
          throw new _exception2['default']('Arg not supported with multiple helpers');
        }
        _utils.extend(this.helpers, name);
      } else {
        this.helpers[name] = fn;
      }
    },
    unregisterHelper: function unregisterHelper(name) {
      delete this.helpers[name];
    },
  
    registerPartial: function registerPartial(name, partial) {
      if (_utils.toString.call(name) === objectType) {
        _utils.extend(this.partials, name);
      } else {
        if (typeof partial === 'undefined') {
          throw new _exception2['default']('Attempting to register a partial called "' + name + '" as undefined');
        }
        this.partials[name] = partial;
      }
    },
    unregisterPartial: function unregisterPartial(name) {
      delete this.partials[name];
    },
  
    registerDecorator: function registerDecorator(name, fn) {
      if (_utils.toString.call(name) === objectType) {
        if (fn) {
          throw new _exception2['default']('Arg not supported with multiple decorators');
        }
        _utils.extend(this.decorators, name);
      } else {
        this.decorators[name] = fn;
      }
    },
    unregisterDecorator: function unregisterDecorator(name) {
      delete this.decorators[name];
    }
  };
  
  var log = _logger2['default'].log;
  
  exports.log = log;
  exports.createFrame = _utils.createFrame;
  exports.logger = _logger2['default'];
  
  
  },{"./decorators":351,"./exception":353,"./helpers":354,"./logger":362,"./utils":366}],351:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.registerDefaultDecorators = registerDefaultDecorators;
  // istanbul ignore next
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  
  var _decoratorsInline = require('./decorators/inline');
  
  var _decoratorsInline2 = _interopRequireDefault(_decoratorsInline);
  
  function registerDefaultDecorators(instance) {
    _decoratorsInline2['default'](instance);
  }
  
  
  },{"./decorators/inline":352}],352:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  
  var _utils = require('../utils');
  
  exports['default'] = function (instance) {
    instance.registerDecorator('inline', function (fn, props, container, options) {
      var ret = fn;
      if (!props.partials) {
        props.partials = {};
        ret = function (context, options) {
          // Create a new partials stack frame prior to exec.
          var original = container.partials;
          container.partials = _utils.extend({}, original, props.partials);
          var ret = fn(context, options);
          container.partials = original;
          return ret;
        };
      }
  
      props.partials[options.args[0]] = options.fn;
  
      return ret;
    });
  };
  
  module.exports = exports['default'];
  
  
  },{"../utils":366}],353:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  
  var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
  
  function Exception(message, node) {
    var loc = node && node.loc,
        line = undefined,
        column = undefined;
    if (loc) {
      line = loc.start.line;
      column = loc.start.column;
  
      message += ' - ' + line + ':' + column;
    }
  
    var tmp = Error.prototype.constructor.call(this, message);
  
    // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
    for (var idx = 0; idx < errorProps.length; idx++) {
      this[errorProps[idx]] = tmp[errorProps[idx]];
    }
  
    /* istanbul ignore else */
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, Exception);
    }
  
    try {
      if (loc) {
        this.lineNumber = line;
  
        // Work around issue under safari where we can't directly set the column value
        /* istanbul ignore next */
        if (Object.defineProperty) {
          Object.defineProperty(this, 'column', {
            value: column,
            enumerable: true
          });
        } else {
          this.column = column;
        }
      }
    } catch (nop) {
      /* Ignore if the browser is very particular */
    }
  }
  
  Exception.prototype = new Error();
  
  exports['default'] = Exception;
  module.exports = exports['default'];
  
  
  },{}],354:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.registerDefaultHelpers = registerDefaultHelpers;
  // istanbul ignore next
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  
  var _helpersBlockHelperMissing = require('./helpers/block-helper-missing');
  
  var _helpersBlockHelperMissing2 = _interopRequireDefault(_helpersBlockHelperMissing);
  
  var _helpersEach = require('./helpers/each');
  
  var _helpersEach2 = _interopRequireDefault(_helpersEach);
  
  var _helpersHelperMissing = require('./helpers/helper-missing');
  
  var _helpersHelperMissing2 = _interopRequireDefault(_helpersHelperMissing);
  
  var _helpersIf = require('./helpers/if');
  
  var _helpersIf2 = _interopRequireDefault(_helpersIf);
  
  var _helpersLog = require('./helpers/log');
  
  var _helpersLog2 = _interopRequireDefault(_helpersLog);
  
  var _helpersLookup = require('./helpers/lookup');
  
  var _helpersLookup2 = _interopRequireDefault(_helpersLookup);
  
  var _helpersWith = require('./helpers/with');
  
  var _helpersWith2 = _interopRequireDefault(_helpersWith);
  
  function registerDefaultHelpers(instance) {
    _helpersBlockHelperMissing2['default'](instance);
    _helpersEach2['default'](instance);
    _helpersHelperMissing2['default'](instance);
    _helpersIf2['default'](instance);
    _helpersLog2['default'](instance);
    _helpersLookup2['default'](instance);
    _helpersWith2['default'](instance);
  }
  
  
  },{"./helpers/block-helper-missing":355,"./helpers/each":356,"./helpers/helper-missing":357,"./helpers/if":358,"./helpers/log":359,"./helpers/lookup":360,"./helpers/with":361}],355:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  
  var _utils = require('../utils');
  
  exports['default'] = function (instance) {
    instance.registerHelper('blockHelperMissing', function (context, options) {
      var inverse = options.inverse,
          fn = options.fn;
  
      if (context === true) {
        return fn(this);
      } else if (context === false || context == null) {
        return inverse(this);
      } else if (_utils.isArray(context)) {
        if (context.length > 0) {
          if (options.ids) {
            options.ids = [options.name];
          }
  
          return instance.helpers.each(context, options);
        } else {
          return inverse(this);
        }
      } else {
        if (options.data && options.ids) {
          var data = _utils.createFrame(options.data);
          data.contextPath = _utils.appendContextPath(options.data.contextPath, options.name);
          options = { data: data };
        }
  
        return fn(context, options);
      }
    });
  };
  
  module.exports = exports['default'];
  
  
  },{"../utils":366}],356:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  // istanbul ignore next
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  
  var _utils = require('../utils');
  
  var _exception = require('../exception');
  
  var _exception2 = _interopRequireDefault(_exception);
  
  exports['default'] = function (instance) {
    instance.registerHelper('each', function (context, options) {
      if (!options) {
        throw new _exception2['default']('Must pass iterator to #each');
      }
  
      var fn = options.fn,
          inverse = options.inverse,
          i = 0,
          ret = '',
          data = undefined,
          contextPath = undefined;
  
      if (options.data && options.ids) {
        contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.';
      }
  
      if (_utils.isFunction(context)) {
        context = context.call(this);
      }
  
      if (options.data) {
        data = _utils.createFrame(options.data);
      }
  
      function execIteration(field, index, last) {
        if (data) {
          data.key = field;
          data.index = index;
          data.first = index === 0;
          data.last = !!last;
  
          if (contextPath) {
            data.contextPath = contextPath + field;
          }
        }
  
        ret = ret + fn(context[field], {
          data: data,
          blockParams: _utils.blockParams([context[field], field], [contextPath + field, null])
        });
      }
  
      if (context && typeof context === 'object') {
        if (_utils.isArray(context)) {
          for (var j = context.length; i < j; i++) {
            if (i in context) {
              execIteration(i, i, i === context.length - 1);
            }
          }
        } else {
          var priorKey = undefined;
  
          for (var key in context) {
            if (context.hasOwnProperty(key)) {
              // We're running the iterations one step out of sync so we can detect
              // the last iteration without have to scan the object twice and create
              // an itermediate keys array.
              if (priorKey !== undefined) {
                execIteration(priorKey, i - 1);
              }
              priorKey = key;
              i++;
            }
          }
          if (priorKey !== undefined) {
            execIteration(priorKey, i - 1, true);
          }
        }
      }
  
      if (i === 0) {
        ret = inverse(this);
      }
  
      return ret;
    });
  };
  
  module.exports = exports['default'];
  
  
  },{"../exception":353,"../utils":366}],357:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  // istanbul ignore next
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  
  var _exception = require('../exception');
  
  var _exception2 = _interopRequireDefault(_exception);
  
  exports['default'] = function (instance) {
    instance.registerHelper('helperMissing', function () /* [args, ]options */{
      if (arguments.length === 1) {
        // A missing field in a {{foo}} construct.
        return undefined;
      } else {
        // Someone is actually trying to call something, blow up.
        throw new _exception2['default']('Missing helper: "' + arguments[arguments.length - 1].name + '"');
      }
    });
  };
  
  module.exports = exports['default'];
  
  
  },{"../exception":353}],358:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  
  var _utils = require('../utils');
  
  exports['default'] = function (instance) {
    instance.registerHelper('if', function (conditional, options) {
      if (_utils.isFunction(conditional)) {
        conditional = conditional.call(this);
      }
  
      // Default behavior is to render the positive path if the value is truthy and not empty.
      // The `includeZero` option may be set to treat the condtional as purely not empty based on the
      // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
      if (!options.hash.includeZero && !conditional || _utils.isEmpty(conditional)) {
        return options.inverse(this);
      } else {
        return options.fn(this);
      }
    });
  
    instance.registerHelper('unless', function (conditional, options) {
      return instance.helpers['if'].call(this, conditional, { fn: options.inverse, inverse: options.fn, hash: options.hash });
    });
  };
  
  module.exports = exports['default'];
  
  
  },{"../utils":366}],359:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  
  exports['default'] = function (instance) {
    instance.registerHelper('log', function () /* message, options */{
      var args = [undefined],
          options = arguments[arguments.length - 1];
      for (var i = 0; i < arguments.length - 1; i++) {
        args.push(arguments[i]);
      }
  
      var level = 1;
      if (options.hash.level != null) {
        level = options.hash.level;
      } else if (options.data && options.data.level != null) {
        level = options.data.level;
      }
      args[0] = level;
  
      instance.log.apply(instance, args);
    });
  };
  
  module.exports = exports['default'];
  
  
  },{}],360:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  
  exports['default'] = function (instance) {
    instance.registerHelper('lookup', function (obj, field) {
      return obj && obj[field];
    });
  };
  
  module.exports = exports['default'];
  
  
  },{}],361:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  
  var _utils = require('../utils');
  
  exports['default'] = function (instance) {
    instance.registerHelper('with', function (context, options) {
      if (_utils.isFunction(context)) {
        context = context.call(this);
      }
  
      var fn = options.fn;
  
      if (!_utils.isEmpty(context)) {
        var data = options.data;
        if (options.data && options.ids) {
          data = _utils.createFrame(options.data);
          data.contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]);
        }
  
        return fn(context, {
          data: data,
          blockParams: _utils.blockParams([context], [data && data.contextPath])
        });
      } else {
        return options.inverse(this);
      }
    });
  };
  
  module.exports = exports['default'];
  
  
  },{"../utils":366}],362:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  
  var _utils = require('./utils');
  
  var logger = {
    methodMap: ['debug', 'info', 'warn', 'error'],
    level: 'info',
  
    // Maps a given level value to the `methodMap` indexes above.
    lookupLevel: function lookupLevel(level) {
      if (typeof level === 'string') {
        var levelMap = _utils.indexOf(logger.methodMap, level.toLowerCase());
        if (levelMap >= 0) {
          level = levelMap;
        } else {
          level = parseInt(level, 10);
        }
      }
  
      return level;
    },
  
    // Can be overridden in the host environment
    log: function log(level) {
      level = logger.lookupLevel(level);
  
      if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) {
        var method = logger.methodMap[level];
        if (!console[method]) {
          // eslint-disable-line no-console
          method = 'log';
        }
  
        for (var _len = arguments.length, message = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
          message[_key - 1] = arguments[_key];
        }
  
        console[method].apply(console, message); // eslint-disable-line no-console
      }
    }
  };
  
  exports['default'] = logger;
  module.exports = exports['default'];
  
  
  },{"./utils":366}],363:[function(require,module,exports){
  (function (global){
  /* global window */
  'use strict';
  
  exports.__esModule = true;
  
  exports['default'] = function (Handlebars) {
    /* istanbul ignore next */
    var root = typeof global !== 'undefined' ? global : window,
        $Handlebars = root.Handlebars;
    /* istanbul ignore next */
    Handlebars.noConflict = function () {
      if (root.Handlebars === Handlebars) {
        root.Handlebars = $Handlebars;
      }
      return Handlebars;
    };
  };
  
  module.exports = exports['default'];
  
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{}],364:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.checkRevision = checkRevision;
  exports.template = template;
  exports.wrapProgram = wrapProgram;
  exports.resolvePartial = resolvePartial;
  exports.invokePartial = invokePartial;
  exports.noop = noop;
  // istanbul ignore next
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  
  // istanbul ignore next
  
  function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
  
  var _utils = require('./utils');
  
  var Utils = _interopRequireWildcard(_utils);
  
  var _exception = require('./exception');
  
  var _exception2 = _interopRequireDefault(_exception);
  
  var _base = require('./base');
  
  function checkRevision(compilerInfo) {
    var compilerRevision = compilerInfo && compilerInfo[0] || 1,
        currentRevision = _base.COMPILER_REVISION;
  
    if (compilerRevision !== currentRevision) {
      if (compilerRevision < currentRevision) {
        var runtimeVersions = _base.REVISION_CHANGES[currentRevision],
            compilerVersions = _base.REVISION_CHANGES[compilerRevision];
        throw new _exception2['default']('Template was precompiled with an older version of Handlebars than the current runtime. ' + 'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').');
      } else {
        // Use the embedded version info since the runtime doesn't know about this revision yet
        throw new _exception2['default']('Template was precompiled with a newer version of Handlebars than the current runtime. ' + 'Please update your runtime to a newer version (' + compilerInfo[1] + ').');
      }
    }
  }
  
  function template(templateSpec, env) {
    /* istanbul ignore next */
    if (!env) {
      throw new _exception2['default']('No environment passed to template');
    }
    if (!templateSpec || !templateSpec.main) {
      throw new _exception2['default']('Unknown template object: ' + typeof templateSpec);
    }
  
    templateSpec.main.decorator = templateSpec.main_d;
  
    // Note: Using env.VM references rather than local var references throughout this section to allow
    // for external users to override these as psuedo-supported APIs.
    env.VM.checkRevision(templateSpec.compiler);
  
    function invokePartialWrapper(partial, context, options) {
      if (options.hash) {
        context = Utils.extend({}, context, options.hash);
        if (options.ids) {
          options.ids[0] = true;
        }
      }
  
      partial = env.VM.resolvePartial.call(this, partial, context, options);
      var result = env.VM.invokePartial.call(this, partial, context, options);
  
      if (result == null && env.compile) {
        options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env);
        result = options.partials[options.name](context, options);
      }
      if (result != null) {
        if (options.indent) {
          var lines = result.split('\n');
          for (var i = 0, l = lines.length; i < l; i++) {
            if (!lines[i] && i + 1 === l) {
              break;
            }
  
            lines[i] = options.indent + lines[i];
          }
          result = lines.join('\n');
        }
        return result;
      } else {
        throw new _exception2['default']('The partial ' + options.name + ' could not be compiled when running in runtime-only mode');
      }
    }
  
    // Just add water
    var container = {
      strict: function strict(obj, name) {
        if (!(name in obj)) {
          throw new _exception2['default']('"' + name + '" not defined in ' + obj);
        }
        return obj[name];
      },
      lookup: function lookup(depths, name) {
        var len = depths.length;
        for (var i = 0; i < len; i++) {
          if (depths[i] && depths[i][name] != null) {
            return depths[i][name];
          }
        }
      },
      lambda: function lambda(current, context) {
        return typeof current === 'function' ? current.call(context) : current;
      },
  
      escapeExpression: Utils.escapeExpression,
      invokePartial: invokePartialWrapper,
  
      fn: function fn(i) {
        var ret = templateSpec[i];
        ret.decorator = templateSpec[i + '_d'];
        return ret;
      },
  
      programs: [],
      program: function program(i, data, declaredBlockParams, blockParams, depths) {
        var programWrapper = this.programs[i],
            fn = this.fn(i);
        if (data || depths || blockParams || declaredBlockParams) {
          programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths);
        } else if (!programWrapper) {
          programWrapper = this.programs[i] = wrapProgram(this, i, fn);
        }
        return programWrapper;
      },
  
      data: function data(value, depth) {
        while (value && depth--) {
          value = value._parent;
        }
        return value;
      },
      merge: function merge(param, common) {
        var obj = param || common;
  
        if (param && common && param !== common) {
          obj = Utils.extend({}, common, param);
        }
  
        return obj;
      },
      // An empty object to use as replacement for null-contexts
      nullContext: Object.seal({}),
  
      noop: env.VM.noop,
      compilerInfo: templateSpec.compiler
    };
  
    function ret(context) {
      var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
  
      var data = options.data;
  
      ret._setup(options);
      if (!options.partial && templateSpec.useData) {
        data = initData(context, data);
      }
      var depths = undefined,
          blockParams = templateSpec.useBlockParams ? [] : undefined;
      if (templateSpec.useDepths) {
        if (options.depths) {
          depths = context != options.depths[0] ? [context].concat(options.depths) : options.depths;
        } else {
          depths = [context];
        }
      }
  
      function main(context /*, options*/) {
        return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths);
      }
      main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams);
      return main(context, options);
    }
    ret.isTop = true;
  
    ret._setup = function (options) {
      if (!options.partial) {
        container.helpers = container.merge(options.helpers, env.helpers);
  
        if (templateSpec.usePartial) {
          container.partials = container.merge(options.partials, env.partials);
        }
        if (templateSpec.usePartial || templateSpec.useDecorators) {
          container.decorators = container.merge(options.decorators, env.decorators);
        }
      } else {
        container.helpers = options.helpers;
        container.partials = options.partials;
        container.decorators = options.decorators;
      }
    };
  
    ret._child = function (i, data, blockParams, depths) {
      if (templateSpec.useBlockParams && !blockParams) {
        throw new _exception2['default']('must pass block params');
      }
      if (templateSpec.useDepths && !depths) {
        throw new _exception2['default']('must pass parent depths');
      }
  
      return wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths);
    };
    return ret;
  }
  
  function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) {
    function prog(context) {
      var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
  
      var currentDepths = depths;
      if (depths && context != depths[0] && !(context === container.nullContext && depths[0] === null)) {
        currentDepths = [context].concat(depths);
      }
  
      return fn(container, context, container.helpers, container.partials, options.data || data, blockParams && [options.blockParams].concat(blockParams), currentDepths);
    }
  
    prog = executeDecorators(fn, prog, container, depths, data, blockParams);
  
    prog.program = i;
    prog.depth = depths ? depths.length : 0;
    prog.blockParams = declaredBlockParams || 0;
    return prog;
  }
  
  function resolvePartial(partial, context, options) {
    if (!partial) {
      if (options.name === '@partial-block') {
        partial = options.data['partial-block'];
      } else {
        partial = options.partials[options.name];
      }
    } else if (!partial.call && !options.name) {
      // This is a dynamic partial that returned a string
      options.name = partial;
      partial = options.partials[partial];
    }
    return partial;
  }
  
  function invokePartial(partial, context, options) {
    // Use the current closure context to save the partial-block if this partial
    var currentPartialBlock = options.data && options.data['partial-block'];
    options.partial = true;
    if (options.ids) {
      options.data.contextPath = options.ids[0] || options.data.contextPath;
    }
  
    var partialBlock = undefined;
    if (options.fn && options.fn !== noop) {
      (function () {
        options.data = _base.createFrame(options.data);
        // Wrapper function to get access to currentPartialBlock from the closure
        var fn = options.fn;
        partialBlock = options.data['partial-block'] = function partialBlockWrapper(context) {
          var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
  
          // Restore the partial-block from the closure for the execution of the block
          // i.e. the part inside the block of the partial call.
          options.data = _base.createFrame(options.data);
          options.data['partial-block'] = currentPartialBlock;
          return fn(context, options);
        };
        if (fn.partials) {
          options.partials = Utils.extend({}, options.partials, fn.partials);
        }
      })();
    }
  
    if (partial === undefined && partialBlock) {
      partial = partialBlock;
    }
  
    if (partial === undefined) {
      throw new _exception2['default']('The partial ' + options.name + ' could not be found');
    } else if (partial instanceof Function) {
      return partial(context, options);
    }
  }
  
  function noop() {
    return '';
  }
  
  function initData(context, data) {
    if (!data || !('root' in data)) {
      data = data ? _base.createFrame(data) : {};
      data.root = context;
    }
    return data;
  }
  
  function executeDecorators(fn, prog, container, depths, data, blockParams) {
    if (fn.decorator) {
      var props = {};
      prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths);
      Utils.extend(prog, props);
    }
    return prog;
  }
  
  
  },{"./base":350,"./exception":353,"./utils":366}],365:[function(require,module,exports){
  // Build out our basic SafeString type
  'use strict';
  
  exports.__esModule = true;
  function SafeString(string) {
    this.string = string;
  }
  
  SafeString.prototype.toString = SafeString.prototype.toHTML = function () {
    return '' + this.string;
  };
  
  exports['default'] = SafeString;
  module.exports = exports['default'];
  
  
  },{}],366:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.extend = extend;
  exports.indexOf = indexOf;
  exports.escapeExpression = escapeExpression;
  exports.isEmpty = isEmpty;
  exports.createFrame = createFrame;
  exports.blockParams = blockParams;
  exports.appendContextPath = appendContextPath;
  var escape = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#x27;',
    '`': '&#x60;',
    '=': '&#x3D;'
  };
  
  var badChars = /[&<>"'`=]/g,
      possible = /[&<>"'`=]/;
  
  function escapeChar(chr) {
    return escape[chr];
  }
  
  function extend(obj /* , ...source */) {
    for (var i = 1; i < arguments.length; i++) {
      for (var key in arguments[i]) {
        if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
          obj[key] = arguments[i][key];
        }
      }
    }
  
    return obj;
  }
  
  var toString = Object.prototype.toString;
  
  exports.toString = toString;
  // Sourced from lodash
  // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
  /* eslint-disable func-style */
  var isFunction = function isFunction(value) {
    return typeof value === 'function';
  };
  // fallback for older versions of Chrome and Safari
  /* istanbul ignore next */
  if (isFunction(/x/)) {
    exports.isFunction = isFunction = function (value) {
      return typeof value === 'function' && toString.call(value) === '[object Function]';
    };
  }
  exports.isFunction = isFunction;
  
  /* eslint-enable func-style */
  
  /* istanbul ignore next */
  var isArray = Array.isArray || function (value) {
    return value && typeof value === 'object' ? toString.call(value) === '[object Array]' : false;
  };
  
  exports.isArray = isArray;
  // Older IE versions do not directly support indexOf so we must implement our own, sadly.
  
  function indexOf(array, value) {
    for (var i = 0, len = array.length; i < len; i++) {
      if (array[i] === value) {
        return i;
      }
    }
    return -1;
  }
  
  function escapeExpression(string) {
    if (typeof string !== 'string') {
      // don't escape SafeStrings, since they're already safe
      if (string && string.toHTML) {
        return string.toHTML();
      } else if (string == null) {
        return '';
      } else if (!string) {
        return string + '';
      }
  
      // Force a string conversion as this will be done by the append regardless and
      // the regex test will do this transparently behind the scenes, causing issues if
      // an object's to string has escaped characters in it.
      string = '' + string;
    }
  
    if (!possible.test(string)) {
      return string;
    }
    return string.replace(badChars, escapeChar);
  }
  
  function isEmpty(value) {
    if (!value && value !== 0) {
      return true;
    } else if (isArray(value) && value.length === 0) {
      return true;
    } else {
      return false;
    }
  }
  
  function createFrame(object) {
    var frame = extend({}, object);
    frame._parent = object;
    return frame;
  }
  
  function blockParams(params, ids) {
    params.path = ids;
    return params;
  }
  
  function appendContextPath(contextPath, id) {
    return (contextPath ? contextPath + '.' : '') + id;
  }
  
  
  },{}],367:[function(require,module,exports){
  // Create a simple path alias to allow browserify to resolve
  // the runtime on a supported path.
  module.exports = require('./dist/cjs/handlebars.runtime')['default'];
  
  },{"./dist/cjs/handlebars.runtime":349}],368:[function(require,module,exports){
  module.exports = require("handlebars/runtime")["default"];
  
  },{"handlebars/runtime":367}],369:[function(require,module,exports){
  /**
   * Copyright 2015, Yahoo! Inc.
   * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
   */
  (function (global, factory) {
      typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
      typeof define === 'function' && define.amd ? define(factory) :
      (global.hoistNonReactStatics = factory());
  }(this, (function () {
      'use strict';
      
      var REACT_STATICS = {
          childContextTypes: true,
          contextTypes: true,
          defaultProps: true,
          displayName: true,
          getDefaultProps: true,
          getDerivedStateFromProps: true,
          mixins: true,
          propTypes: true,
          type: true
      };
      
      var KNOWN_STATICS = {
          name: true,
          length: true,
          prototype: true,
          caller: true,
          callee: true,
          arguments: true,
          arity: true
      };
      
      var defineProperty = Object.defineProperty;
      var getOwnPropertyNames = Object.getOwnPropertyNames;
      var getOwnPropertySymbols = Object.getOwnPropertySymbols;
      var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
      var getPrototypeOf = Object.getPrototypeOf;
      var objectPrototype = getPrototypeOf && getPrototypeOf(Object);
      
      return function hoistNonReactStatics(targetComponent, sourceComponent, blacklist) {
          if (typeof sourceComponent !== 'string') { // don't hoist over string (html) components
              
              if (objectPrototype) {
                  var inheritedComponent = getPrototypeOf(sourceComponent);
                  if (inheritedComponent && inheritedComponent !== objectPrototype) {
                      hoistNonReactStatics(targetComponent, inheritedComponent, blacklist);
                  }
              }
              
              var keys = getOwnPropertyNames(sourceComponent);
              
              if (getOwnPropertySymbols) {
                  keys = keys.concat(getOwnPropertySymbols(sourceComponent));
              }
              
              for (var i = 0; i < keys.length; ++i) {
                  var key = keys[i];
                  if (!REACT_STATICS[key] && !KNOWN_STATICS[key] && (!blacklist || !blacklist[key])) {
                      var descriptor = getOwnPropertyDescriptor(sourceComponent, key);
                      try { // Avoid failures from read-only properties
                          defineProperty(targetComponent, key, descriptor);
                      } catch (e) {}
                  }
              }
              
              return targetComponent;
          }
          
          return targetComponent;
      };
  })));
  
  },{}],370:[function(require,module,exports){
  exports.read = function (buffer, offset, isLE, mLen, nBytes) {
    var e, m
    var eLen = nBytes * 8 - mLen - 1
    var eMax = (1 << eLen) - 1
    var eBias = eMax >> 1
    var nBits = -7
    var i = isLE ? (nBytes - 1) : 0
    var d = isLE ? -1 : 1
    var s = buffer[offset + i]
  
    i += d
  
    e = s & ((1 << (-nBits)) - 1)
    s >>= (-nBits)
    nBits += eLen
    for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}
  
    m = e & ((1 << (-nBits)) - 1)
    e >>= (-nBits)
    nBits += mLen
    for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}
  
    if (e === 0) {
      e = 1 - eBias
    } else if (e === eMax) {
      return m ? NaN : ((s ? -1 : 1) * Infinity)
    } else {
      m = m + Math.pow(2, mLen)
      e = e - eBias
    }
    return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
  }
  
  exports.write = function (buffer, value, offset, isLE, mLen, nBytes) {
    var e, m, c
    var eLen = nBytes * 8 - mLen - 1
    var eMax = (1 << eLen) - 1
    var eBias = eMax >> 1
    var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
    var i = isLE ? 0 : (nBytes - 1)
    var d = isLE ? 1 : -1
    var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0
  
    value = Math.abs(value)
  
    if (isNaN(value) || value === Infinity) {
      m = isNaN(value) ? 1 : 0
      e = eMax
    } else {
      e = Math.floor(Math.log(value) / Math.LN2)
      if (value * (c = Math.pow(2, -e)) < 1) {
        e--
        c *= 2
      }
      if (e + eBias >= 1) {
        value += rt / c
      } else {
        value += rt * Math.pow(2, 1 - eBias)
      }
      if (value * c >= 2) {
        e++
        c /= 2
      }
  
      if (e + eBias >= eMax) {
        m = 0
        e = eMax
      } else if (e + eBias >= 1) {
        m = (value * c - 1) * Math.pow(2, mLen)
        e = e + eBias
      } else {
        m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)
        e = 0
      }
    }
  
    for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
  
    e = (e << mLen) | m
    eLen += mLen
    for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
  
    buffer[offset + i - d] |= s * 128
  }
  
  },{}],371:[function(require,module,exports){
  /**
   * Copyright 2013-2015, Facebook, Inc.
   * All rights reserved.
   *
   * This source code is licensed under the BSD-style license found in the
   * LICENSE file in the root directory of this source tree. An additional grant
   * of patent rights can be found in the PATENTS file in the same directory.
   */
  
  'use strict';
  
  /**
   * Use invariant() to assert state which your program assumes to be true.
   *
   * Provide sprintf-style format (only %s is supported) and arguments
   * to provide information about what broke and what you were
   * expecting.
   *
   * The invariant message will be stripped in production, but the invariant
   * will remain to ensure logic does not differ in production.
   */
  
  var invariant = function(condition, format, a, b, c, d, e, f) {
    if ("production" !== 'production') {
      if (format === undefined) {
        throw new Error('invariant requires an error message argument');
      }
    }
  
    if (!condition) {
      var error;
      if (format === undefined) {
        error = new Error(
          'Minified exception occurred; use the non-minified dev environment ' +
          'for the full error message and additional helpful warnings.'
        );
      } else {
        var args = [a, b, c, d, e, f];
        var argIndex = 0;
        error = new Error(
          format.replace(/%s/g, function() { return args[argIndex++]; })
        );
        error.name = 'Invariant Violation';
      }
  
      error.framesToPop = 1; // we don't care about invariant's own frame
      throw error;
    }
  };
  
  module.exports = invariant;
  
  },{}],372:[function(require,module,exports){
  var toString = {}.toString;
  
  module.exports = Array.isArray || function (arr) {
    return toString.call(arr) == '[object Array]';
  };
  
  },{}],373:[function(require,module,exports){
  var json = typeof JSON !== 'undefined' ? JSON : require('jsonify');
  
  module.exports = function (obj, opts) {
      if (!opts) opts = {};
      if (typeof opts === 'function') opts = { cmp: opts };
      var space = opts.space || '';
      if (typeof space === 'number') space = Array(space+1).join(' ');
      var cycles = (typeof opts.cycles === 'boolean') ? opts.cycles : false;
      var replacer = opts.replacer || function(key, value) { return value; };
  
      var cmp = opts.cmp && (function (f) {
          return function (node) {
              return function (a, b) {
                  var aobj = { key: a, value: node[a] };
                  var bobj = { key: b, value: node[b] };
                  return f(aobj, bobj);
              };
          };
      })(opts.cmp);
  
      var seen = [];
      return (function stringify (parent, key, node, level) {
          var indent = space ? ('\n' + new Array(level + 1).join(space)) : '';
          var colonSeparator = space ? ': ' : ':';
  
          if (node && node.toJSON && typeof node.toJSON === 'function') {
              node = node.toJSON();
          }
  
          node = replacer.call(parent, key, node);
  
          if (node === undefined) {
              return;
          }
          if (typeof node !== 'object' || node === null) {
              return json.stringify(node);
          }
          if (isArray(node)) {
              var out = [];
              for (var i = 0; i < node.length; i++) {
                  var item = stringify(node, i, node[i], level+1) || json.stringify(null);
                  out.push(indent + space + item);
              }
              return '[' + out.join(',') + indent + ']';
          }
          else {
              if (seen.indexOf(node) !== -1) {
                  if (cycles) return json.stringify('__cycle__');
                  throw new TypeError('Converting circular structure to JSON');
              }
              else seen.push(node);
  
              var keys = objectKeys(node).sort(cmp && cmp(node));
              var out = [];
              for (var i = 0; i < keys.length; i++) {
                  var key = keys[i];
                  var value = stringify(node, key, node[key], level+1);
  
                  if(!value) continue;
  
                  var keyValue = json.stringify(key)
                      + colonSeparator
                      + value;
                  ;
                  out.push(indent + space + keyValue);
              }
              seen.splice(seen.indexOf(node), 1);
              return '{' + out.join(',') + indent + '}';
          }
      })({ '': obj }, '', obj, 0);
  };
  
  var isArray = Array.isArray || function (x) {
      return {}.toString.call(x) === '[object Array]';
  };
  
  var objectKeys = Object.keys || function (obj) {
      var has = Object.prototype.hasOwnProperty || function () { return true };
      var keys = [];
      for (var key in obj) {
          if (has.call(obj, key)) keys.push(key);
      }
      return keys;
  };
  
  },{"jsonify":374}],374:[function(require,module,exports){
  exports.parse = JSON.parse;
  exports.stringify = JSON.stringify;
  
  },{}],375:[function(require,module,exports){
  var hashClear = require('./_hashClear'),
      hashDelete = require('./_hashDelete'),
      hashGet = require('./_hashGet'),
      hashHas = require('./_hashHas'),
      hashSet = require('./_hashSet');
  
  /**
   * Creates a hash object.
   *
   * @private
   * @constructor
   * @param {Array} [entries] The key-value pairs to cache.
   */
  function Hash(entries) {
    var index = -1,
        length = entries == null ? 0 : entries.length;
  
    this.clear();
    while (++index < length) {
      var entry = entries[index];
      this.set(entry[0], entry[1]);
    }
  }
  
  // Add methods to `Hash`.
  Hash.prototype.clear = hashClear;
  Hash.prototype['delete'] = hashDelete;
  Hash.prototype.get = hashGet;
  Hash.prototype.has = hashHas;
  Hash.prototype.set = hashSet;
  
  module.exports = Hash;
  
  },{"./_hashClear":394,"./_hashDelete":395,"./_hashGet":396,"./_hashHas":397,"./_hashSet":398}],376:[function(require,module,exports){
  var listCacheClear = require('./_listCacheClear'),
      listCacheDelete = require('./_listCacheDelete'),
      listCacheGet = require('./_listCacheGet'),
      listCacheHas = require('./_listCacheHas'),
      listCacheSet = require('./_listCacheSet');
  
  /**
   * Creates an list cache object.
   *
   * @private
   * @constructor
   * @param {Array} [entries] The key-value pairs to cache.
   */
  function ListCache(entries) {
    var index = -1,
        length = entries == null ? 0 : entries.length;
  
    this.clear();
    while (++index < length) {
      var entry = entries[index];
      this.set(entry[0], entry[1]);
    }
  }
  
  // Add methods to `ListCache`.
  ListCache.prototype.clear = listCacheClear;
  ListCache.prototype['delete'] = listCacheDelete;
  ListCache.prototype.get = listCacheGet;
  ListCache.prototype.has = listCacheHas;
  ListCache.prototype.set = listCacheSet;
  
  module.exports = ListCache;
  
  },{"./_listCacheClear":402,"./_listCacheDelete":403,"./_listCacheGet":404,"./_listCacheHas":405,"./_listCacheSet":406}],377:[function(require,module,exports){
  var getNative = require('./_getNative'),
      root = require('./_root');
  
  /* Built-in method references that are verified to be native. */
  var Map = getNative(root, 'Map');
  
  module.exports = Map;
  
  },{"./_getNative":390,"./_root":416}],378:[function(require,module,exports){
  var mapCacheClear = require('./_mapCacheClear'),
      mapCacheDelete = require('./_mapCacheDelete'),
      mapCacheGet = require('./_mapCacheGet'),
      mapCacheHas = require('./_mapCacheHas'),
      mapCacheSet = require('./_mapCacheSet');
  
  /**
   * Creates a map cache object to store key-value pairs.
   *
   * @private
   * @constructor
   * @param {Array} [entries] The key-value pairs to cache.
   */
  function MapCache(entries) {
    var index = -1,
        length = entries == null ? 0 : entries.length;
  
    this.clear();
    while (++index < length) {
      var entry = entries[index];
      this.set(entry[0], entry[1]);
    }
  }
  
  // Add methods to `MapCache`.
  MapCache.prototype.clear = mapCacheClear;
  MapCache.prototype['delete'] = mapCacheDelete;
  MapCache.prototype.get = mapCacheGet;
  MapCache.prototype.has = mapCacheHas;
  MapCache.prototype.set = mapCacheSet;
  
  module.exports = MapCache;
  
  },{"./_mapCacheClear":407,"./_mapCacheDelete":408,"./_mapCacheGet":409,"./_mapCacheHas":410,"./_mapCacheSet":411}],379:[function(require,module,exports){
  var root = require('./_root');
  
  /** Built-in value references. */
  var Symbol = root.Symbol;
  
  module.exports = Symbol;
  
  },{"./_root":416}],380:[function(require,module,exports){
  /**
   * A specialized version of `_.map` for arrays without support for iteratee
   * shorthands.
   *
   * @private
   * @param {Array} [array] The array to iterate over.
   * @param {Function} iteratee The function invoked per iteration.
   * @returns {Array} Returns the new mapped array.
   */
  function arrayMap(array, iteratee) {
    var index = -1,
        length = array == null ? 0 : array.length,
        result = Array(length);
  
    while (++index < length) {
      result[index] = iteratee(array[index], index, array);
    }
    return result;
  }
  
  module.exports = arrayMap;
  
  },{}],381:[function(require,module,exports){
  var eq = require('./eq');
  
  /**
   * Gets the index at which the `key` is found in `array` of key-value pairs.
   *
   * @private
   * @param {Array} array The array to inspect.
   * @param {*} key The key to search for.
   * @returns {number} Returns the index of the matched value, else `-1`.
   */
  function assocIndexOf(array, key) {
    var length = array.length;
    while (length--) {
      if (eq(array[length][0], key)) {
        return length;
      }
    }
    return -1;
  }
  
  module.exports = assocIndexOf;
  
  },{"./eq":421}],382:[function(require,module,exports){
  var castPath = require('./_castPath'),
      toKey = require('./_toKey');
  
  /**
   * The base implementation of `_.get` without support for default values.
   *
   * @private
   * @param {Object} object The object to query.
   * @param {Array|string} path The path of the property to get.
   * @returns {*} Returns the resolved value.
   */
  function baseGet(object, path) {
    path = castPath(path, object);
  
    var index = 0,
        length = path.length;
  
    while (object != null && index < length) {
      object = object[toKey(path[index++])];
    }
    return (index && index == length) ? object : undefined;
  }
  
  module.exports = baseGet;
  
  },{"./_castPath":386,"./_toKey":418}],383:[function(require,module,exports){
  var Symbol = require('./_Symbol'),
      getRawTag = require('./_getRawTag'),
      objectToString = require('./_objectToString');
  
  /** `Object#toString` result references. */
  var nullTag = '[object Null]',
      undefinedTag = '[object Undefined]';
  
  /** Built-in value references. */
  var symToStringTag = Symbol ? Symbol.toStringTag : undefined;
  
  /**
   * The base implementation of `getTag` without fallbacks for buggy environments.
   *
   * @private
   * @param {*} value The value to query.
   * @returns {string} Returns the `toStringTag`.
   */
  function baseGetTag(value) {
    if (value == null) {
      return value === undefined ? undefinedTag : nullTag;
    }
    return (symToStringTag && symToStringTag in Object(value))
      ? getRawTag(value)
      : objectToString(value);
  }
  
  module.exports = baseGetTag;
  
  },{"./_Symbol":379,"./_getRawTag":392,"./_objectToString":414}],384:[function(require,module,exports){
  var isFunction = require('./isFunction'),
      isMasked = require('./_isMasked'),
      isObject = require('./isObject'),
      toSource = require('./_toSource');
  
  /**
   * Used to match `RegExp`
   * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
   */
  var reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
  
  /** Used to detect host constructors (Safari). */
  var reIsHostCtor = /^\[object .+?Constructor\]$/;
  
  /** Used for built-in method references. */
  var funcProto = Function.prototype,
      objectProto = Object.prototype;
  
  /** Used to resolve the decompiled source of functions. */
  var funcToString = funcProto.toString;
  
  /** Used to check objects for own properties. */
  var hasOwnProperty = objectProto.hasOwnProperty;
  
  /** Used to detect if a method is native. */
  var reIsNative = RegExp('^' +
    funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
    .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
  );
  
  /**
   * The base implementation of `_.isNative` without bad shim checks.
   *
   * @private
   * @param {*} value The value to check.
   * @returns {boolean} Returns `true` if `value` is a native function,
   *  else `false`.
   */
  function baseIsNative(value) {
    if (!isObject(value) || isMasked(value)) {
      return false;
    }
    var pattern = isFunction(value) ? reIsNative : reIsHostCtor;
    return pattern.test(toSource(value));
  }
  
  module.exports = baseIsNative;
  
  },{"./_isMasked":401,"./_toSource":419,"./isFunction":424,"./isObject":425}],385:[function(require,module,exports){
  var Symbol = require('./_Symbol'),
      arrayMap = require('./_arrayMap'),
      isArray = require('./isArray'),
      isSymbol = require('./isSymbol');
  
  /** Used as references for various `Number` constants. */
  var INFINITY = 1 / 0;
  
  /** Used to convert symbols to primitives and strings. */
  var symbolProto = Symbol ? Symbol.prototype : undefined,
      symbolToString = symbolProto ? symbolProto.toString : undefined;
  
  /**
   * The base implementation of `_.toString` which doesn't convert nullish
   * values to empty strings.
   *
   * @private
   * @param {*} value The value to process.
   * @returns {string} Returns the string.
   */
  function baseToString(value) {
    // Exit early for strings to avoid a performance hit in some environments.
    if (typeof value == 'string') {
      return value;
    }
    if (isArray(value)) {
      // Recursively convert values (susceptible to call stack limits).
      return arrayMap(value, baseToString) + '';
    }
    if (isSymbol(value)) {
      return symbolToString ? symbolToString.call(value) : '';
    }
    var result = (value + '');
    return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
  }
  
  module.exports = baseToString;
  
  },{"./_Symbol":379,"./_arrayMap":380,"./isArray":423,"./isSymbol":428}],386:[function(require,module,exports){
  var isArray = require('./isArray'),
      isKey = require('./_isKey'),
      stringToPath = require('./_stringToPath'),
      toString = require('./toString');
  
  /**
   * Casts `value` to a path array if it's not one.
   *
   * @private
   * @param {*} value The value to inspect.
   * @param {Object} [object] The object to query keys on.
   * @returns {Array} Returns the cast property path array.
   */
  function castPath(value, object) {
    if (isArray(value)) {
      return value;
    }
    return isKey(value, object) ? [value] : stringToPath(toString(value));
  }
  
  module.exports = castPath;
  
  },{"./_isKey":399,"./_stringToPath":417,"./isArray":423,"./toString":432}],387:[function(require,module,exports){
  var root = require('./_root');
  
  /** Used to detect overreaching core-js shims. */
  var coreJsData = root['__core-js_shared__'];
  
  module.exports = coreJsData;
  
  },{"./_root":416}],388:[function(require,module,exports){
  (function (global){
  /** Detect free variable `global` from Node.js. */
  var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
  
  module.exports = freeGlobal;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{}],389:[function(require,module,exports){
  var isKeyable = require('./_isKeyable');
  
  /**
   * Gets the data for `map`.
   *
   * @private
   * @param {Object} map The map to query.
   * @param {string} key The reference key.
   * @returns {*} Returns the map data.
   */
  function getMapData(map, key) {
    var data = map.__data__;
    return isKeyable(key)
      ? data[typeof key == 'string' ? 'string' : 'hash']
      : data.map;
  }
  
  module.exports = getMapData;
  
  },{"./_isKeyable":400}],390:[function(require,module,exports){
  var baseIsNative = require('./_baseIsNative'),
      getValue = require('./_getValue');
  
  /**
   * Gets the native function at `key` of `object`.
   *
   * @private
   * @param {Object} object The object to query.
   * @param {string} key The key of the method to get.
   * @returns {*} Returns the function if it's native, else `undefined`.
   */
  function getNative(object, key) {
    var value = getValue(object, key);
    return baseIsNative(value) ? value : undefined;
  }
  
  module.exports = getNative;
  
  },{"./_baseIsNative":384,"./_getValue":393}],391:[function(require,module,exports){
  var overArg = require('./_overArg');
  
  /** Built-in value references. */
  var getPrototype = overArg(Object.getPrototypeOf, Object);
  
  module.exports = getPrototype;
  
  },{"./_overArg":415}],392:[function(require,module,exports){
  var Symbol = require('./_Symbol');
  
  /** Used for built-in method references. */
  var objectProto = Object.prototype;
  
  /** Used to check objects for own properties. */
  var hasOwnProperty = objectProto.hasOwnProperty;
  
  /**
   * Used to resolve the
   * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
   * of values.
   */
  var nativeObjectToString = objectProto.toString;
  
  /** Built-in value references. */
  var symToStringTag = Symbol ? Symbol.toStringTag : undefined;
  
  /**
   * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
   *
   * @private
   * @param {*} value The value to query.
   * @returns {string} Returns the raw `toStringTag`.
   */
  function getRawTag(value) {
    var isOwn = hasOwnProperty.call(value, symToStringTag),
        tag = value[symToStringTag];
  
    try {
      value[symToStringTag] = undefined;
      var unmasked = true;
    } catch (e) {}
  
    var result = nativeObjectToString.call(value);
    if (unmasked) {
      if (isOwn) {
        value[symToStringTag] = tag;
      } else {
        delete value[symToStringTag];
      }
    }
    return result;
  }
  
  module.exports = getRawTag;
  
  },{"./_Symbol":379}],393:[function(require,module,exports){
  /**
   * Gets the value at `key` of `object`.
   *
   * @private
   * @param {Object} [object] The object to query.
   * @param {string} key The key of the property to get.
   * @returns {*} Returns the property value.
   */
  function getValue(object, key) {
    return object == null ? undefined : object[key];
  }
  
  module.exports = getValue;
  
  },{}],394:[function(require,module,exports){
  var nativeCreate = require('./_nativeCreate');
  
  /**
   * Removes all key-value entries from the hash.
   *
   * @private
   * @name clear
   * @memberOf Hash
   */
  function hashClear() {
    this.__data__ = nativeCreate ? nativeCreate(null) : {};
    this.size = 0;
  }
  
  module.exports = hashClear;
  
  },{"./_nativeCreate":413}],395:[function(require,module,exports){
  /**
   * Removes `key` and its value from the hash.
   *
   * @private
   * @name delete
   * @memberOf Hash
   * @param {Object} hash The hash to modify.
   * @param {string} key The key of the value to remove.
   * @returns {boolean} Returns `true` if the entry was removed, else `false`.
   */
  function hashDelete(key) {
    var result = this.has(key) && delete this.__data__[key];
    this.size -= result ? 1 : 0;
    return result;
  }
  
  module.exports = hashDelete;
  
  },{}],396:[function(require,module,exports){
  var nativeCreate = require('./_nativeCreate');
  
  /** Used to stand-in for `undefined` hash values. */
  var HASH_UNDEFINED = '__lodash_hash_undefined__';
  
  /** Used for built-in method references. */
  var objectProto = Object.prototype;
  
  /** Used to check objects for own properties. */
  var hasOwnProperty = objectProto.hasOwnProperty;
  
  /**
   * Gets the hash value for `key`.
   *
   * @private
   * @name get
   * @memberOf Hash
   * @param {string} key The key of the value to get.
   * @returns {*} Returns the entry value.
   */
  function hashGet(key) {
    var data = this.__data__;
    if (nativeCreate) {
      var result = data[key];
      return result === HASH_UNDEFINED ? undefined : result;
    }
    return hasOwnProperty.call(data, key) ? data[key] : undefined;
  }
  
  module.exports = hashGet;
  
  },{"./_nativeCreate":413}],397:[function(require,module,exports){
  var nativeCreate = require('./_nativeCreate');
  
  /** Used for built-in method references. */
  var objectProto = Object.prototype;
  
  /** Used to check objects for own properties. */
  var hasOwnProperty = objectProto.hasOwnProperty;
  
  /**
   * Checks if a hash value for `key` exists.
   *
   * @private
   * @name has
   * @memberOf Hash
   * @param {string} key The key of the entry to check.
   * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
   */
  function hashHas(key) {
    var data = this.__data__;
    return nativeCreate ? (data[key] !== undefined) : hasOwnProperty.call(data, key);
  }
  
  module.exports = hashHas;
  
  },{"./_nativeCreate":413}],398:[function(require,module,exports){
  var nativeCreate = require('./_nativeCreate');
  
  /** Used to stand-in for `undefined` hash values. */
  var HASH_UNDEFINED = '__lodash_hash_undefined__';
  
  /**
   * Sets the hash `key` to `value`.
   *
   * @private
   * @name set
   * @memberOf Hash
   * @param {string} key The key of the value to set.
   * @param {*} value The value to set.
   * @returns {Object} Returns the hash instance.
   */
  function hashSet(key, value) {
    var data = this.__data__;
    this.size += this.has(key) ? 0 : 1;
    data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;
    return this;
  }
  
  module.exports = hashSet;
  
  },{"./_nativeCreate":413}],399:[function(require,module,exports){
  var isArray = require('./isArray'),
      isSymbol = require('./isSymbol');
  
  /** Used to match property names within property paths. */
  var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
      reIsPlainProp = /^\w*$/;
  
  /**
   * Checks if `value` is a property name and not a property path.
   *
   * @private
   * @param {*} value The value to check.
   * @param {Object} [object] The object to query keys on.
   * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
   */
  function isKey(value, object) {
    if (isArray(value)) {
      return false;
    }
    var type = typeof value;
    if (type == 'number' || type == 'symbol' || type == 'boolean' ||
        value == null || isSymbol(value)) {
      return true;
    }
    return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
      (object != null && value in Object(object));
  }
  
  module.exports = isKey;
  
  },{"./isArray":423,"./isSymbol":428}],400:[function(require,module,exports){
  /**
   * Checks if `value` is suitable for use as unique object key.
   *
   * @private
   * @param {*} value The value to check.
   * @returns {boolean} Returns `true` if `value` is suitable, else `false`.
   */
  function isKeyable(value) {
    var type = typeof value;
    return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
      ? (value !== '__proto__')
      : (value === null);
  }
  
  module.exports = isKeyable;
  
  },{}],401:[function(require,module,exports){
  var coreJsData = require('./_coreJsData');
  
  /** Used to detect methods masquerading as native. */
  var maskSrcKey = (function() {
    var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
    return uid ? ('Symbol(src)_1.' + uid) : '';
  }());
  
  /**
   * Checks if `func` has its source masked.
   *
   * @private
   * @param {Function} func The function to check.
   * @returns {boolean} Returns `true` if `func` is masked, else `false`.
   */
  function isMasked(func) {
    return !!maskSrcKey && (maskSrcKey in func);
  }
  
  module.exports = isMasked;
  
  },{"./_coreJsData":387}],402:[function(require,module,exports){
  /**
   * Removes all key-value entries from the list cache.
   *
   * @private
   * @name clear
   * @memberOf ListCache
   */
  function listCacheClear() {
    this.__data__ = [];
    this.size = 0;
  }
  
  module.exports = listCacheClear;
  
  },{}],403:[function(require,module,exports){
  var assocIndexOf = require('./_assocIndexOf');
  
  /** Used for built-in method references. */
  var arrayProto = Array.prototype;
  
  /** Built-in value references. */
  var splice = arrayProto.splice;
  
  /**
   * Removes `key` and its value from the list cache.
   *
   * @private
   * @name delete
   * @memberOf ListCache
   * @param {string} key The key of the value to remove.
   * @returns {boolean} Returns `true` if the entry was removed, else `false`.
   */
  function listCacheDelete(key) {
    var data = this.__data__,
        index = assocIndexOf(data, key);
  
    if (index < 0) {
      return false;
    }
    var lastIndex = data.length - 1;
    if (index == lastIndex) {
      data.pop();
    } else {
      splice.call(data, index, 1);
    }
    --this.size;
    return true;
  }
  
  module.exports = listCacheDelete;
  
  },{"./_assocIndexOf":381}],404:[function(require,module,exports){
  var assocIndexOf = require('./_assocIndexOf');
  
  /**
   * Gets the list cache value for `key`.
   *
   * @private
   * @name get
   * @memberOf ListCache
   * @param {string} key The key of the value to get.
   * @returns {*} Returns the entry value.
   */
  function listCacheGet(key) {
    var data = this.__data__,
        index = assocIndexOf(data, key);
  
    return index < 0 ? undefined : data[index][1];
  }
  
  module.exports = listCacheGet;
  
  },{"./_assocIndexOf":381}],405:[function(require,module,exports){
  var assocIndexOf = require('./_assocIndexOf');
  
  /**
   * Checks if a list cache value for `key` exists.
   *
   * @private
   * @name has
   * @memberOf ListCache
   * @param {string} key The key of the entry to check.
   * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
   */
  function listCacheHas(key) {
    return assocIndexOf(this.__data__, key) > -1;
  }
  
  module.exports = listCacheHas;
  
  },{"./_assocIndexOf":381}],406:[function(require,module,exports){
  var assocIndexOf = require('./_assocIndexOf');
  
  /**
   * Sets the list cache `key` to `value`.
   *
   * @private
   * @name set
   * @memberOf ListCache
   * @param {string} key The key of the value to set.
   * @param {*} value The value to set.
   * @returns {Object} Returns the list cache instance.
   */
  function listCacheSet(key, value) {
    var data = this.__data__,
        index = assocIndexOf(data, key);
  
    if (index < 0) {
      ++this.size;
      data.push([key, value]);
    } else {
      data[index][1] = value;
    }
    return this;
  }
  
  module.exports = listCacheSet;
  
  },{"./_assocIndexOf":381}],407:[function(require,module,exports){
  var Hash = require('./_Hash'),
      ListCache = require('./_ListCache'),
      Map = require('./_Map');
  
  /**
   * Removes all key-value entries from the map.
   *
   * @private
   * @name clear
   * @memberOf MapCache
   */
  function mapCacheClear() {
    this.size = 0;
    this.__data__ = {
      'hash': new Hash,
      'map': new (Map || ListCache),
      'string': new Hash
    };
  }
  
  module.exports = mapCacheClear;
  
  },{"./_Hash":375,"./_ListCache":376,"./_Map":377}],408:[function(require,module,exports){
  var getMapData = require('./_getMapData');
  
  /**
   * Removes `key` and its value from the map.
   *
   * @private
   * @name delete
   * @memberOf MapCache
   * @param {string} key The key of the value to remove.
   * @returns {boolean} Returns `true` if the entry was removed, else `false`.
   */
  function mapCacheDelete(key) {
    var result = getMapData(this, key)['delete'](key);
    this.size -= result ? 1 : 0;
    return result;
  }
  
  module.exports = mapCacheDelete;
  
  },{"./_getMapData":389}],409:[function(require,module,exports){
  var getMapData = require('./_getMapData');
  
  /**
   * Gets the map value for `key`.
   *
   * @private
   * @name get
   * @memberOf MapCache
   * @param {string} key The key of the value to get.
   * @returns {*} Returns the entry value.
   */
  function mapCacheGet(key) {
    return getMapData(this, key).get(key);
  }
  
  module.exports = mapCacheGet;
  
  },{"./_getMapData":389}],410:[function(require,module,exports){
  var getMapData = require('./_getMapData');
  
  /**
   * Checks if a map value for `key` exists.
   *
   * @private
   * @name has
   * @memberOf MapCache
   * @param {string} key The key of the entry to check.
   * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
   */
  function mapCacheHas(key) {
    return getMapData(this, key).has(key);
  }
  
  module.exports = mapCacheHas;
  
  },{"./_getMapData":389}],411:[function(require,module,exports){
  var getMapData = require('./_getMapData');
  
  /**
   * Sets the map `key` to `value`.
   *
   * @private
   * @name set
   * @memberOf MapCache
   * @param {string} key The key of the value to set.
   * @param {*} value The value to set.
   * @returns {Object} Returns the map cache instance.
   */
  function mapCacheSet(key, value) {
    var data = getMapData(this, key),
        size = data.size;
  
    data.set(key, value);
    this.size += data.size == size ? 0 : 1;
    return this;
  }
  
  module.exports = mapCacheSet;
  
  },{"./_getMapData":389}],412:[function(require,module,exports){
  var memoize = require('./memoize');
  
  /** Used as the maximum memoize cache size. */
  var MAX_MEMOIZE_SIZE = 500;
  
  /**
   * A specialized version of `_.memoize` which clears the memoized function's
   * cache when it exceeds `MAX_MEMOIZE_SIZE`.
   *
   * @private
   * @param {Function} func The function to have its output memoized.
   * @returns {Function} Returns the new memoized function.
   */
  function memoizeCapped(func) {
    var result = memoize(func, function(key) {
      if (cache.size === MAX_MEMOIZE_SIZE) {
        cache.clear();
      }
      return key;
    });
  
    var cache = result.cache;
    return result;
  }
  
  module.exports = memoizeCapped;
  
  },{"./memoize":429}],413:[function(require,module,exports){
  var getNative = require('./_getNative');
  
  /* Built-in method references that are verified to be native. */
  var nativeCreate = getNative(Object, 'create');
  
  module.exports = nativeCreate;
  
  },{"./_getNative":390}],414:[function(require,module,exports){
  /** Used for built-in method references. */
  var objectProto = Object.prototype;
  
  /**
   * Used to resolve the
   * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
   * of values.
   */
  var nativeObjectToString = objectProto.toString;
  
  /**
   * Converts `value` to a string using `Object.prototype.toString`.
   *
   * @private
   * @param {*} value The value to convert.
   * @returns {string} Returns the converted string.
   */
  function objectToString(value) {
    return nativeObjectToString.call(value);
  }
  
  module.exports = objectToString;
  
  },{}],415:[function(require,module,exports){
  /**
   * Creates a unary function that invokes `func` with its argument transformed.
   *
   * @private
   * @param {Function} func The function to wrap.
   * @param {Function} transform The argument transform.
   * @returns {Function} Returns the new function.
   */
  function overArg(func, transform) {
    return function(arg) {
      return func(transform(arg));
    };
  }
  
  module.exports = overArg;
  
  },{}],416:[function(require,module,exports){
  var freeGlobal = require('./_freeGlobal');
  
  /** Detect free variable `self`. */
  var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
  
  /** Used as a reference to the global object. */
  var root = freeGlobal || freeSelf || Function('return this')();
  
  module.exports = root;
  
  },{"./_freeGlobal":388}],417:[function(require,module,exports){
  var memoizeCapped = require('./_memoizeCapped');
  
  /** Used to match property names within property paths. */
  var rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
  
  /** Used to match backslashes in property paths. */
  var reEscapeChar = /\\(\\)?/g;
  
  /**
   * Converts `string` to a property path array.
   *
   * @private
   * @param {string} string The string to convert.
   * @returns {Array} Returns the property path array.
   */
  var stringToPath = memoizeCapped(function(string) {
    var result = [];
    if (string.charCodeAt(0) === 46 /* . */) {
      result.push('');
    }
    string.replace(rePropName, function(match, number, quote, subString) {
      result.push(quote ? subString.replace(reEscapeChar, '$1') : (number || match));
    });
    return result;
  });
  
  module.exports = stringToPath;
  
  },{"./_memoizeCapped":412}],418:[function(require,module,exports){
  var isSymbol = require('./isSymbol');
  
  /** Used as references for various `Number` constants. */
  var INFINITY = 1 / 0;
  
  /**
   * Converts `value` to a string key if it's not a string or symbol.
   *
   * @private
   * @param {*} value The value to inspect.
   * @returns {string|symbol} Returns the key.
   */
  function toKey(value) {
    if (typeof value == 'string' || isSymbol(value)) {
      return value;
    }
    var result = (value + '');
    return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
  }
  
  module.exports = toKey;
  
  },{"./isSymbol":428}],419:[function(require,module,exports){
  /** Used for built-in method references. */
  var funcProto = Function.prototype;
  
  /** Used to resolve the decompiled source of functions. */
  var funcToString = funcProto.toString;
  
  /**
   * Converts `func` to its source code.
   *
   * @private
   * @param {Function} func The function to convert.
   * @returns {string} Returns the source code.
   */
  function toSource(func) {
    if (func != null) {
      try {
        return funcToString.call(func);
      } catch (e) {}
      try {
        return (func + '');
      } catch (e) {}
    }
    return '';
  }
  
  module.exports = toSource;
  
  },{}],420:[function(require,module,exports){
  var isObject = require('./isObject'),
      now = require('./now'),
      toNumber = require('./toNumber');
  
  /** Error message constants. */
  var FUNC_ERROR_TEXT = 'Expected a function';
  
  /* Built-in method references for those with the same name as other `lodash` methods. */
  var nativeMax = Math.max,
      nativeMin = Math.min;
  
  /**
   * Creates a debounced function that delays invoking `func` until after `wait`
   * milliseconds have elapsed since the last time the debounced function was
   * invoked. The debounced function comes with a `cancel` method to cancel
   * delayed `func` invocations and a `flush` method to immediately invoke them.
   * Provide `options` to indicate whether `func` should be invoked on the
   * leading and/or trailing edge of the `wait` timeout. The `func` is invoked
   * with the last arguments provided to the debounced function. Subsequent
   * calls to the debounced function return the result of the last `func`
   * invocation.
   *
   * **Note:** If `leading` and `trailing` options are `true`, `func` is
   * invoked on the trailing edge of the timeout only if the debounced function
   * is invoked more than once during the `wait` timeout.
   *
   * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
   * until to the next tick, similar to `setTimeout` with a timeout of `0`.
   *
   * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
   * for details over the differences between `_.debounce` and `_.throttle`.
   *
   * @static
   * @memberOf _
   * @since 0.1.0
   * @category Function
   * @param {Function} func The function to debounce.
   * @param {number} [wait=0] The number of milliseconds to delay.
   * @param {Object} [options={}] The options object.
   * @param {boolean} [options.leading=false]
   *  Specify invoking on the leading edge of the timeout.
   * @param {number} [options.maxWait]
   *  The maximum time `func` is allowed to be delayed before it's invoked.
   * @param {boolean} [options.trailing=true]
   *  Specify invoking on the trailing edge of the timeout.
   * @returns {Function} Returns the new debounced function.
   * @example
   *
   * // Avoid costly calculations while the window size is in flux.
   * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
   *
   * // Invoke `sendMail` when clicked, debouncing subsequent calls.
   * jQuery(element).on('click', _.debounce(sendMail, 300, {
   *   'leading': true,
   *   'trailing': false
   * }));
   *
   * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
   * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
   * var source = new EventSource('/stream');
   * jQuery(source).on('message', debounced);
   *
   * // Cancel the trailing debounced invocation.
   * jQuery(window).on('popstate', debounced.cancel);
   */
  function debounce(func, wait, options) {
    var lastArgs,
        lastThis,
        maxWait,
        result,
        timerId,
        lastCallTime,
        lastInvokeTime = 0,
        leading = false,
        maxing = false,
        trailing = true;
  
    if (typeof func != 'function') {
      throw new TypeError(FUNC_ERROR_TEXT);
    }
    wait = toNumber(wait) || 0;
    if (isObject(options)) {
      leading = !!options.leading;
      maxing = 'maxWait' in options;
      maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
      trailing = 'trailing' in options ? !!options.trailing : trailing;
    }
  
    function invokeFunc(time) {
      var args = lastArgs,
          thisArg = lastThis;
  
      lastArgs = lastThis = undefined;
      lastInvokeTime = time;
      result = func.apply(thisArg, args);
      return result;
    }
  
    function leadingEdge(time) {
      // Reset any `maxWait` timer.
      lastInvokeTime = time;
      // Start the timer for the trailing edge.
      timerId = setTimeout(timerExpired, wait);
      // Invoke the leading edge.
      return leading ? invokeFunc(time) : result;
    }
  
    function remainingWait(time) {
      var timeSinceLastCall = time - lastCallTime,
          timeSinceLastInvoke = time - lastInvokeTime,
          timeWaiting = wait - timeSinceLastCall;
  
      return maxing
        ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
        : timeWaiting;
    }
  
    function shouldInvoke(time) {
      var timeSinceLastCall = time - lastCallTime,
          timeSinceLastInvoke = time - lastInvokeTime;
  
      // Either this is the first call, activity has stopped and we're at the
      // trailing edge, the system time has gone backwards and we're treating
      // it as the trailing edge, or we've hit the `maxWait` limit.
      return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
        (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
    }
  
    function timerExpired() {
      var time = now();
      if (shouldInvoke(time)) {
        return trailingEdge(time);
      }
      // Restart the timer.
      timerId = setTimeout(timerExpired, remainingWait(time));
    }
  
    function trailingEdge(time) {
      timerId = undefined;
  
      // Only invoke if we have `lastArgs` which means `func` has been
      // debounced at least once.
      if (trailing && lastArgs) {
        return invokeFunc(time);
      }
      lastArgs = lastThis = undefined;
      return result;
    }
  
    function cancel() {
      if (timerId !== undefined) {
        clearTimeout(timerId);
      }
      lastInvokeTime = 0;
      lastArgs = lastCallTime = lastThis = timerId = undefined;
    }
  
    function flush() {
      return timerId === undefined ? result : trailingEdge(now());
    }
  
    function debounced() {
      var time = now(),
          isInvoking = shouldInvoke(time);
  
      lastArgs = arguments;
      lastThis = this;
      lastCallTime = time;
  
      if (isInvoking) {
        if (timerId === undefined) {
          return leadingEdge(lastCallTime);
        }
        if (maxing) {
          // Handle invocations in a tight loop.
          timerId = setTimeout(timerExpired, wait);
          return invokeFunc(lastCallTime);
        }
      }
      if (timerId === undefined) {
        timerId = setTimeout(timerExpired, wait);
      }
      return result;
    }
    debounced.cancel = cancel;
    debounced.flush = flush;
    return debounced;
  }
  
  module.exports = debounce;
  
  },{"./isObject":425,"./now":430,"./toNumber":431}],421:[function(require,module,exports){
  /**
   * Performs a
   * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
   * comparison between two values to determine if they are equivalent.
   *
   * @static
   * @memberOf _
   * @since 4.0.0
   * @category Lang
   * @param {*} value The value to compare.
   * @param {*} other The other value to compare.
   * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
   * @example
   *
   * var object = { 'a': 1 };
   * var other = { 'a': 1 };
   *
   * _.eq(object, object);
   * // => true
   *
   * _.eq(object, other);
   * // => false
   *
   * _.eq('a', 'a');
   * // => true
   *
   * _.eq('a', Object('a'));
   * // => false
   *
   * _.eq(NaN, NaN);
   * // => true
   */
  function eq(value, other) {
    return value === other || (value !== value && other !== other);
  }
  
  module.exports = eq;
  
  },{}],422:[function(require,module,exports){
  var baseGet = require('./_baseGet');
  
  /**
   * Gets the value at `path` of `object`. If the resolved value is
   * `undefined`, the `defaultValue` is returned in its place.
   *
   * @static
   * @memberOf _
   * @since 3.7.0
   * @category Object
   * @param {Object} object The object to query.
   * @param {Array|string} path The path of the property to get.
   * @param {*} [defaultValue] The value returned for `undefined` resolved values.
   * @returns {*} Returns the resolved value.
   * @example
   *
   * var object = { 'a': [{ 'b': { 'c': 3 } }] };
   *
   * _.get(object, 'a[0].b.c');
   * // => 3
   *
   * _.get(object, ['a', '0', 'b', 'c']);
   * // => 3
   *
   * _.get(object, 'a.b.c', 'default');
   * // => 'default'
   */
  function get(object, path, defaultValue) {
    var result = object == null ? undefined : baseGet(object, path);
    return result === undefined ? defaultValue : result;
  }
  
  module.exports = get;
  
  },{"./_baseGet":382}],423:[function(require,module,exports){
  /**
   * Checks if `value` is classified as an `Array` object.
   *
   * @static
   * @memberOf _
   * @since 0.1.0
   * @category Lang
   * @param {*} value The value to check.
   * @returns {boolean} Returns `true` if `value` is an array, else `false`.
   * @example
   *
   * _.isArray([1, 2, 3]);
   * // => true
   *
   * _.isArray(document.body.children);
   * // => false
   *
   * _.isArray('abc');
   * // => false
   *
   * _.isArray(_.noop);
   * // => false
   */
  var isArray = Array.isArray;
  
  module.exports = isArray;
  
  },{}],424:[function(require,module,exports){
  var baseGetTag = require('./_baseGetTag'),
      isObject = require('./isObject');
  
  /** `Object#toString` result references. */
  var asyncTag = '[object AsyncFunction]',
      funcTag = '[object Function]',
      genTag = '[object GeneratorFunction]',
      proxyTag = '[object Proxy]';
  
  /**
   * Checks if `value` is classified as a `Function` object.
   *
   * @static
   * @memberOf _
   * @since 0.1.0
   * @category Lang
   * @param {*} value The value to check.
   * @returns {boolean} Returns `true` if `value` is a function, else `false`.
   * @example
   *
   * _.isFunction(_);
   * // => true
   *
   * _.isFunction(/abc/);
   * // => false
   */
  function isFunction(value) {
    if (!isObject(value)) {
      return false;
    }
    // The use of `Object#toString` avoids issues with the `typeof` operator
    // in Safari 9 which returns 'object' for typed arrays and other constructors.
    var tag = baseGetTag(value);
    return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;
  }
  
  module.exports = isFunction;
  
  },{"./_baseGetTag":383,"./isObject":425}],425:[function(require,module,exports){
  /**
   * Checks if `value` is the
   * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
   * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
   *
   * @static
   * @memberOf _
   * @since 0.1.0
   * @category Lang
   * @param {*} value The value to check.
   * @returns {boolean} Returns `true` if `value` is an object, else `false`.
   * @example
   *
   * _.isObject({});
   * // => true
   *
   * _.isObject([1, 2, 3]);
   * // => true
   *
   * _.isObject(_.noop);
   * // => true
   *
   * _.isObject(null);
   * // => false
   */
  function isObject(value) {
    var type = typeof value;
    return value != null && (type == 'object' || type == 'function');
  }
  
  module.exports = isObject;
  
  },{}],426:[function(require,module,exports){
  /**
   * Checks if `value` is object-like. A value is object-like if it's not `null`
   * and has a `typeof` result of "object".
   *
   * @static
   * @memberOf _
   * @since 4.0.0
   * @category Lang
   * @param {*} value The value to check.
   * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
   * @example
   *
   * _.isObjectLike({});
   * // => true
   *
   * _.isObjectLike([1, 2, 3]);
   * // => true
   *
   * _.isObjectLike(_.noop);
   * // => false
   *
   * _.isObjectLike(null);
   * // => false
   */
  function isObjectLike(value) {
    return value != null && typeof value == 'object';
  }
  
  module.exports = isObjectLike;
  
  },{}],427:[function(require,module,exports){
  var baseGetTag = require('./_baseGetTag'),
      getPrototype = require('./_getPrototype'),
      isObjectLike = require('./isObjectLike');
  
  /** `Object#toString` result references. */
  var objectTag = '[object Object]';
  
  /** Used for built-in method references. */
  var funcProto = Function.prototype,
      objectProto = Object.prototype;
  
  /** Used to resolve the decompiled source of functions. */
  var funcToString = funcProto.toString;
  
  /** Used to check objects for own properties. */
  var hasOwnProperty = objectProto.hasOwnProperty;
  
  /** Used to infer the `Object` constructor. */
  var objectCtorString = funcToString.call(Object);
  
  /**
   * Checks if `value` is a plain object, that is, an object created by the
   * `Object` constructor or one with a `[[Prototype]]` of `null`.
   *
   * @static
   * @memberOf _
   * @since 0.8.0
   * @category Lang
   * @param {*} value The value to check.
   * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
   * @example
   *
   * function Foo() {
   *   this.a = 1;
   * }
   *
   * _.isPlainObject(new Foo);
   * // => false
   *
   * _.isPlainObject([1, 2, 3]);
   * // => false
   *
   * _.isPlainObject({ 'x': 0, 'y': 0 });
   * // => true
   *
   * _.isPlainObject(Object.create(null));
   * // => true
   */
  function isPlainObject(value) {
    if (!isObjectLike(value) || baseGetTag(value) != objectTag) {
      return false;
    }
    var proto = getPrototype(value);
    if (proto === null) {
      return true;
    }
    var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
    return typeof Ctor == 'function' && Ctor instanceof Ctor &&
      funcToString.call(Ctor) == objectCtorString;
  }
  
  module.exports = isPlainObject;
  
  },{"./_baseGetTag":383,"./_getPrototype":391,"./isObjectLike":426}],428:[function(require,module,exports){
  var baseGetTag = require('./_baseGetTag'),
      isObjectLike = require('./isObjectLike');
  
  /** `Object#toString` result references. */
  var symbolTag = '[object Symbol]';
  
  /**
   * Checks if `value` is classified as a `Symbol` primitive or object.
   *
   * @static
   * @memberOf _
   * @since 4.0.0
   * @category Lang
   * @param {*} value The value to check.
   * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
   * @example
   *
   * _.isSymbol(Symbol.iterator);
   * // => true
   *
   * _.isSymbol('abc');
   * // => false
   */
  function isSymbol(value) {
    return typeof value == 'symbol' ||
      (isObjectLike(value) && baseGetTag(value) == symbolTag);
  }
  
  module.exports = isSymbol;
  
  },{"./_baseGetTag":383,"./isObjectLike":426}],429:[function(require,module,exports){
  var MapCache = require('./_MapCache');
  
  /** Error message constants. */
  var FUNC_ERROR_TEXT = 'Expected a function';
  
  /**
   * Creates a function that memoizes the result of `func`. If `resolver` is
   * provided, it determines the cache key for storing the result based on the
   * arguments provided to the memoized function. By default, the first argument
   * provided to the memoized function is used as the map cache key. The `func`
   * is invoked with the `this` binding of the memoized function.
   *
   * **Note:** The cache is exposed as the `cache` property on the memoized
   * function. Its creation may be customized by replacing the `_.memoize.Cache`
   * constructor with one whose instances implement the
   * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
   * method interface of `clear`, `delete`, `get`, `has`, and `set`.
   *
   * @static
   * @memberOf _
   * @since 0.1.0
   * @category Function
   * @param {Function} func The function to have its output memoized.
   * @param {Function} [resolver] The function to resolve the cache key.
   * @returns {Function} Returns the new memoized function.
   * @example
   *
   * var object = { 'a': 1, 'b': 2 };
   * var other = { 'c': 3, 'd': 4 };
   *
   * var values = _.memoize(_.values);
   * values(object);
   * // => [1, 2]
   *
   * values(other);
   * // => [3, 4]
   *
   * object.a = 2;
   * values(object);
   * // => [1, 2]
   *
   * // Modify the result cache.
   * values.cache.set(object, ['a', 'b']);
   * values(object);
   * // => ['a', 'b']
   *
   * // Replace `_.memoize.Cache`.
   * _.memoize.Cache = WeakMap;
   */
  function memoize(func, resolver) {
    if (typeof func != 'function' || (resolver != null && typeof resolver != 'function')) {
      throw new TypeError(FUNC_ERROR_TEXT);
    }
    var memoized = function() {
      var args = arguments,
          key = resolver ? resolver.apply(this, args) : args[0],
          cache = memoized.cache;
  
      if (cache.has(key)) {
        return cache.get(key);
      }
      var result = func.apply(this, args);
      memoized.cache = cache.set(key, result) || cache;
      return result;
    };
    memoized.cache = new (memoize.Cache || MapCache);
    return memoized;
  }
  
  // Expose `MapCache`.
  memoize.Cache = MapCache;
  
  module.exports = memoize;
  
  },{"./_MapCache":378}],430:[function(require,module,exports){
  var root = require('./_root');
  
  /**
   * Gets the timestamp of the number of milliseconds that have elapsed since
   * the Unix epoch (1 January 1970 00:00:00 UTC).
   *
   * @static
   * @memberOf _
   * @since 2.4.0
   * @category Date
   * @returns {number} Returns the timestamp.
   * @example
   *
   * _.defer(function(stamp) {
   *   console.log(_.now() - stamp);
   * }, _.now());
   * // => Logs the number of milliseconds it took for the deferred invocation.
   */
  var now = function() {
    return root.Date.now();
  };
  
  module.exports = now;
  
  },{"./_root":416}],431:[function(require,module,exports){
  var isObject = require('./isObject'),
      isSymbol = require('./isSymbol');
  
  /** Used as references for various `Number` constants. */
  var NAN = 0 / 0;
  
  /** Used to match leading and trailing whitespace. */
  var reTrim = /^\s+|\s+$/g;
  
  /** Used to detect bad signed hexadecimal string values. */
  var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
  
  /** Used to detect binary string values. */
  var reIsBinary = /^0b[01]+$/i;
  
  /** Used to detect octal string values. */
  var reIsOctal = /^0o[0-7]+$/i;
  
  /** Built-in method references without a dependency on `root`. */
  var freeParseInt = parseInt;
  
  /**
   * Converts `value` to a number.
   *
   * @static
   * @memberOf _
   * @since 4.0.0
   * @category Lang
   * @param {*} value The value to process.
   * @returns {number} Returns the number.
   * @example
   *
   * _.toNumber(3.2);
   * // => 3.2
   *
   * _.toNumber(Number.MIN_VALUE);
   * // => 5e-324
   *
   * _.toNumber(Infinity);
   * // => Infinity
   *
   * _.toNumber('3.2');
   * // => 3.2
   */
  function toNumber(value) {
    if (typeof value == 'number') {
      return value;
    }
    if (isSymbol(value)) {
      return NAN;
    }
    if (isObject(value)) {
      var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
      value = isObject(other) ? (other + '') : other;
    }
    if (typeof value != 'string') {
      return value === 0 ? value : +value;
    }
    value = value.replace(reTrim, '');
    var isBinary = reIsBinary.test(value);
    return (isBinary || reIsOctal.test(value))
      ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
      : (reIsBadHex.test(value) ? NAN : +value);
  }
  
  module.exports = toNumber;
  
  },{"./isObject":425,"./isSymbol":428}],432:[function(require,module,exports){
  var baseToString = require('./_baseToString');
  
  /**
   * Converts `value` to a string. An empty string is returned for `null`
   * and `undefined` values. The sign of `-0` is preserved.
   *
   * @static
   * @memberOf _
   * @since 4.0.0
   * @category Lang
   * @param {*} value The value to convert.
   * @returns {string} Returns the converted string.
   * @example
   *
   * _.toString(null);
   * // => ''
   *
   * _.toString(-0);
   * // => '-0'
   *
   * _.toString([1, 2, 3]);
   * // => '1,2,3'
   */
  function toString(value) {
    return value == null ? '' : baseToString(value);
  }
  
  module.exports = toString;
  
  },{"./_baseToString":385}],433:[function(require,module,exports){
  var indexOf = require('./indexOf');
  
      /**
       * Combines an array with all the items of another.
       * Does not allow duplicates and is case and type sensitive.
       */
      function combine(arr1, arr2) {
          if (arr2 == null) {
              return arr1;
          }
  
          var i = -1, len = arr2.length;
          while (++i < len) {
              if (indexOf(arr1, arr2[i]) === -1) {
                  arr1.push(arr2[i]);
              }
          }
  
          return arr1;
      }
      module.exports = combine;
  
  
  },{"./indexOf":440}],434:[function(require,module,exports){
  var indexOf = require('./indexOf');
  
      /**
       * If array contains values.
       */
      function contains(arr, val) {
          return indexOf(arr, val) !== -1;
      }
      module.exports = contains;
  
  
  },{"./indexOf":440}],435:[function(require,module,exports){
  var unique = require('./unique');
  var filter = require('./filter');
  var some = require('./some');
  var contains = require('./contains');
  var slice = require('./slice');
  
  
      /**
       * Return a new Array with elements that aren't present in the other Arrays.
       */
      function difference(arr) {
          var arrs = slice(arguments, 1),
              result = filter(unique(arr), function(needle){
                  return !some(arrs, function(haystack){
                      return contains(haystack, needle);
                  });
              });
          return result;
      }
  
      module.exports = difference;
  
  
  
  },{"./contains":434,"./filter":438,"./slice":444,"./some":445,"./unique":446}],436:[function(require,module,exports){
  var is = require('../lang/is');
  var isArray = require('../lang/isArray');
  var every = require('./every');
  
      /**
       * Compares if both arrays have the same elements
       */
      function equals(a, b, callback){
          callback = callback || is;
  
          if (!isArray(a) || !isArray(b)) {
              return callback(a, b);
          }
  
          if (a.length !== b.length) {
              return false;
          }
  
          return every(a, makeCompare(callback), b);
      }
  
      function makeCompare(callback) {
          return function(value, i) {
              return i in this && callback(value, this[i]);
          };
      }
  
      module.exports = equals;
  
  
  
  },{"../lang/is":454,"../lang/isArray":455,"./every":437}],437:[function(require,module,exports){
  var makeIterator = require('../function/makeIterator_');
  
      /**
       * Array every
       */
      function every(arr, callback, thisObj) {
          callback = makeIterator(callback, thisObj);
          var result = true;
          if (arr == null) {
              return result;
          }
  
          var i = -1, len = arr.length;
          while (++i < len) {
              // we iterate over sparse items since there is no way to make it
              // work properly on IE 7-8. see #64
              if (!callback(arr[i], i, arr) ) {
                  result = false;
                  break;
              }
          }
  
          return result;
      }
  
      module.exports = every;
  
  
  },{"../function/makeIterator_":448}],438:[function(require,module,exports){
  var makeIterator = require('../function/makeIterator_');
  
      /**
       * Array filter
       */
      function filter(arr, callback, thisObj) {
          callback = makeIterator(callback, thisObj);
          var results = [];
          if (arr == null) {
              return results;
          }
  
          var i = -1, len = arr.length, value;
          while (++i < len) {
              value = arr[i];
              if (callback(value, i, arr)) {
                  results.push(value);
              }
          }
  
          return results;
      }
  
      module.exports = filter;
  
  
  
  },{"../function/makeIterator_":448}],439:[function(require,module,exports){
  
  
      /**
       * Array forEach
       */
      function forEach(arr, callback, thisObj) {
          if (arr == null) {
              return;
          }
          var i = -1,
              len = arr.length;
          while (++i < len) {
              // we iterate over sparse items since there is no way to make it
              // work properly on IE 7-8. see #64
              if ( callback.call(thisObj, arr[i], i, arr) === false ) {
                  break;
              }
          }
      }
  
      module.exports = forEach;
  
  
  
  },{}],440:[function(require,module,exports){
  
  
      /**
       * Array.indexOf
       */
      function indexOf(arr, item, fromIndex) {
          fromIndex = fromIndex || 0;
          if (arr == null) {
              return -1;
          }
  
          var len = arr.length,
              i = fromIndex < 0 ? len + fromIndex : fromIndex;
          while (i < len) {
              // we iterate over sparse items since there is no way to make it
              // work properly on IE 7-8. see #64
              if (arr[i] === item) {
                  return i;
              }
  
              i++;
          }
  
          return -1;
      }
  
      module.exports = indexOf;
  
  
  },{}],441:[function(require,module,exports){
  var unique = require('./unique');
  var filter = require('./filter');
  var every = require('./every');
  var contains = require('./contains');
  var slice = require('./slice');
  
  
      /**
       * Return a new Array with elements common to all Arrays.
       * - based on underscore.js implementation
       */
      function intersection(arr) {
          var arrs = slice(arguments, 1),
              result = filter(unique(arr), function(needle){
                  return every(arrs, function(haystack){
                      return contains(haystack, needle);
                  });
              });
          return result;
      }
  
      module.exports = intersection;
  
  
  
  },{"./contains":434,"./every":437,"./filter":438,"./slice":444,"./unique":446}],442:[function(require,module,exports){
  var makeIterator = require('../function/makeIterator_');
  
      /**
       * Array map
       */
      function map(arr, callback, thisObj) {
          callback = makeIterator(callback, thisObj);
          var results = [];
          if (arr == null){
              return results;
          }
  
          var i = -1, len = arr.length;
          while (++i < len) {
              results[i] = callback(arr[i], i, arr);
          }
  
          return results;
      }
  
       module.exports = map;
  
  
  },{"../function/makeIterator_":448}],443:[function(require,module,exports){
  var indexOf = require('./indexOf');
  
      /**
       * Remove a single item from the array.
       * (it won't remove duplicates, just a single item)
       */
      function remove(arr, item){
          var idx = indexOf(arr, item);
          if (idx !== -1) arr.splice(idx, 1);
      }
  
      module.exports = remove;
  
  
  },{"./indexOf":440}],444:[function(require,module,exports){
  
  
      /**
       * Create slice of source array or array-like object
       */
      function slice(arr, start, end){
          var len = arr.length;
  
          if (start == null) {
              start = 0;
          } else if (start < 0) {
              start = Math.max(len + start, 0);
          } else {
              start = Math.min(start, len);
          }
  
          if (end == null) {
              end = len;
          } else if (end < 0) {
              end = Math.max(len + end, 0);
          } else {
              end = Math.min(end, len);
          }
  
          var result = [];
          while (start < end) {
              result.push(arr[start++]);
          }
  
          return result;
      }
  
      module.exports = slice;
  
  
  
  },{}],445:[function(require,module,exports){
  var makeIterator = require('../function/makeIterator_');
  
      /**
       * Array some
       */
      function some(arr, callback, thisObj) {
          callback = makeIterator(callback, thisObj);
          var result = false;
          if (arr == null) {
              return result;
          }
  
          var i = -1, len = arr.length;
          while (++i < len) {
              // we iterate over sparse items since there is no way to make it
              // work properly on IE 7-8. see #64
              if ( callback(arr[i], i, arr) ) {
                  result = true;
                  break;
              }
          }
  
          return result;
      }
  
      module.exports = some;
  
  
  },{"../function/makeIterator_":448}],446:[function(require,module,exports){
  var filter = require('./filter');
  
      /**
       * @return {array} Array of unique items
       */
      function unique(arr, compare){
          compare = compare || isEqual;
          return filter(arr, function(item, i, arr){
              var n = arr.length;
              while (++i < n) {
                  if ( compare(item, arr[i]) ) {
                      return false;
                  }
              }
              return true;
          });
      }
  
      function isEqual(a, b){
          return a === b;
      }
  
      module.exports = unique;
  
  
  
  },{"./filter":438}],447:[function(require,module,exports){
  
  
      /**
       * Returns the first argument provided to it.
       */
      function identity(val){
          return val;
      }
  
      module.exports = identity;
  
  
  
  },{}],448:[function(require,module,exports){
  var identity = require('./identity');
  var prop = require('./prop');
  var deepMatches = require('../object/deepMatches');
  
      /**
       * Converts argument into a valid iterator.
       * Used internally on most array/object/collection methods that receives a
       * callback/iterator providing a shortcut syntax.
       */
      function makeIterator(src, thisObj){
          if (src == null) {
              return identity;
          }
          switch(typeof src) {
              case 'function':
                  // function is the first to improve perf (most common case)
                  // also avoid using `Function#call` if not needed, which boosts
                  // perf a lot in some cases
                  return (typeof thisObj !== 'undefined')? function(val, i, arr){
                      return src.call(thisObj, val, i, arr);
                  } : src;
              case 'object':
                  return function(val){
                      return deepMatches(val, src);
                  };
              case 'string':
              case 'number':
                  return prop(src);
          }
      }
  
      module.exports = makeIterator;
  
  
  
  },{"../object/deepMatches":463,"./identity":447,"./prop":449}],449:[function(require,module,exports){
  
  
      /**
       * Returns a function that gets a property of the passed object
       */
      function prop(name){
          return function(obj){
              return obj[name];
          };
      }
  
      module.exports = prop;
  
  
  
  },{}],450:[function(require,module,exports){
  var kindOf = require('./kindOf');
  var isPlainObject = require('./isPlainObject');
  var mixIn = require('../object/mixIn');
  
      /**
       * Clone native types.
       */
      function clone(val){
          switch (kindOf(val)) {
              case 'Object':
                  return cloneObject(val);
              case 'Array':
                  return cloneArray(val);
              case 'RegExp':
                  return cloneRegExp(val);
              case 'Date':
                  return cloneDate(val);
              default:
                  return val;
          }
      }
  
      function cloneObject(source) {
          if (isPlainObject(source)) {
              return mixIn({}, source);
          } else {
              return source;
          }
      }
  
      function cloneRegExp(r) {
          var flags = '';
          flags += r.multiline ? 'm' : '';
          flags += r.global ? 'g' : '';
          flags += r.ignoreCase ? 'i' : '';
          return new RegExp(r.source, flags);
      }
  
      function cloneDate(date) {
          return new Date(+date);
      }
  
      function cloneArray(arr) {
          return arr.slice();
      }
  
      module.exports = clone;
  
  
  
  },{"../object/mixIn":471,"./isPlainObject":459,"./kindOf":461}],451:[function(require,module,exports){
  var mixIn = require('../object/mixIn');
  
      /**
       * Create Object using prototypal inheritance and setting custom properties.
       * - Mix between Douglas Crockford Prototypal Inheritance <http://javascript.crockford.com/prototypal.html> and the EcmaScript 5 `Object.create()` method.
       * @param {object} parent    Parent Object.
       * @param {object} [props] Object properties.
       * @return {object} Created object.
       */
      function createObject(parent, props){
          function F(){}
          F.prototype = parent;
          return mixIn(new F(), props);
  
      }
      module.exports = createObject;
  
  
  
  },{"../object/mixIn":471}],452:[function(require,module,exports){
  var clone = require('./clone');
  var forOwn = require('../object/forOwn');
  var kindOf = require('./kindOf');
  var isPlainObject = require('./isPlainObject');
  
      /**
       * Recursively clone native types.
       */
      function deepClone(val, instanceClone) {
          switch ( kindOf(val) ) {
              case 'Object':
                  return cloneObject(val, instanceClone);
              case 'Array':
                  return cloneArray(val, instanceClone);
              default:
                  return clone(val);
          }
      }
  
      function cloneObject(source, instanceClone) {
          if (isPlainObject(source)) {
              var out = {};
              forOwn(source, function(val, key) {
                  this[key] = deepClone(val, instanceClone);
              }, out);
              return out;
          } else if (instanceClone) {
              return instanceClone(source);
          } else {
              return source;
          }
      }
  
      function cloneArray(arr, instanceClone) {
          var out = [],
              i = -1,
              n = arr.length,
              val;
          while (++i < n) {
              out[i] = deepClone(arr[i], instanceClone);
          }
          return out;
      }
  
      module.exports = deepClone;
  
  
  
  
  },{"../object/forOwn":468,"./clone":450,"./isPlainObject":459,"./kindOf":461}],453:[function(require,module,exports){
  var is = require('./is');
  var isObject = require('./isObject');
  var isArray = require('./isArray');
  var objEquals = require('../object/equals');
  var arrEquals = require('../array/equals');
  
      /**
       * Recursively checks for same properties and values.
       */
      function deepEquals(a, b, callback){
          callback = callback || is;
  
          var bothObjects = isObject(a) && isObject(b);
          var bothArrays = !bothObjects && isArray(a) && isArray(b);
  
          if (!bothObjects && !bothArrays) {
              return callback(a, b);
          }
  
          function compare(a, b){
              return deepEquals(a, b, callback);
          }
  
          var method = bothObjects ? objEquals : arrEquals;
          return method(a, b, compare);
      }
  
      module.exports = deepEquals;
  
  
  
  },{"../array/equals":436,"../object/equals":465,"./is":454,"./isArray":455,"./isObject":458}],454:[function(require,module,exports){
  
  
      /**
       * Check if both arguments are egal.
       */
      function is(x, y){
          // implementation borrowed from harmony:egal spec
          if (x === y) {
            // 0 === -0, but they are not identical
            return x !== 0 || 1 / x === 1 / y;
          }
  
          // NaN !== NaN, but they are identical.
          // NaNs are the only non-reflexive value, i.e., if x !== x,
          // then x is a NaN.
          // isNaN is broken: it converts its argument to number, so
          // isNaN("foo") => true
          return x !== x && y !== y;
      }
  
      module.exports = is;
  
  
  
  },{}],455:[function(require,module,exports){
  var isKind = require('./isKind');
      /**
       */
      var isArray = Array.isArray || function (val) {
          return isKind(val, 'Array');
      };
      module.exports = isArray;
  
  
  },{"./isKind":456}],456:[function(require,module,exports){
  var kindOf = require('./kindOf');
      /**
       * Check if value is from a specific "kind".
       */
      function isKind(val, kind){
          return kindOf(val) === kind;
      }
      module.exports = isKind;
  
  
  },{"./kindOf":461}],457:[function(require,module,exports){
  var isKind = require('./isKind');
      /**
       */
      function isNumber(val) {
          return isKind(val, 'Number');
      }
      module.exports = isNumber;
  
  
  },{"./isKind":456}],458:[function(require,module,exports){
  var isKind = require('./isKind');
      /**
       */
      function isObject(val) {
          return isKind(val, 'Object');
      }
      module.exports = isObject;
  
  
  },{"./isKind":456}],459:[function(require,module,exports){
  
  
      /**
       * Checks if the value is created by the `Object` constructor.
       */
      function isPlainObject(value) {
          return (!!value && typeof value === 'object' &&
              value.constructor === Object);
      }
  
      module.exports = isPlainObject;
  
  
  
  },{}],460:[function(require,module,exports){
  var isKind = require('./isKind');
      /**
       */
      function isRegExp(val) {
          return isKind(val, 'RegExp');
      }
      module.exports = isRegExp;
  
  
  },{"./isKind":456}],461:[function(require,module,exports){
  
  
      var _rKind = /^\[object (.*)\]$/,
          _toString = Object.prototype.toString,
          UNDEF;
  
      /**
       * Gets the "kind" of value. (e.g. "String", "Number", etc)
       */
      function kindOf(val) {
          if (val === null) {
              return 'Null';
          } else if (val === UNDEF) {
              return 'Undefined';
          } else {
              return _rKind.exec( _toString.call(val) )[1];
          }
      }
      module.exports = kindOf;
  
  
  },{}],462:[function(require,module,exports){
  
  
      /**
       * Typecast a value to a String, using an empty string value for null or
       * undefined.
       */
      function toString(val){
          return val == null ? '' : val.toString();
      }
  
      module.exports = toString;
  
  
  
  },{}],463:[function(require,module,exports){
  var forOwn = require('./forOwn');
  var isArray = require('../lang/isArray');
  
      function containsMatch(array, pattern) {
          var i = -1, length = array.length;
          while (++i < length) {
              if (deepMatches(array[i], pattern)) {
                  return true;
              }
          }
  
          return false;
      }
  
      function matchArray(target, pattern) {
          var i = -1, patternLength = pattern.length;
          while (++i < patternLength) {
              if (!containsMatch(target, pattern[i])) {
                  return false;
              }
          }
  
          return true;
      }
  
      function matchObject(target, pattern) {
          var result = true;
          forOwn(pattern, function(val, key) {
              if (!deepMatches(target[key], val)) {
                  // Return false to break out of forOwn early
                  return (result = false);
              }
          });
  
          return result;
      }
  
      /**
       * Recursively check if the objects match.
       */
      function deepMatches(target, pattern){
          if (target && typeof target === 'object') {
              if (isArray(target) && isArray(pattern)) {
                  return matchArray(target, pattern);
              } else {
                  return matchObject(target, pattern);
              }
          } else {
              return target === pattern;
          }
      }
  
      module.exports = deepMatches;
  
  
  
  },{"../lang/isArray":455,"./forOwn":468}],464:[function(require,module,exports){
  var forOwn = require('./forOwn');
  var isPlainObject = require('../lang/isPlainObject');
  
      /**
       * Mixes objects into the target object, recursively mixing existing child
       * objects.
       */
      function deepMixIn(target, objects) {
          var i = 0,
              n = arguments.length,
              obj;
  
          while(++i < n){
              obj = arguments[i];
              if (obj) {
                  forOwn(obj, copyProp, target);
              }
          }
  
          return target;
      }
  
      function copyProp(val, key) {
          var existing = this[key];
          if (isPlainObject(val) && isPlainObject(existing)) {
              deepMixIn(existing, val);
          } else {
              this[key] = val;
          }
      }
  
      module.exports = deepMixIn;
  
  
  
  },{"../lang/isPlainObject":459,"./forOwn":468}],465:[function(require,module,exports){
  var hasOwn = require('./hasOwn');
  var every = require('./every');
  var isObject = require('../lang/isObject');
  var is = require('../lang/is');
  
      // Makes a function to compare the object values from the specified compare
      // operation callback.
      function makeCompare(callback) {
          return function(value, key) {
              return hasOwn(this, key) && callback(value, this[key]);
          };
      }
  
      function checkProperties(value, key) {
          return hasOwn(this, key);
      }
  
      /**
       * Checks if two objects have the same keys and values.
       */
      function equals(a, b, callback) {
          callback = callback || is;
  
          if (!isObject(a) || !isObject(b)) {
              return callback(a, b);
          }
  
          return (every(a, makeCompare(callback), b) &&
                  every(b, checkProperties, a));
      }
  
      module.exports = equals;
  
  
  },{"../lang/is":454,"../lang/isObject":458,"./every":466,"./hasOwn":469}],466:[function(require,module,exports){
  var forOwn = require('./forOwn');
  var makeIterator = require('../function/makeIterator_');
  
      /**
       * Object every
       */
      function every(obj, callback, thisObj) {
          callback = makeIterator(callback, thisObj);
          var result = true;
          forOwn(obj, function(val, key) {
              // we consider any falsy values as "false" on purpose so shorthand
              // syntax can be used to check property existence
              if (!callback(val, key, obj)) {
                  result = false;
                  return false; // break
              }
          });
          return result;
      }
  
      module.exports = every;
  
  
  
  },{"../function/makeIterator_":448,"./forOwn":468}],467:[function(require,module,exports){
  var hasOwn = require('./hasOwn');
  
      var _hasDontEnumBug,
          _dontEnums;
  
      function checkDontEnum(){
          _dontEnums = [
                  'toString',
                  'toLocaleString',
                  'valueOf',
                  'hasOwnProperty',
                  'isPrototypeOf',
                  'propertyIsEnumerable',
                  'constructor'
              ];
  
          _hasDontEnumBug = true;
  
          for (var key in {'toString': null}) {
              _hasDontEnumBug = false;
          }
      }
  
      /**
       * Similar to Array/forEach but works over object properties and fixes Don't
       * Enum bug on IE.
       * based on: http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation
       */
      function forIn(obj, fn, thisObj){
          var key, i = 0;
          // no need to check if argument is a real object that way we can use
          // it for arrays, functions, date, etc.
  
          //post-pone check till needed
          if (_hasDontEnumBug == null) checkDontEnum();
  
          for (key in obj) {
              if (exec(fn, obj, key, thisObj) === false) {
                  break;
              }
          }
  
  
          if (_hasDontEnumBug) {
              var ctor = obj.constructor,
                  isProto = !!ctor && obj === ctor.prototype;
  
              while (key = _dontEnums[i++]) {
                  // For constructor, if it is a prototype object the constructor
                  // is always non-enumerable unless defined otherwise (and
                  // enumerated above).  For non-prototype objects, it will have
                  // to be defined on this object, since it cannot be defined on
                  // any prototype objects.
                  //
                  // For other [[DontEnum]] properties, check if the value is
                  // different than Object prototype value.
                  if (
                      (key !== 'constructor' ||
                          (!isProto && hasOwn(obj, key))) &&
                      obj[key] !== Object.prototype[key]
                  ) {
                      if (exec(fn, obj, key, thisObj) === false) {
                          break;
                      }
                  }
              }
          }
      }
  
      function exec(fn, obj, key, thisObj){
          return fn.call(thisObj, obj[key], key, obj);
      }
  
      module.exports = forIn;
  
  
  
  },{"./hasOwn":469}],468:[function(require,module,exports){
  var hasOwn = require('./hasOwn');
  var forIn = require('./forIn');
  
      /**
       * Similar to Array/forEach but works over object properties and fixes Don't
       * Enum bug on IE.
       * based on: http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation
       */
      function forOwn(obj, fn, thisObj){
          forIn(obj, function(val, key){
              if (hasOwn(obj, key)) {
                  return fn.call(thisObj, obj[key], key, obj);
              }
          });
      }
  
      module.exports = forOwn;
  
  
  
  },{"./forIn":467,"./hasOwn":469}],469:[function(require,module,exports){
  
  
      /**
       * Safer Object.hasOwnProperty
       */
       function hasOwn(obj, prop){
           return Object.prototype.hasOwnProperty.call(obj, prop);
       }
  
       module.exports = hasOwn;
  
  
  
  },{}],470:[function(require,module,exports){
  var hasOwn = require('./hasOwn');
  var deepClone = require('../lang/deepClone');
  var isObject = require('../lang/isObject');
  
      /**
       * Deep merge objects.
       */
      function merge() {
          var i = 1,
              key, val, obj, target;
  
          // make sure we don't modify source element and it's properties
          // objects are passed by reference
          target = deepClone( arguments[0] );
  
          while (obj = arguments[i++]) {
              for (key in obj) {
                  if ( ! hasOwn(obj, key) ) {
                      continue;
                  }
  
                  val = obj[key];
  
                  if ( isObject(val) && isObject(target[key]) ){
                      // inception, deep merge objects
                      target[key] = merge(target[key], val);
                  } else {
                      // make sure arrays, regexp, date, objects are cloned
                      target[key] = deepClone(val);
                  }
  
              }
          }
  
          return target;
      }
  
      module.exports = merge;
  
  
  
  },{"../lang/deepClone":452,"../lang/isObject":458,"./hasOwn":469}],471:[function(require,module,exports){
  var forOwn = require('./forOwn');
  
      /**
      * Combine properties from all the objects into first one.
      * - This method affects target object in place, if you want to create a new Object pass an empty object as first param.
      * @param {object} target    Target Object
      * @param {...object} objects    Objects to be combined (0...n objects).
      * @return {object} Target Object.
      */
      function mixIn(target, objects){
          var i = 0,
              n = arguments.length,
              obj;
          while(++i < n){
              obj = arguments[i];
              if (obj != null) {
                  forOwn(obj, copyProp, target);
              }
          }
          return target;
      }
  
      function copyProp(val, key){
          this[key] = val;
      }
  
      module.exports = mixIn;
  
  
  },{"./forOwn":468}],472:[function(require,module,exports){
  var slice = require('../array/slice');
  
      /**
       * Return a copy of the object, filtered to only have values for the whitelisted keys.
       */
      function pick(obj, var_keys){
          var keys = typeof arguments[1] !== 'string'? arguments[1] : slice(arguments, 1),
              out = {},
              i = 0, key;
          while (key = keys[i++]) {
              out[key] = obj[key];
          }
          return out;
      }
  
      module.exports = pick;
  
  
  
  },{"../array/slice":444}],473:[function(require,module,exports){
  
      /**
       * Contains all Unicode white-spaces. Taken from
       * http://en.wikipedia.org/wiki/Whitespace_character.
       */
      module.exports = [
          ' ', '\n', '\r', '\t', '\f', '\v', '\u00A0', '\u1680', '\u180E',
          '\u2000', '\u2001', '\u2002', '\u2003', '\u2004', '\u2005', '\u2006',
          '\u2007', '\u2008', '\u2009', '\u200A', '\u2028', '\u2029', '\u202F',
          '\u205F', '\u3000'
      ];
  
  
  },{}],474:[function(require,module,exports){
  var toString = require('../lang/toString');
  
      /**
       * Escape RegExp string chars.
       */
      function escapeRegExp(str) {
          return toString(str).replace(/\W/g,'\\$&');
      }
  
      module.exports = escapeRegExp;
  
  
  
  },{"../lang/toString":462}],475:[function(require,module,exports){
  var toString = require('../lang/toString');
  var WHITE_SPACES = require('./WHITE_SPACES');
      /**
       * Remove chars from beginning of string.
       */
      function ltrim(str, chars) {
          str = toString(str);
          chars = chars || WHITE_SPACES;
  
          var start = 0,
              len = str.length,
              charLen = chars.length,
              found = true,
              i, c;
  
          while (found && start < len) {
              found = false;
              i = -1;
              c = str.charAt(start);
  
              while (++i < charLen) {
                  if (c === chars[i]) {
                      found = true;
                      start++;
                      break;
                  }
              }
          }
  
          return (start >= len) ? '' : str.substr(start, len);
      }
  
      module.exports = ltrim;
  
  
  },{"../lang/toString":462,"./WHITE_SPACES":473}],476:[function(require,module,exports){
  var toString = require('../lang/toString');
  var WHITE_SPACES = require('./WHITE_SPACES');
      /**
       * Remove chars from end of string.
       */
      function rtrim(str, chars) {
          str = toString(str);
          chars = chars || WHITE_SPACES;
  
          var end = str.length - 1,
              charLen = chars.length,
              found = true,
              i, c;
  
          while (found && end >= 0) {
              found = false;
              i = -1;
              c = str.charAt(end);
  
              while (++i < charLen) {
                  if (c === chars[i]) {
                      found = true;
                      end--;
                      break;
                  }
              }
          }
  
          return (end >= 0) ? str.substring(0, end + 1) : '';
      }
  
      module.exports = rtrim;
  
  
  },{"../lang/toString":462,"./WHITE_SPACES":473}],477:[function(require,module,exports){
  var toString = require('../lang/toString');
  var WHITE_SPACES = require('./WHITE_SPACES');
  var ltrim = require('./ltrim');
  var rtrim = require('./rtrim');
      /**
       * Remove white-spaces from beginning and end of string.
       */
      function trim(str, chars) {
          str = toString(str);
          chars = chars || WHITE_SPACES;
          return ltrim(rtrim(str, chars), chars);
      }
  
      module.exports = trim;
  
  
  },{"../lang/toString":462,"./WHITE_SPACES":473,"./ltrim":475,"./rtrim":476}],478:[function(require,module,exports){
  
  
      /**
       * Get current time in miliseconds
       */
      function now(){
          // yes, we defer the work to another function to allow mocking it
          // during the tests
          return now.get();
      }
  
      now.get = (typeof Date.now === 'function')? Date.now : function(){
          return +(new Date());
      };
  
      module.exports = now;
  
  
  
  },{}],479:[function(require,module,exports){
  /**
   * Helpers.
   */
  
  var s = 1000;
  var m = s * 60;
  var h = m * 60;
  var d = h * 24;
  var y = d * 365.25;
  
  /**
   * Parse or format the given `val`.
   *
   * Options:
   *
   *  - `long` verbose formatting [false]
   *
   * @param {String|Number} val
   * @param {Object} [options]
   * @throws {Error} throw an error if val is not a non-empty string or a number
   * @return {String|Number}
   * @api public
   */
  
  module.exports = function(val, options) {
    options = options || {};
    var type = typeof val;
    if (type === 'string' && val.length > 0) {
      return parse(val);
    } else if (type === 'number' && isNaN(val) === false) {
      return options.long ? fmtLong(val) : fmtShort(val);
    }
    throw new Error(
      'val is not a non-empty string or a valid number. val=' +
        JSON.stringify(val)
    );
  };
  
  /**
   * Parse the given `str` and return milliseconds.
   *
   * @param {String} str
   * @return {Number}
   * @api private
   */
  
  function parse(str) {
    str = String(str);
    if (str.length > 100) {
      return;
    }
    var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(
      str
    );
    if (!match) {
      return;
    }
    var n = parseFloat(match[1]);
    var type = (match[2] || 'ms').toLowerCase();
    switch (type) {
      case 'years':
      case 'year':
      case 'yrs':
      case 'yr':
      case 'y':
        return n * y;
      case 'days':
      case 'day':
      case 'd':
        return n * d;
      case 'hours':
      case 'hour':
      case 'hrs':
      case 'hr':
      case 'h':
        return n * h;
      case 'minutes':
      case 'minute':
      case 'mins':
      case 'min':
      case 'm':
        return n * m;
      case 'seconds':
      case 'second':
      case 'secs':
      case 'sec':
      case 's':
        return n * s;
      case 'milliseconds':
      case 'millisecond':
      case 'msecs':
      case 'msec':
      case 'ms':
        return n;
      default:
        return undefined;
    }
  }
  
  /**
   * Short format for `ms`.
   *
   * @param {Number} ms
   * @return {String}
   * @api private
   */
  
  function fmtShort(ms) {
    if (ms >= d) {
      return Math.round(ms / d) + 'd';
    }
    if (ms >= h) {
      return Math.round(ms / h) + 'h';
    }
    if (ms >= m) {
      return Math.round(ms / m) + 'm';
    }
    if (ms >= s) {
      return Math.round(ms / s) + 's';
    }
    return ms + 'ms';
  }
  
  /**
   * Long format for `ms`.
   *
   * @param {Number} ms
   * @return {String}
   * @api private
   */
  
  function fmtLong(ms) {
    return plural(ms, d, 'day') ||
      plural(ms, h, 'hour') ||
      plural(ms, m, 'minute') ||
      plural(ms, s, 'second') ||
      ms + ' ms';
  }
  
  /**
   * Pluralization helper.
   */
  
  function plural(ms, n, name) {
    if (ms < n) {
      return;
    }
    if (ms < n * 1.5) {
      return Math.floor(ms / n) + ' ' + name;
    }
    return Math.ceil(ms / n) + ' ' + name + 's';
  }
  
  },{}],480:[function(require,module,exports){
  /*
  object-assign
  (c) Sindre Sorhus
  @license MIT
  */
  
  'use strict';
  /* eslint-disable no-unused-vars */
  var getOwnPropertySymbols = Object.getOwnPropertySymbols;
  var hasOwnProperty = Object.prototype.hasOwnProperty;
  var propIsEnumerable = Object.prototype.propertyIsEnumerable;
  
  function toObject(val) {
    if (val === null || val === undefined) {
      throw new TypeError('Object.assign cannot be called with null or undefined');
    }
  
    return Object(val);
  }
  
  function shouldUseNative() {
    try {
      if (!Object.assign) {
        return false;
      }
  
      // Detect buggy property enumeration order in older V8 versions.
  
      // https://bugs.chromium.org/p/v8/issues/detail?id=4118
      var test1 = new String('abc');  // eslint-disable-line no-new-wrappers
      test1[5] = 'de';
      if (Object.getOwnPropertyNames(test1)[0] === '5') {
        return false;
      }
  
      // https://bugs.chromium.org/p/v8/issues/detail?id=3056
      var test2 = {};
      for (var i = 0; i < 10; i++) {
        test2['_' + String.fromCharCode(i)] = i;
      }
      var order2 = Object.getOwnPropertyNames(test2).map(function (n) {
        return test2[n];
      });
      if (order2.join('') !== '0123456789') {
        return false;
      }
  
      // https://bugs.chromium.org/p/v8/issues/detail?id=3056
      var test3 = {};
      'abcdefghijklmnopqrst'.split('').forEach(function (letter) {
        test3[letter] = letter;
      });
      if (Object.keys(Object.assign({}, test3)).join('') !==
          'abcdefghijklmnopqrst') {
        return false;
      }
  
      return true;
    } catch (err) {
      // We don't expect any of the above to throw, but better to be safe.
      return false;
    }
  }
  
  module.exports = shouldUseNative() ? Object.assign : function (target, source) {
    var from;
    var to = toObject(target);
    var symbols;
  
    for (var s = 1; s < arguments.length; s++) {
      from = Object(arguments[s]);
  
      for (var key in from) {
        if (hasOwnProperty.call(from, key)) {
          to[key] = from[key];
        }
      }
  
      if (getOwnPropertySymbols) {
        symbols = getOwnPropertySymbols(from);
        for (var i = 0; i < symbols.length; i++) {
          if (propIsEnumerable.call(from, symbols[i])) {
            to[symbols[i]] = from[symbols[i]];
          }
        }
      }
    }
  
    return to;
  };
  
  },{}],481:[function(require,module,exports){
  // shim for using process in browser
  var process = module.exports = {};
  
  // cached from whatever global is present so that test runners that stub it
  // don't break things.  But we need to wrap it in a try catch in case it is
  // wrapped in strict mode code which doesn't define any globals.  It's inside a
  // function because try/catches deoptimize in certain engines.
  
  var cachedSetTimeout;
  var cachedClearTimeout;
  
  function defaultSetTimout() {
      throw new Error('setTimeout has not been defined');
  }
  function defaultClearTimeout () {
      throw new Error('clearTimeout has not been defined');
  }
  (function () {
      try {
          if (typeof setTimeout === 'function') {
              cachedSetTimeout = setTimeout;
          } else {
              cachedSetTimeout = defaultSetTimout;
          }
      } catch (e) {
          cachedSetTimeout = defaultSetTimout;
      }
      try {
          if (typeof clearTimeout === 'function') {
              cachedClearTimeout = clearTimeout;
          } else {
              cachedClearTimeout = defaultClearTimeout;
          }
      } catch (e) {
          cachedClearTimeout = defaultClearTimeout;
      }
  } ())
  function runTimeout(fun) {
      if (cachedSetTimeout === setTimeout) {
          //normal enviroments in sane situations
          return setTimeout(fun, 0);
      }
      // if setTimeout wasn't available but was latter defined
      if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
          cachedSetTimeout = setTimeout;
          return setTimeout(fun, 0);
      }
      try {
          // when when somebody has screwed with setTimeout but no I.E. maddness
          return cachedSetTimeout(fun, 0);
      } catch(e){
          try {
              // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
              return cachedSetTimeout.call(null, fun, 0);
          } catch(e){
              // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
              return cachedSetTimeout.call(this, fun, 0);
          }
      }
  
  
  }
  function runClearTimeout(marker) {
      if (cachedClearTimeout === clearTimeout) {
          //normal enviroments in sane situations
          return clearTimeout(marker);
      }
      // if clearTimeout wasn't available but was latter defined
      if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
          cachedClearTimeout = clearTimeout;
          return clearTimeout(marker);
      }
      try {
          // when when somebody has screwed with setTimeout but no I.E. maddness
          return cachedClearTimeout(marker);
      } catch (e){
          try {
              // When we are in I.E. but the script has been evaled so I.E. doesn't  trust the global object when called normally
              return cachedClearTimeout.call(null, marker);
          } catch (e){
              // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
              // Some versions of I.E. have different rules for clearTimeout vs setTimeout
              return cachedClearTimeout.call(this, marker);
          }
      }
  
  
  
  }
  var queue = [];
  var draining = false;
  var currentQueue;
  var queueIndex = -1;
  
  function cleanUpNextTick() {
      if (!draining || !currentQueue) {
          return;
      }
      draining = false;
      if (currentQueue.length) {
          queue = currentQueue.concat(queue);
      } else {
          queueIndex = -1;
      }
      if (queue.length) {
          drainQueue();
      }
  }
  
  function drainQueue() {
      if (draining) {
          return;
      }
      var timeout = runTimeout(cleanUpNextTick);
      draining = true;
  
      var len = queue.length;
      while(len) {
          currentQueue = queue;
          queue = [];
          while (++queueIndex < len) {
              if (currentQueue) {
                  currentQueue[queueIndex].run();
              }
          }
          queueIndex = -1;
          len = queue.length;
      }
      currentQueue = null;
      draining = false;
      runClearTimeout(timeout);
  }
  
  process.nextTick = function (fun) {
      var args = new Array(arguments.length - 1);
      if (arguments.length > 1) {
          for (var i = 1; i < arguments.length; i++) {
              args[i - 1] = arguments[i];
          }
      }
      queue.push(new Item(fun, args));
      if (queue.length === 1 && !draining) {
          runTimeout(drainQueue);
      }
  };
  
  // v8 likes predictible objects
  function Item(fun, array) {
      this.fun = fun;
      this.array = array;
  }
  Item.prototype.run = function () {
      this.fun.apply(null, this.array);
  };
  process.title = 'browser';
  process.browser = true;
  process.env = {};
  process.argv = [];
  process.version = ''; // empty string to avoid regexp issues
  process.versions = {};
  
  function noop() {}
  
  process.on = noop;
  process.addListener = noop;
  process.once = noop;
  process.off = noop;
  process.removeListener = noop;
  process.removeAllListeners = noop;
  process.emit = noop;
  
  process.binding = function (name) {
      throw new Error('process.binding is not supported');
  };
  
  process.cwd = function () { return '/' };
  process.chdir = function (dir) {
      throw new Error('process.chdir is not supported');
  };
  process.umask = function() { return 0; };
  
  },{}],482:[function(require,module,exports){
  (function (global){
  "use strict";
  
  /* global module, require */
  module.exports = function () {
  
      "use strict";
  
      var globalObject = void 0;
  
      // Seek the global object
      if (global !== undefined) {
          globalObject = global;
      } else if (window !== undefined && window.document) {
          globalObject = window;
      } else {
          globalObject = self;
      }
  
      /**
       * thatLooksLikeAPromiseToMe()
       *
       * Duck-types a promise.
       *
       * @param {object} o
       * @return {bool} True if this resembles a promise
       */
      function thatLooksLikeAPromiseToMe(o) {
          return o && typeof o.then === "function" && typeof o.catch === "function";
      }
  
      /**
       * promisify()
       *
       * Transforms callback-based function -- func(arg1, arg2 .. argN, callback) -- into
       * an ES6-compatible Promise. Promisify provides a default callback of the form (error, result)
       * and rejects when `error` is truthy. You can also supply settings object as the second argument.
       *
       * @param {function} original - The function to promisify
       * @param {object} settings - Settings object
       * @param {object} settings.thisArg - A `this` context to use. If not set, assume `settings` _is_ `thisArg`
       * @param {bool} settings.multiArgs - Should multiple arguments be returned as an array?
       * @return {function} A promisified version of `original`
       */
      return function promisify(original, settings) {
  
          return function () {
              for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
                  args[_key] = arguments[_key];
              }
  
              var returnMultipleArguments = settings && settings.multiArgs;
  
              var target = void 0;
              if (settings && settings.thisArg) {
                  target = settings.thisArg;
              } else if (settings) {
                  target = settings;
              }
  
              // Return the promisified function
              return new globalObject.Promise(function (resolve, reject) {
                  // Append the callback bound to the context
                  args.push(function callback(err) {
  
                      if (err) {
                          return reject(err);
                      }
  
                      for (var _len2 = arguments.length, values = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
                          values[_key2 - 1] = arguments[_key2];
                      }
  
                      if (false === !!returnMultipleArguments) {
                          return resolve(values[0]);
                      }
  
                      resolve(values);
                  });
  
                  // Call the function
                  var response = original.apply(target, args);
  
                  // If it looks like original already returns a promise,
                  // then just resolve with that promise. Hopefully, the callback function we added will just be ignored.
                  if (thatLooksLikeAPromiseToMe(response)) {
                      resolve(response);
                  }
              });
          };
      };
  }();
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{}],483:[function(require,module,exports){
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   */
  
  'use strict';
  
  if ("production" !== 'production') {
    var invariant = require('fbjs/lib/invariant');
    var warning = require('fbjs/lib/warning');
    var ReactPropTypesSecret = require('./lib/ReactPropTypesSecret');
    var loggedTypeFailures = {};
  }
  
  /**
   * Assert that the values match with the type specs.
   * Error messages are memorized and will only be shown once.
   *
   * @param {object} typeSpecs Map of name to a ReactPropType
   * @param {object} values Runtime values that need to be type-checked
   * @param {string} location e.g. "prop", "context", "child context"
   * @param {string} componentName Name of the component for error messages.
   * @param {?Function} getStack Returns the component stack.
   * @private
   */
  function checkPropTypes(typeSpecs, values, location, componentName, getStack) {
    if ("production" !== 'production') {
      for (var typeSpecName in typeSpecs) {
        if (typeSpecs.hasOwnProperty(typeSpecName)) {
          var error;
          // Prop type validation may throw. In case they do, we don't want to
          // fail the render phase where it didn't fail before. So we log it.
          // After these have been cleaned up, we'll let them throw.
          try {
            // This is intentionally an invariant that gets caught. It's the same
            // behavior as without this statement except with a better message.
            invariant(typeof typeSpecs[typeSpecName] === 'function', '%s: %s type `%s` is invalid; it must be a function, usually from ' + 'the `prop-types` package, but received `%s`.', componentName || 'React class', location, typeSpecName, typeof typeSpecs[typeSpecName]);
            error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, ReactPropTypesSecret);
          } catch (ex) {
            error = ex;
          }
          warning(!error || error instanceof Error, '%s: type specification of %s `%s` is invalid; the type checker ' + 'function must return `null` or an `Error` but returned a %s. ' + 'You may have forgotten to pass an argument to the type checker ' + 'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' + 'shape all require an argument).', componentName || 'React class', location, typeSpecName, typeof error);
          if (error instanceof Error && !(error.message in loggedTypeFailures)) {
            // Only monitor this failure once because there tends to be a lot of the
            // same error.
            loggedTypeFailures[error.message] = true;
  
            var stack = getStack ? getStack() : '';
  
            warning(false, 'Failed %s type: %s%s', location, error.message, stack != null ? stack : '');
          }
        }
      }
    }
  }
  
  module.exports = checkPropTypes;
  
  },{"./lib/ReactPropTypesSecret":487,"fbjs/lib/invariant":344,"fbjs/lib/warning":348}],484:[function(require,module,exports){
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   */
  
  'use strict';
  
  var emptyFunction = require('fbjs/lib/emptyFunction');
  var invariant = require('fbjs/lib/invariant');
  var ReactPropTypesSecret = require('./lib/ReactPropTypesSecret');
  
  module.exports = function() {
    function shim(props, propName, componentName, location, propFullName, secret) {
      if (secret === ReactPropTypesSecret) {
        // It is still safe when called from React.
        return;
      }
      invariant(
        false,
        'Calling PropTypes validators directly is not supported by the `prop-types` package. ' +
        'Use PropTypes.checkPropTypes() to call them. ' +
        'Read more at http://fb.me/use-check-prop-types'
      );
    };
    shim.isRequired = shim;
    function getShim() {
      return shim;
    };
    // Important!
    // Keep this list in sync with production version in `./factoryWithTypeCheckers.js`.
    var ReactPropTypes = {
      array: shim,
      bool: shim,
      func: shim,
      number: shim,
      object: shim,
      string: shim,
      symbol: shim,
  
      any: shim,
      arrayOf: getShim,
      element: shim,
      instanceOf: getShim,
      node: shim,
      objectOf: getShim,
      oneOf: getShim,
      oneOfType: getShim,
      shape: getShim,
      exact: getShim
    };
  
    ReactPropTypes.checkPropTypes = emptyFunction;
    ReactPropTypes.PropTypes = ReactPropTypes;
  
    return ReactPropTypes;
  };
  
  },{"./lib/ReactPropTypesSecret":487,"fbjs/lib/emptyFunction":338,"fbjs/lib/invariant":344}],485:[function(require,module,exports){
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   */
  
  'use strict';
  
  var emptyFunction = require('fbjs/lib/emptyFunction');
  var invariant = require('fbjs/lib/invariant');
  var warning = require('fbjs/lib/warning');
  var assign = require('object-assign');
  
  var ReactPropTypesSecret = require('./lib/ReactPropTypesSecret');
  var checkPropTypes = require('./checkPropTypes');
  
  module.exports = function(isValidElement, throwOnDirectAccess) {
    /* global Symbol */
    var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
    var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec.
  
    /**
     * Returns the iterator method function contained on the iterable object.
     *
     * Be sure to invoke the function with the iterable as context:
     *
     *     var iteratorFn = getIteratorFn(myIterable);
     *     if (iteratorFn) {
     *       var iterator = iteratorFn.call(myIterable);
     *       ...
     *     }
     *
     * @param {?object} maybeIterable
     * @return {?function}
     */
    function getIteratorFn(maybeIterable) {
      var iteratorFn = maybeIterable && (ITERATOR_SYMBOL && maybeIterable[ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL]);
      if (typeof iteratorFn === 'function') {
        return iteratorFn;
      }
    }
  
    /**
     * Collection of methods that allow declaration and validation of props that are
     * supplied to React components. Example usage:
     *
     *   var Props = require('ReactPropTypes');
     *   var MyArticle = React.createClass({
     *     propTypes: {
     *       // An optional string prop named "description".
     *       description: Props.string,
     *
     *       // A required enum prop named "category".
     *       category: Props.oneOf(['News','Photos']).isRequired,
     *
     *       // A prop named "dialog" that requires an instance of Dialog.
     *       dialog: Props.instanceOf(Dialog).isRequired
     *     },
     *     render: function() { ... }
     *   });
     *
     * A more formal specification of how these methods are used:
     *
     *   type := array|bool|func|object|number|string|oneOf([...])|instanceOf(...)
     *   decl := ReactPropTypes.{type}(.isRequired)?
     *
     * Each and every declaration produces a function with the same signature. This
     * allows the creation of custom validation functions. For example:
     *
     *  var MyLink = React.createClass({
     *    propTypes: {
     *      // An optional string or URI prop named "href".
     *      href: function(props, propName, componentName) {
     *        var propValue = props[propName];
     *        if (propValue != null && typeof propValue !== 'string' &&
     *            !(propValue instanceof URI)) {
     *          return new Error(
     *            'Expected a string or an URI for ' + propName + ' in ' +
     *            componentName
     *          );
     *        }
     *      }
     *    },
     *    render: function() {...}
     *  });
     *
     * @internal
     */
  
    var ANONYMOUS = '<<anonymous>>';
  
    // Important!
    // Keep this list in sync with production version in `./factoryWithThrowingShims.js`.
    var ReactPropTypes = {
      array: createPrimitiveTypeChecker('array'),
      bool: createPrimitiveTypeChecker('boolean'),
      func: createPrimitiveTypeChecker('function'),
      number: createPrimitiveTypeChecker('number'),
      object: createPrimitiveTypeChecker('object'),
      string: createPrimitiveTypeChecker('string'),
      symbol: createPrimitiveTypeChecker('symbol'),
  
      any: createAnyTypeChecker(),
      arrayOf: createArrayOfTypeChecker,
      element: createElementTypeChecker(),
      instanceOf: createInstanceTypeChecker,
      node: createNodeChecker(),
      objectOf: createObjectOfTypeChecker,
      oneOf: createEnumTypeChecker,
      oneOfType: createUnionTypeChecker,
      shape: createShapeTypeChecker,
      exact: createStrictShapeTypeChecker,
    };
  
    /**
     * inlined Object.is polyfill to avoid requiring consumers ship their own
     * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
     */
    /*eslint-disable no-self-compare*/
    function is(x, y) {
      // SameValue algorithm
      if (x === y) {
        // Steps 1-5, 7-10
        // Steps 6.b-6.e: +0 != -0
        return x !== 0 || 1 / x === 1 / y;
      } else {
        // Step 6.a: NaN == NaN
        return x !== x && y !== y;
      }
    }
    /*eslint-enable no-self-compare*/
  
    /**
     * We use an Error-like object for backward compatibility as people may call
     * PropTypes directly and inspect their output. However, we don't use real
     * Errors anymore. We don't inspect their stack anyway, and creating them
     * is prohibitively expensive if they are created too often, such as what
     * happens in oneOfType() for any type before the one that matched.
     */
    function PropTypeError(message) {
      this.message = message;
      this.stack = '';
    }
    // Make `instanceof Error` still work for returned errors.
    PropTypeError.prototype = Error.prototype;
  
    function createChainableTypeChecker(validate) {
      if ("production" !== 'production') {
        var manualPropTypeCallCache = {};
        var manualPropTypeWarningCount = 0;
      }
      function checkType(isRequired, props, propName, componentName, location, propFullName, secret) {
        componentName = componentName || ANONYMOUS;
        propFullName = propFullName || propName;
  
        if (secret !== ReactPropTypesSecret) {
          if (throwOnDirectAccess) {
            // New behavior only for users of `prop-types` package
            invariant(
              false,
              'Calling PropTypes validators directly is not supported by the `prop-types` package. ' +
              'Use `PropTypes.checkPropTypes()` to call them. ' +
              'Read more at http://fb.me/use-check-prop-types'
            );
          } else if ("production" !== 'production' && typeof console !== 'undefined') {
            // Old behavior for people using React.PropTypes
            var cacheKey = componentName + ':' + propName;
            if (
              !manualPropTypeCallCache[cacheKey] &&
              // Avoid spamming the console because they are often not actionable except for lib authors
              manualPropTypeWarningCount < 3
            ) {
              warning(
                false,
                'You are manually calling a React.PropTypes validation ' +
                'function for the `%s` prop on `%s`. This is deprecated ' +
                'and will throw in the standalone `prop-types` package. ' +
                'You may be seeing this warning due to a third-party PropTypes ' +
                'library. See https://fb.me/react-warning-dont-call-proptypes ' + 'for details.',
                propFullName,
                componentName
              );
              manualPropTypeCallCache[cacheKey] = true;
              manualPropTypeWarningCount++;
            }
          }
        }
        if (props[propName] == null) {
          if (isRequired) {
            if (props[propName] === null) {
              return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required ' + ('in `' + componentName + '`, but its value is `null`.'));
            }
            return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required in ' + ('`' + componentName + '`, but its value is `undefined`.'));
          }
          return null;
        } else {
          return validate(props, propName, componentName, location, propFullName);
        }
      }
  
      var chainedCheckType = checkType.bind(null, false);
      chainedCheckType.isRequired = checkType.bind(null, true);
  
      return chainedCheckType;
    }
  
    function createPrimitiveTypeChecker(expectedType) {
      function validate(props, propName, componentName, location, propFullName, secret) {
        var propValue = props[propName];
        var propType = getPropType(propValue);
        if (propType !== expectedType) {
          // `propValue` being instance of, say, date/regexp, pass the 'object'
          // check, but we can offer a more precise error message here rather than
          // 'of type `object`'.
          var preciseType = getPreciseType(propValue);
  
          return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + preciseType + '` supplied to `' + componentName + '`, expected ') + ('`' + expectedType + '`.'));
        }
        return null;
      }
      return createChainableTypeChecker(validate);
    }
  
    function createAnyTypeChecker() {
      return createChainableTypeChecker(emptyFunction.thatReturnsNull);
    }
  
    function createArrayOfTypeChecker(typeChecker) {
      function validate(props, propName, componentName, location, propFullName) {
        if (typeof typeChecker !== 'function') {
          return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside arrayOf.');
        }
        var propValue = props[propName];
        if (!Array.isArray(propValue)) {
          var propType = getPropType(propValue);
          return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an array.'));
        }
        for (var i = 0; i < propValue.length; i++) {
          var error = typeChecker(propValue, i, componentName, location, propFullName + '[' + i + ']', ReactPropTypesSecret);
          if (error instanceof Error) {
            return error;
          }
        }
        return null;
      }
      return createChainableTypeChecker(validate);
    }
  
    function createElementTypeChecker() {
      function validate(props, propName, componentName, location, propFullName) {
        var propValue = props[propName];
        if (!isValidElement(propValue)) {
          var propType = getPropType(propValue);
          return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected a single ReactElement.'));
        }
        return null;
      }
      return createChainableTypeChecker(validate);
    }
  
    function createInstanceTypeChecker(expectedClass) {
      function validate(props, propName, componentName, location, propFullName) {
        if (!(props[propName] instanceof expectedClass)) {
          var expectedClassName = expectedClass.name || ANONYMOUS;
          var actualClassName = getClassName(props[propName]);
          return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + actualClassName + '` supplied to `' + componentName + '`, expected ') + ('instance of `' + expectedClassName + '`.'));
        }
        return null;
      }
      return createChainableTypeChecker(validate);
    }
  
    function createEnumTypeChecker(expectedValues) {
      if (!Array.isArray(expectedValues)) {
        "production" !== 'production' ? warning(false, 'Invalid argument supplied to oneOf, expected an instance of array.') : void 0;
        return emptyFunction.thatReturnsNull;
      }
  
      function validate(props, propName, componentName, location, propFullName) {
        var propValue = props[propName];
        for (var i = 0; i < expectedValues.length; i++) {
          if (is(propValue, expectedValues[i])) {
            return null;
          }
        }
  
        var valuesString = JSON.stringify(expectedValues);
        return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of value `' + propValue + '` ' + ('supplied to `' + componentName + '`, expected one of ' + valuesString + '.'));
      }
      return createChainableTypeChecker(validate);
    }
  
    function createObjectOfTypeChecker(typeChecker) {
      function validate(props, propName, componentName, location, propFullName) {
        if (typeof typeChecker !== 'function') {
          return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside objectOf.');
        }
        var propValue = props[propName];
        var propType = getPropType(propValue);
        if (propType !== 'object') {
          return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an object.'));
        }
        for (var key in propValue) {
          if (propValue.hasOwnProperty(key)) {
            var error = typeChecker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);
            if (error instanceof Error) {
              return error;
            }
          }
        }
        return null;
      }
      return createChainableTypeChecker(validate);
    }
  
    function createUnionTypeChecker(arrayOfTypeCheckers) {
      if (!Array.isArray(arrayOfTypeCheckers)) {
        "production" !== 'production' ? warning(false, 'Invalid argument supplied to oneOfType, expected an instance of array.') : void 0;
        return emptyFunction.thatReturnsNull;
      }
  
      for (var i = 0; i < arrayOfTypeCheckers.length; i++) {
        var checker = arrayOfTypeCheckers[i];
        if (typeof checker !== 'function') {
          warning(
            false,
            'Invalid argument supplied to oneOfType. Expected an array of check functions, but ' +
            'received %s at index %s.',
            getPostfixForTypeWarning(checker),
            i
          );
          return emptyFunction.thatReturnsNull;
        }
      }
  
      function validate(props, propName, componentName, location, propFullName) {
        for (var i = 0; i < arrayOfTypeCheckers.length; i++) {
          var checker = arrayOfTypeCheckers[i];
          if (checker(props, propName, componentName, location, propFullName, ReactPropTypesSecret) == null) {
            return null;
          }
        }
  
        return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`.'));
      }
      return createChainableTypeChecker(validate);
    }
  
    function createNodeChecker() {
      function validate(props, propName, componentName, location, propFullName) {
        if (!isNode(props[propName])) {
          return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`, expected a ReactNode.'));
        }
        return null;
      }
      return createChainableTypeChecker(validate);
    }
  
    function createShapeTypeChecker(shapeTypes) {
      function validate(props, propName, componentName, location, propFullName) {
        var propValue = props[propName];
        var propType = getPropType(propValue);
        if (propType !== 'object') {
          return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.'));
        }
        for (var key in shapeTypes) {
          var checker = shapeTypes[key];
          if (!checker) {
            continue;
          }
          var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);
          if (error) {
            return error;
          }
        }
        return null;
      }
      return createChainableTypeChecker(validate);
    }
  
    function createStrictShapeTypeChecker(shapeTypes) {
      function validate(props, propName, componentName, location, propFullName) {
        var propValue = props[propName];
        var propType = getPropType(propValue);
        if (propType !== 'object') {
          return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.'));
        }
        // We need to check all keys in case some are required but missing from
        // props.
        var allKeys = assign({}, props[propName], shapeTypes);
        for (var key in allKeys) {
          var checker = shapeTypes[key];
          if (!checker) {
            return new PropTypeError(
              'Invalid ' + location + ' `' + propFullName + '` key `' + key + '` supplied to `' + componentName + '`.' +
              '\nBad object: ' + JSON.stringify(props[propName], null, '  ') +
              '\nValid keys: ' +  JSON.stringify(Object.keys(shapeTypes), null, '  ')
            );
          }
          var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);
          if (error) {
            return error;
          }
        }
        return null;
      }
  
      return createChainableTypeChecker(validate);
    }
  
    function isNode(propValue) {
      switch (typeof propValue) {
        case 'number':
        case 'string':
        case 'undefined':
          return true;
        case 'boolean':
          return !propValue;
        case 'object':
          if (Array.isArray(propValue)) {
            return propValue.every(isNode);
          }
          if (propValue === null || isValidElement(propValue)) {
            return true;
          }
  
          var iteratorFn = getIteratorFn(propValue);
          if (iteratorFn) {
            var iterator = iteratorFn.call(propValue);
            var step;
            if (iteratorFn !== propValue.entries) {
              while (!(step = iterator.next()).done) {
                if (!isNode(step.value)) {
                  return false;
                }
              }
            } else {
              // Iterator will provide entry [k,v] tuples rather than values.
              while (!(step = iterator.next()).done) {
                var entry = step.value;
                if (entry) {
                  if (!isNode(entry[1])) {
                    return false;
                  }
                }
              }
            }
          } else {
            return false;
          }
  
          return true;
        default:
          return false;
      }
    }
  
    function isSymbol(propType, propValue) {
      // Native Symbol.
      if (propType === 'symbol') {
        return true;
      }
  
      // 19.4.3.5 Symbol.prototype[@@toStringTag] === 'Symbol'
      if (propValue['@@toStringTag'] === 'Symbol') {
        return true;
      }
  
      // Fallback for non-spec compliant Symbols which are polyfilled.
      if (typeof Symbol === 'function' && propValue instanceof Symbol) {
        return true;
      }
  
      return false;
    }
  
    // Equivalent of `typeof` but with special handling for array and regexp.
    function getPropType(propValue) {
      var propType = typeof propValue;
      if (Array.isArray(propValue)) {
        return 'array';
      }
      if (propValue instanceof RegExp) {
        // Old webkits (at least until Android 4.0) return 'function' rather than
        // 'object' for typeof a RegExp. We'll normalize this here so that /bla/
        // passes PropTypes.object.
        return 'object';
      }
      if (isSymbol(propType, propValue)) {
        return 'symbol';
      }
      return propType;
    }
  
    // This handles more types than `getPropType`. Only used for error messages.
    // See `createPrimitiveTypeChecker`.
    function getPreciseType(propValue) {
      if (typeof propValue === 'undefined' || propValue === null) {
        return '' + propValue;
      }
      var propType = getPropType(propValue);
      if (propType === 'object') {
        if (propValue instanceof Date) {
          return 'date';
        } else if (propValue instanceof RegExp) {
          return 'regexp';
        }
      }
      return propType;
    }
  
    // Returns a string that is postfixed to a warning about an invalid type.
    // For example, "undefined" or "of type array"
    function getPostfixForTypeWarning(value) {
      var type = getPreciseType(value);
      switch (type) {
        case 'array':
        case 'object':
          return 'an ' + type;
        case 'boolean':
        case 'date':
        case 'regexp':
          return 'a ' + type;
        default:
          return type;
      }
    }
  
    // Returns class name of the object, if any.
    function getClassName(propValue) {
      if (!propValue.constructor || !propValue.constructor.name) {
        return ANONYMOUS;
      }
      return propValue.constructor.name;
    }
  
    ReactPropTypes.checkPropTypes = checkPropTypes;
    ReactPropTypes.PropTypes = ReactPropTypes;
  
    return ReactPropTypes;
  };
  
  },{"./checkPropTypes":483,"./lib/ReactPropTypesSecret":487,"fbjs/lib/emptyFunction":338,"fbjs/lib/invariant":344,"fbjs/lib/warning":348,"object-assign":480}],486:[function(require,module,exports){
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   */
  
  if ("production" !== 'production') {
    var REACT_ELEMENT_TYPE = (typeof Symbol === 'function' &&
      Symbol.for &&
      Symbol.for('react.element')) ||
      0xeac7;
  
    var isValidElement = function(object) {
      return typeof object === 'object' &&
        object !== null &&
        object.$$typeof === REACT_ELEMENT_TYPE;
    };
  
    // By explicitly using `prop-types` you are opting into new development behavior.
    // http://fb.me/prop-types-in-prod
    var throwOnDirectAccess = true;
    module.exports = require('./factoryWithTypeCheckers')(isValidElement, throwOnDirectAccess);
  } else {
    // By explicitly using `prop-types` you are opting into new production behavior.
    // http://fb.me/prop-types-in-prod
    module.exports = require('./factoryWithThrowingShims')();
  }
  
  },{"./factoryWithThrowingShims":484,"./factoryWithTypeCheckers":485}],487:[function(require,module,exports){
  /**
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   */
  
  'use strict';
  
  var ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED';
  
  module.exports = ReactPropTypesSecret;
  
  },{}],495:[function(require,module,exports){
  /** @license React v16.2.0
   * react-dom.development.js
   *
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   */
  
  'use strict';
  
  
  
  if ("production" !== "production") {
    (function() {
  'use strict';
  
  var React = require('react');
  var invariant = require('fbjs/lib/invariant');
  var warning = require('fbjs/lib/warning');
  var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
  var _assign = require('object-assign');
  var emptyFunction = require('fbjs/lib/emptyFunction');
  var EventListener = require('fbjs/lib/EventListener');
  var getActiveElement = require('fbjs/lib/getActiveElement');
  var shallowEqual = require('fbjs/lib/shallowEqual');
  var containsNode = require('fbjs/lib/containsNode');
  var focusNode = require('fbjs/lib/focusNode');
  var emptyObject = require('fbjs/lib/emptyObject');
  var checkPropTypes = require('prop-types/checkPropTypes');
  var hyphenateStyleName = require('fbjs/lib/hyphenateStyleName');
  var camelizeStyleName = require('fbjs/lib/camelizeStyleName');
  
  /**
   * WARNING: DO NOT manually require this module.
   * This is a replacement for `invariant(...)` used by the error code system
   * and will _only_ be required by the corresponding babel pass.
   * It always throws.
   */
  
  !React ? invariant(false, 'ReactDOM was loaded before React. Make sure you load the React package before loading ReactDOM.') : void 0;
  
  // These attributes should be all lowercase to allow for
  // case insensitive checks
  var RESERVED_PROPS = {
    children: true,
    dangerouslySetInnerHTML: true,
    defaultValue: true,
    defaultChecked: true,
    innerHTML: true,
    suppressContentEditableWarning: true,
    suppressHydrationWarning: true,
    style: true
  };
  
  function checkMask(value, bitmask) {
    return (value & bitmask) === bitmask;
  }
  
  var DOMPropertyInjection = {
    /**
     * Mapping from normalized, camelcased property names to a configuration that
     * specifies how the associated DOM property should be accessed or rendered.
     */
    MUST_USE_PROPERTY: 0x1,
    HAS_BOOLEAN_VALUE: 0x4,
    HAS_NUMERIC_VALUE: 0x8,
    HAS_POSITIVE_NUMERIC_VALUE: 0x10 | 0x8,
    HAS_OVERLOADED_BOOLEAN_VALUE: 0x20,
    HAS_STRING_BOOLEAN_VALUE: 0x40,
  
    /**
     * Inject some specialized knowledge about the DOM. This takes a config object
     * with the following properties:
     *
     * Properties: object mapping DOM property name to one of the
     * DOMPropertyInjection constants or null. If your attribute isn't in here,
     * it won't get written to the DOM.
     *
     * DOMAttributeNames: object mapping React attribute name to the DOM
     * attribute name. Attribute names not specified use the **lowercase**
     * normalized name.
     *
     * DOMAttributeNamespaces: object mapping React attribute name to the DOM
     * attribute namespace URL. (Attribute names not specified use no namespace.)
     *
     * DOMPropertyNames: similar to DOMAttributeNames but for DOM properties.
     * Property names not specified use the normalized name.
     *
     * DOMMutationMethods: Properties that require special mutation methods. If
     * `value` is undefined, the mutation method should unset the property.
     *
     * @param {object} domPropertyConfig the config as described above.
     */
    injectDOMPropertyConfig: function (domPropertyConfig) {
      var Injection = DOMPropertyInjection;
      var Properties = domPropertyConfig.Properties || {};
      var DOMAttributeNamespaces = domPropertyConfig.DOMAttributeNamespaces || {};
      var DOMAttributeNames = domPropertyConfig.DOMAttributeNames || {};
      var DOMMutationMethods = domPropertyConfig.DOMMutationMethods || {};
  
      for (var propName in Properties) {
        !!properties.hasOwnProperty(propName) ? invariant(false, "injectDOMPropertyConfig(...): You're trying to inject DOM property '%s' which has already been injected. You may be accidentally injecting the same DOM property config twice, or you may be injecting two configs that have conflicting property names.", propName) : void 0;
  
        var lowerCased = propName.toLowerCase();
        var propConfig = Properties[propName];
  
        var propertyInfo = {
          attributeName: lowerCased,
          attributeNamespace: null,
          propertyName: propName,
          mutationMethod: null,
  
          mustUseProperty: checkMask(propConfig, Injection.MUST_USE_PROPERTY),
          hasBooleanValue: checkMask(propConfig, Injection.HAS_BOOLEAN_VALUE),
          hasNumericValue: checkMask(propConfig, Injection.HAS_NUMERIC_VALUE),
          hasPositiveNumericValue: checkMask(propConfig, Injection.HAS_POSITIVE_NUMERIC_VALUE),
          hasOverloadedBooleanValue: checkMask(propConfig, Injection.HAS_OVERLOADED_BOOLEAN_VALUE),
          hasStringBooleanValue: checkMask(propConfig, Injection.HAS_STRING_BOOLEAN_VALUE)
        };
        !(propertyInfo.hasBooleanValue + propertyInfo.hasNumericValue + propertyInfo.hasOverloadedBooleanValue <= 1) ? invariant(false, "DOMProperty: Value can be one of boolean, overloaded boolean, or numeric value, but not a combination: %s", propName) : void 0;
  
        if (DOMAttributeNames.hasOwnProperty(propName)) {
          var attributeName = DOMAttributeNames[propName];
  
          propertyInfo.attributeName = attributeName;
        }
  
        if (DOMAttributeNamespaces.hasOwnProperty(propName)) {
          propertyInfo.attributeNamespace = DOMAttributeNamespaces[propName];
        }
  
        if (DOMMutationMethods.hasOwnProperty(propName)) {
          propertyInfo.mutationMethod = DOMMutationMethods[propName];
        }
  
        // Downcase references to whitelist properties to check for membership
        // without case-sensitivity. This allows the whitelist to pick up
        // `allowfullscreen`, which should be written using the property configuration
        // for `allowFullscreen`
        properties[propName] = propertyInfo;
      }
    }
  };
  
  /* eslint-disable max-len */
  var ATTRIBUTE_NAME_START_CHAR = ":A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
  /* eslint-enable max-len */
  var ATTRIBUTE_NAME_CHAR = ATTRIBUTE_NAME_START_CHAR + "\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
  
  
  var ROOT_ATTRIBUTE_NAME = 'data-reactroot';
  
  /**
   * Map from property "standard name" to an object with info about how to set
   * the property in the DOM. Each object contains:
   *
   * attributeName:
   *   Used when rendering markup or with `*Attribute()`.
   * attributeNamespace
   * propertyName:
   *   Used on DOM node instances. (This includes properties that mutate due to
   *   external factors.)
   * mutationMethod:
   *   If non-null, used instead of the property or `setAttribute()` after
   *   initial render.
   * mustUseProperty:
   *   Whether the property must be accessed and mutated as an object property.
   * hasBooleanValue:
   *   Whether the property should be removed when set to a falsey value.
   * hasNumericValue:
   *   Whether the property must be numeric or parse as a numeric and should be
   *   removed when set to a falsey value.
   * hasPositiveNumericValue:
   *   Whether the property must be positive numeric or parse as a positive
   *   numeric and should be removed when set to a falsey value.
   * hasOverloadedBooleanValue:
   *   Whether the property can be used as a flag as well as with a value.
   *   Removed when strictly equal to false; present without a value when
   *   strictly equal to true; present with a value otherwise.
   */
  var properties = {};
  
  /**
   * Checks whether a property name is a writeable attribute.
   * @method
   */
  function shouldSetAttribute(name, value) {
    if (isReservedProp(name)) {
      return false;
    }
    if (name.length > 2 && (name[0] === 'o' || name[0] === 'O') && (name[1] === 'n' || name[1] === 'N')) {
      return false;
    }
    if (value === null) {
      return true;
    }
    switch (typeof value) {
      case 'boolean':
        return shouldAttributeAcceptBooleanValue(name);
      case 'undefined':
      case 'number':
      case 'string':
      case 'object':
        return true;
      default:
        // function, symbol
        return false;
    }
  }
  
  function getPropertyInfo(name) {
    return properties.hasOwnProperty(name) ? properties[name] : null;
  }
  
  function shouldAttributeAcceptBooleanValue(name) {
    if (isReservedProp(name)) {
      return true;
    }
    var propertyInfo = getPropertyInfo(name);
    if (propertyInfo) {
      return propertyInfo.hasBooleanValue || propertyInfo.hasStringBooleanValue || propertyInfo.hasOverloadedBooleanValue;
    }
    var prefix = name.toLowerCase().slice(0, 5);
    return prefix === 'data-' || prefix === 'aria-';
  }
  
  /**
   * Checks to see if a property name is within the list of properties
   * reserved for internal React operations. These properties should
   * not be set on an HTML element.
   *
   * @private
   * @param {string} name
   * @return {boolean} If the name is within reserved props
   */
  function isReservedProp(name) {
    return RESERVED_PROPS.hasOwnProperty(name);
  }
  
  var injection = DOMPropertyInjection;
  
  var MUST_USE_PROPERTY = injection.MUST_USE_PROPERTY;
  var HAS_BOOLEAN_VALUE = injection.HAS_BOOLEAN_VALUE;
  var HAS_NUMERIC_VALUE = injection.HAS_NUMERIC_VALUE;
  var HAS_POSITIVE_NUMERIC_VALUE = injection.HAS_POSITIVE_NUMERIC_VALUE;
  var HAS_OVERLOADED_BOOLEAN_VALUE = injection.HAS_OVERLOADED_BOOLEAN_VALUE;
  var HAS_STRING_BOOLEAN_VALUE = injection.HAS_STRING_BOOLEAN_VALUE;
  
  var HTMLDOMPropertyConfig = {
    // When adding attributes to this list, be sure to also add them to
    // the `possibleStandardNames` module to ensure casing and incorrect
    // name warnings.
    Properties: {
      allowFullScreen: HAS_BOOLEAN_VALUE,
      // specifies target context for links with `preload` type
      async: HAS_BOOLEAN_VALUE,
      // Note: there is a special case that prevents it from being written to the DOM
      // on the client side because the browsers are inconsistent. Instead we call focus().
      autoFocus: HAS_BOOLEAN_VALUE,
      autoPlay: HAS_BOOLEAN_VALUE,
      capture: HAS_OVERLOADED_BOOLEAN_VALUE,
      checked: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
      cols: HAS_POSITIVE_NUMERIC_VALUE,
      contentEditable: HAS_STRING_BOOLEAN_VALUE,
      controls: HAS_BOOLEAN_VALUE,
      'default': HAS_BOOLEAN_VALUE,
      defer: HAS_BOOLEAN_VALUE,
      disabled: HAS_BOOLEAN_VALUE,
      download: HAS_OVERLOADED_BOOLEAN_VALUE,
      draggable: HAS_STRING_BOOLEAN_VALUE,
      formNoValidate: HAS_BOOLEAN_VALUE,
      hidden: HAS_BOOLEAN_VALUE,
      loop: HAS_BOOLEAN_VALUE,
      // Caution; `option.selected` is not updated if `select.multiple` is
      // disabled with `removeAttribute`.
      multiple: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
      muted: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
      noValidate: HAS_BOOLEAN_VALUE,
      open: HAS_BOOLEAN_VALUE,
      playsInline: HAS_BOOLEAN_VALUE,
      readOnly: HAS_BOOLEAN_VALUE,
      required: HAS_BOOLEAN_VALUE,
      reversed: HAS_BOOLEAN_VALUE,
      rows: HAS_POSITIVE_NUMERIC_VALUE,
      rowSpan: HAS_NUMERIC_VALUE,
      scoped: HAS_BOOLEAN_VALUE,
      seamless: HAS_BOOLEAN_VALUE,
      selected: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
      size: HAS_POSITIVE_NUMERIC_VALUE,
      start: HAS_NUMERIC_VALUE,
      // support for projecting regular DOM Elements via V1 named slots ( shadow dom )
      span: HAS_POSITIVE_NUMERIC_VALUE,
      spellCheck: HAS_STRING_BOOLEAN_VALUE,
      // Style must be explicitly set in the attribute list. React components
      // expect a style object
      style: 0,
      // Keep it in the whitelist because it is case-sensitive for SVG.
      tabIndex: 0,
      // itemScope is for for Microdata support.
      // See http://schema.org/docs/gs.html
      itemScope: HAS_BOOLEAN_VALUE,
      // These attributes must stay in the white-list because they have
      // different attribute names (see DOMAttributeNames below)
      acceptCharset: 0,
      className: 0,
      htmlFor: 0,
      httpEquiv: 0,
      // Attributes with mutation methods must be specified in the whitelist
      // Set the string boolean flag to allow the behavior
      value: HAS_STRING_BOOLEAN_VALUE
    },
    DOMAttributeNames: {
      acceptCharset: 'accept-charset',
      className: 'class',
      htmlFor: 'for',
      httpEquiv: 'http-equiv'
    },
    DOMMutationMethods: {
      value: function (node, value) {
        if (value == null) {
          return node.removeAttribute('value');
        }
  
        // Number inputs get special treatment due to some edge cases in
        // Chrome. Let everything else assign the value attribute as normal.
        // https://github.com/facebook/react/issues/7253#issuecomment-236074326
        if (node.type !== 'number' || node.hasAttribute('value') === false) {
          node.setAttribute('value', '' + value);
        } else if (node.validity && !node.validity.badInput && node.ownerDocument.activeElement !== node) {
          // Don't assign an attribute if validation reports bad
          // input. Chrome will clear the value. Additionally, don't
          // operate on inputs that have focus, otherwise Chrome might
          // strip off trailing decimal places and cause the user's
          // cursor position to jump to the beginning of the input.
          //
          // In ReactDOMInput, we have an onBlur event that will trigger
          // this function again when focus is lost.
          node.setAttribute('value', '' + value);
        }
      }
    }
  };
  
  var HAS_STRING_BOOLEAN_VALUE$1 = injection.HAS_STRING_BOOLEAN_VALUE;
  
  
  var NS = {
    xlink: 'http://www.w3.org/1999/xlink',
    xml: 'http://www.w3.org/XML/1998/namespace'
  };
  
  /**
   * This is a list of all SVG attributes that need special casing,
   * namespacing, or boolean value assignment.
   *
   * When adding attributes to this list, be sure to also add them to
   * the `possibleStandardNames` module to ensure casing and incorrect
   * name warnings.
   *
   * SVG Attributes List:
   * https://www.w3.org/TR/SVG/attindex.html
   * SMIL Spec:
   * https://www.w3.org/TR/smil
   */
  var ATTRS = ['accent-height', 'alignment-baseline', 'arabic-form', 'baseline-shift', 'cap-height', 'clip-path', 'clip-rule', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'dominant-baseline', 'enable-background', 'fill-opacity', 'fill-rule', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-name', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'horiz-adv-x', 'horiz-origin-x', 'image-rendering', 'letter-spacing', 'lighting-color', 'marker-end', 'marker-mid', 'marker-start', 'overline-position', 'overline-thickness', 'paint-order', 'panose-1', 'pointer-events', 'rendering-intent', 'shape-rendering', 'stop-color', 'stop-opacity', 'strikethrough-position', 'strikethrough-thickness', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-decoration', 'text-rendering', 'underline-position', 'underline-thickness', 'unicode-bidi', 'unicode-range', 'units-per-em', 'v-alphabetic', 'v-hanging', 'v-ideographic', 'v-mathematical', 'vector-effect', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'word-spacing', 'writing-mode', 'x-height', 'xlink:actuate', 'xlink:arcrole', 'xlink:href', 'xlink:role', 'xlink:show', 'xlink:title', 'xlink:type', 'xml:base', 'xmlns:xlink', 'xml:lang', 'xml:space'];
  
  var SVGDOMPropertyConfig = {
    Properties: {
      autoReverse: HAS_STRING_BOOLEAN_VALUE$1,
      externalResourcesRequired: HAS_STRING_BOOLEAN_VALUE$1,
      preserveAlpha: HAS_STRING_BOOLEAN_VALUE$1
    },
    DOMAttributeNames: {
      autoReverse: 'autoReverse',
      externalResourcesRequired: 'externalResourcesRequired',
      preserveAlpha: 'preserveAlpha'
    },
    DOMAttributeNamespaces: {
      xlinkActuate: NS.xlink,
      xlinkArcrole: NS.xlink,
      xlinkHref: NS.xlink,
      xlinkRole: NS.xlink,
      xlinkShow: NS.xlink,
      xlinkTitle: NS.xlink,
      xlinkType: NS.xlink,
      xmlBase: NS.xml,
      xmlLang: NS.xml,
      xmlSpace: NS.xml
    }
  };
  
  var CAMELIZE = /[\-\:]([a-z])/g;
  var capitalize = function (token) {
    return token[1].toUpperCase();
  };
  
  ATTRS.forEach(function (original) {
    var reactName = original.replace(CAMELIZE, capitalize);
  
    SVGDOMPropertyConfig.Properties[reactName] = 0;
    SVGDOMPropertyConfig.DOMAttributeNames[reactName] = original;
  });
  
  injection.injectDOMPropertyConfig(HTMLDOMPropertyConfig);
  injection.injectDOMPropertyConfig(SVGDOMPropertyConfig);
  
  var ReactErrorUtils = {
    // Used by Fiber to simulate a try-catch.
    _caughtError: null,
    _hasCaughtError: false,
  
    // Used by event system to capture/rethrow the first error.
    _rethrowError: null,
    _hasRethrowError: false,
  
    injection: {
      injectErrorUtils: function (injectedErrorUtils) {
        !(typeof injectedErrorUtils.invokeGuardedCallback === 'function') ? invariant(false, 'Injected invokeGuardedCallback() must be a function.') : void 0;
        invokeGuardedCallback = injectedErrorUtils.invokeGuardedCallback;
      }
    },
  
    /**
     * Call a function while guarding against errors that happens within it.
     * Returns an error if it throws, otherwise null.
     *
     * In production, this is implemented using a try-catch. The reason we don't
     * use a try-catch directly is so that we can swap out a different
     * implementation in DEV mode.
     *
     * @param {String} name of the guard to use for logging or debugging
     * @param {Function} func The function to invoke
     * @param {*} context The context to use when calling the function
     * @param {...*} args Arguments for function
     */
    invokeGuardedCallback: function (name, func, context, a, b, c, d, e, f) {
      invokeGuardedCallback.apply(ReactErrorUtils, arguments);
    },
  
    /**
     * Same as invokeGuardedCallback, but instead of returning an error, it stores
     * it in a global so it can be rethrown by `rethrowCaughtError` later.
     * TODO: See if _caughtError and _rethrowError can be unified.
     *
     * @param {String} name of the guard to use for logging or debugging
     * @param {Function} func The function to invoke
     * @param {*} context The context to use when calling the function
     * @param {...*} args Arguments for function
     */
    invokeGuardedCallbackAndCatchFirstError: function (name, func, context, a, b, c, d, e, f) {
      ReactErrorUtils.invokeGuardedCallback.apply(this, arguments);
      if (ReactErrorUtils.hasCaughtError()) {
        var error = ReactErrorUtils.clearCaughtError();
        if (!ReactErrorUtils._hasRethrowError) {
          ReactErrorUtils._hasRethrowError = true;
          ReactErrorUtils._rethrowError = error;
        }
      }
    },
  
    /**
     * During execution of guarded functions we will capture the first error which
     * we will rethrow to be handled by the top level error handler.
     */
    rethrowCaughtError: function () {
      return rethrowCaughtError.apply(ReactErrorUtils, arguments);
    },
  
    hasCaughtError: function () {
      return ReactErrorUtils._hasCaughtError;
    },
  
    clearCaughtError: function () {
      if (ReactErrorUtils._hasCaughtError) {
        var error = ReactErrorUtils._caughtError;
        ReactErrorUtils._caughtError = null;
        ReactErrorUtils._hasCaughtError = false;
        return error;
      } else {
        invariant(false, 'clearCaughtError was called but no error was captured. This error is likely caused by a bug in React. Please file an issue.');
      }
    }
  };
  
  var invokeGuardedCallback = function (name, func, context, a, b, c, d, e, f) {
    ReactErrorUtils._hasCaughtError = false;
    ReactErrorUtils._caughtError = null;
    var funcArgs = Array.prototype.slice.call(arguments, 3);
    try {
      func.apply(context, funcArgs);
    } catch (error) {
      ReactErrorUtils._caughtError = error;
      ReactErrorUtils._hasCaughtError = true;
    }
  };
  
  {
    // In DEV mode, we swap out invokeGuardedCallback for a special version
    // that plays more nicely with the browser's DevTools. The idea is to preserve
    // "Pause on exceptions" behavior. Because React wraps all user-provided
    // functions in invokeGuardedCallback, and the production version of
    // invokeGuardedCallback uses a try-catch, all user exceptions are treated
    // like caught exceptions, and the DevTools won't pause unless the developer
    // takes the extra step of enabling pause on caught exceptions. This is
    // untintuitive, though, because even though React has caught the error, from
    // the developer's perspective, the error is uncaught.
    //
    // To preserve the expected "Pause on exceptions" behavior, we don't use a
    // try-catch in DEV. Instead, we synchronously dispatch a fake event to a fake
    // DOM node, and call the user-provided callback from inside an event handler
    // for that fake event. If the callback throws, the error is "captured" using
    // a global event handler. But because the error happens in a different
    // event loop context, it does not interrupt the normal program flow.
    // Effectively, this gives us try-catch behavior without actually using
    // try-catch. Neat!
  
    // Check that the browser supports the APIs we need to implement our special
    // DEV version of invokeGuardedCallback
    if (typeof window !== 'undefined' && typeof window.dispatchEvent === 'function' && typeof document !== 'undefined' && typeof document.createEvent === 'function') {
      var fakeNode = document.createElement('react');
  
      var invokeGuardedCallbackDev = function (name, func, context, a, b, c, d, e, f) {
        // Keeps track of whether the user-provided callback threw an error. We
        // set this to true at the beginning, then set it to false right after
        // calling the function. If the function errors, `didError` will never be
        // set to false. This strategy works even if the browser is flaky and
        // fails to call our global error handler, because it doesn't rely on
        // the error event at all.
        var didError = true;
  
        // Create an event handler for our fake event. We will synchronously
        // dispatch our fake event using `dispatchEvent`. Inside the handler, we
        // call the user-provided callback.
        var funcArgs = Array.prototype.slice.call(arguments, 3);
        function callCallback() {
          // We immediately remove the callback from event listeners so that
          // nested `invokeGuardedCallback` calls do not clash. Otherwise, a
          // nested call would trigger the fake event handlers of any call higher
          // in the stack.
          fakeNode.removeEventListener(evtType, callCallback, false);
          func.apply(context, funcArgs);
          didError = false;
        }
  
        // Create a global error event handler. We use this to capture the value
        // that was thrown. It's possible that this error handler will fire more
        // than once; for example, if non-React code also calls `dispatchEvent`
        // and a handler for that event throws. We should be resilient to most of
        // those cases. Even if our error event handler fires more than once, the
        // last error event is always used. If the callback actually does error,
        // we know that the last error event is the correct one, because it's not
        // possible for anything else to have happened in between our callback
        // erroring and the code that follows the `dispatchEvent` call below. If
        // the callback doesn't error, but the error event was fired, we know to
        // ignore it because `didError` will be false, as described above.
        var error = void 0;
        // Use this to track whether the error event is ever called.
        var didSetError = false;
        var isCrossOriginError = false;
  
        function onError(event) {
          error = event.error;
          didSetError = true;
          if (error === null && event.colno === 0 && event.lineno === 0) {
            isCrossOriginError = true;
          }
        }
  
        // Create a fake event type.
        var evtType = 'react-' + (name ? name : 'invokeguardedcallback');
  
        // Attach our event handlers
        window.addEventListener('error', onError);
        fakeNode.addEventListener(evtType, callCallback, false);
  
        // Synchronously dispatch our fake event. If the user-provided function
        // errors, it will trigger our global error handler.
        var evt = document.createEvent('Event');
        evt.initEvent(evtType, false, false);
        fakeNode.dispatchEvent(evt);
  
        if (didError) {
          if (!didSetError) {
            // The callback errored, but the error event never fired.
            error = new Error('An error was thrown inside one of your components, but React ' + "doesn't know what it was. This is likely due to browser " + 'flakiness. React does its best to preserve the "Pause on ' + 'exceptions" behavior of the DevTools, which requires some ' + "DEV-mode only tricks. It's possible that these don't work in " + 'your browser. Try triggering the error in production mode, ' + 'or switching to a modern browser. If you suspect that this is ' + 'actually an issue with React, please file an issue.');
          } else if (isCrossOriginError) {
            error = new Error("A cross-origin error was thrown. React doesn't have access to " + 'the actual error object in development. ' + 'See https://fb.me/react-crossorigin-error for more information.');
          }
          ReactErrorUtils._hasCaughtError = true;
          ReactErrorUtils._caughtError = error;
        } else {
          ReactErrorUtils._hasCaughtError = false;
          ReactErrorUtils._caughtError = null;
        }
  
        // Remove our event listeners
        window.removeEventListener('error', onError);
      };
  
      invokeGuardedCallback = invokeGuardedCallbackDev;
    }
  }
  
  var rethrowCaughtError = function () {
    if (ReactErrorUtils._hasRethrowError) {
      var error = ReactErrorUtils._rethrowError;
      ReactErrorUtils._rethrowError = null;
      ReactErrorUtils._hasRethrowError = false;
      throw error;
    }
  };
  
  /**
   * Injectable ordering of event plugins.
   */
  var eventPluginOrder = null;
  
  /**
   * Injectable mapping from names to event plugin modules.
   */
  var namesToPlugins = {};
  
  /**
   * Recomputes the plugin list using the injected plugins and plugin ordering.
   *
   * @private
   */
  function recomputePluginOrdering() {
    if (!eventPluginOrder) {
      // Wait until an `eventPluginOrder` is injected.
      return;
    }
    for (var pluginName in namesToPlugins) {
      var pluginModule = namesToPlugins[pluginName];
      var pluginIndex = eventPluginOrder.indexOf(pluginName);
      !(pluginIndex > -1) ? invariant(false, 'EventPluginRegistry: Cannot inject event plugins that do not exist in the plugin ordering, `%s`.', pluginName) : void 0;
      if (plugins[pluginIndex]) {
        continue;
      }
      !pluginModule.extractEvents ? invariant(false, 'EventPluginRegistry: Event plugins must implement an `extractEvents` method, but `%s` does not.', pluginName) : void 0;
      plugins[pluginIndex] = pluginModule;
      var publishedEvents = pluginModule.eventTypes;
      for (var eventName in publishedEvents) {
        !publishEventForPlugin(publishedEvents[eventName], pluginModule, eventName) ? invariant(false, 'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.', eventName, pluginName) : void 0;
      }
    }
  }
  
  /**
   * Publishes an event so that it can be dispatched by the supplied plugin.
   *
   * @param {object} dispatchConfig Dispatch configuration for the event.
   * @param {object} PluginModule Plugin publishing the event.
   * @return {boolean} True if the event was successfully published.
   * @private
   */
  function publishEventForPlugin(dispatchConfig, pluginModule, eventName) {
    !!eventNameDispatchConfigs.hasOwnProperty(eventName) ? invariant(false, 'EventPluginHub: More than one plugin attempted to publish the same event name, `%s`.', eventName) : void 0;
    eventNameDispatchConfigs[eventName] = dispatchConfig;
  
    var phasedRegistrationNames = dispatchConfig.phasedRegistrationNames;
    if (phasedRegistrationNames) {
      for (var phaseName in phasedRegistrationNames) {
        if (phasedRegistrationNames.hasOwnProperty(phaseName)) {
          var phasedRegistrationName = phasedRegistrationNames[phaseName];
          publishRegistrationName(phasedRegistrationName, pluginModule, eventName);
        }
      }
      return true;
    } else if (dispatchConfig.registrationName) {
      publishRegistrationName(dispatchConfig.registrationName, pluginModule, eventName);
      return true;
    }
    return false;
  }
  
  /**
   * Publishes a registration name that is used to identify dispatched events.
   *
   * @param {string} registrationName Registration name to add.
   * @param {object} PluginModule Plugin publishing the event.
   * @private
   */
  function publishRegistrationName(registrationName, pluginModule, eventName) {
    !!registrationNameModules[registrationName] ? invariant(false, 'EventPluginHub: More than one plugin attempted to publish the same registration name, `%s`.', registrationName) : void 0;
    registrationNameModules[registrationName] = pluginModule;
    registrationNameDependencies[registrationName] = pluginModule.eventTypes[eventName].dependencies;
  
    {
      var lowerCasedName = registrationName.toLowerCase();
      possibleRegistrationNames[lowerCasedName] = registrationName;
  
      if (registrationName === 'onDoubleClick') {
        possibleRegistrationNames.ondblclick = registrationName;
      }
    }
  }
  
  /**
   * Registers plugins so that they can extract and dispatch events.
   *
   * @see {EventPluginHub}
   */
  
  /**
   * Ordered list of injected plugins.
   */
  var plugins = [];
  
  /**
   * Mapping from event name to dispatch config
   */
  var eventNameDispatchConfigs = {};
  
  /**
   * Mapping from registration name to plugin module
   */
  var registrationNameModules = {};
  
  /**
   * Mapping from registration name to event name
   */
  var registrationNameDependencies = {};
  
  /**
   * Mapping from lowercase registration names to the properly cased version,
   * used to warn in the case of missing event handlers. Available
   * only in true.
   * @type {Object}
   */
  var possibleRegistrationNames = {};
  // Trust the developer to only use possibleRegistrationNames in true
  
  /**
   * Injects an ordering of plugins (by plugin name). This allows the ordering
   * to be decoupled from injection of the actual plugins so that ordering is
   * always deterministic regardless of packaging, on-the-fly injection, etc.
   *
   * @param {array} InjectedEventPluginOrder
   * @internal
   * @see {EventPluginHub.injection.injectEventPluginOrder}
   */
  function injectEventPluginOrder(injectedEventPluginOrder) {
    !!eventPluginOrder ? invariant(false, 'EventPluginRegistry: Cannot inject event plugin ordering more than once. You are likely trying to load more than one copy of React.') : void 0;
    // Clone the ordering so it cannot be dynamically mutated.
    eventPluginOrder = Array.prototype.slice.call(injectedEventPluginOrder);
    recomputePluginOrdering();
  }
  
  /**
   * Injects plugins to be used by `EventPluginHub`. The plugin names must be
   * in the ordering injected by `injectEventPluginOrder`.
   *
   * Plugins can be injected as part of page initialization or on-the-fly.
   *
   * @param {object} injectedNamesToPlugins Map from names to plugin modules.
   * @internal
   * @see {EventPluginHub.injection.injectEventPluginsByName}
   */
  function injectEventPluginsByName(injectedNamesToPlugins) {
    var isOrderingDirty = false;
    for (var pluginName in injectedNamesToPlugins) {
      if (!injectedNamesToPlugins.hasOwnProperty(pluginName)) {
        continue;
      }
      var pluginModule = injectedNamesToPlugins[pluginName];
      if (!namesToPlugins.hasOwnProperty(pluginName) || namesToPlugins[pluginName] !== pluginModule) {
        !!namesToPlugins[pluginName] ? invariant(false, 'EventPluginRegistry: Cannot inject two different event plugins using the same name, `%s`.', pluginName) : void 0;
        namesToPlugins[pluginName] = pluginModule;
        isOrderingDirty = true;
      }
    }
    if (isOrderingDirty) {
      recomputePluginOrdering();
    }
  }
  
  var EventPluginRegistry = Object.freeze({
    plugins: plugins,
    eventNameDispatchConfigs: eventNameDispatchConfigs,
    registrationNameModules: registrationNameModules,
    registrationNameDependencies: registrationNameDependencies,
    possibleRegistrationNames: possibleRegistrationNames,
    injectEventPluginOrder: injectEventPluginOrder,
    injectEventPluginsByName: injectEventPluginsByName
  });
  
  var getFiberCurrentPropsFromNode = null;
  var getInstanceFromNode = null;
  var getNodeFromInstance = null;
  
  var injection$2 = {
    injectComponentTree: function (Injected) {
      getFiberCurrentPropsFromNode = Injected.getFiberCurrentPropsFromNode;
      getInstanceFromNode = Injected.getInstanceFromNode;
      getNodeFromInstance = Injected.getNodeFromInstance;
  
      {
        warning(getNodeFromInstance && getInstanceFromNode, 'EventPluginUtils.injection.injectComponentTree(...): Injected ' + 'module is missing getNodeFromInstance or getInstanceFromNode.');
      }
    }
  };
  
  
  
  
  
  
  var validateEventDispatches;
  {
    validateEventDispatches = function (event) {
      var dispatchListeners = event._dispatchListeners;
      var dispatchInstances = event._dispatchInstances;
  
      var listenersIsArr = Array.isArray(dispatchListeners);
      var listenersLen = listenersIsArr ? dispatchListeners.length : dispatchListeners ? 1 : 0;
  
      var instancesIsArr = Array.isArray(dispatchInstances);
      var instancesLen = instancesIsArr ? dispatchInstances.length : dispatchInstances ? 1 : 0;
  
      warning(instancesIsArr === listenersIsArr && instancesLen === listenersLen, 'EventPluginUtils: Invalid `event`.');
    };
  }
  
  /**
   * Dispatch the event to the listener.
   * @param {SyntheticEvent} event SyntheticEvent to handle
   * @param {boolean} simulated If the event is simulated (changes exn behavior)
   * @param {function} listener Application-level callback
   * @param {*} inst Internal component instance
   */
  function executeDispatch(event, simulated, listener, inst) {
    var type = event.type || 'unknown-event';
    event.currentTarget = getNodeFromInstance(inst);
    ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
    event.currentTarget = null;
  }
  
  /**
   * Standard/simple iteration through an event's collected dispatches.
   */
  function executeDispatchesInOrder(event, simulated) {
    var dispatchListeners = event._dispatchListeners;
    var dispatchInstances = event._dispatchInstances;
    {
      validateEventDispatches(event);
    }
    if (Array.isArray(dispatchListeners)) {
      for (var i = 0; i < dispatchListeners.length; i++) {
        if (event.isPropagationStopped()) {
          break;
        }
        // Listeners and Instances are two parallel arrays that are always in sync.
        executeDispatch(event, simulated, dispatchListeners[i], dispatchInstances[i]);
      }
    } else if (dispatchListeners) {
      executeDispatch(event, simulated, dispatchListeners, dispatchInstances);
    }
    event._dispatchListeners = null;
    event._dispatchInstances = null;
  }
  
  /**
   * @see executeDispatchesInOrderStopAtTrueImpl
   */
  
  
  /**
   * Execution of a "direct" dispatch - there must be at most one dispatch
   * accumulated on the event or it is considered an error. It doesn't really make
   * sense for an event with multiple dispatches (bubbled) to keep track of the
   * return values at each dispatch execution, but it does tend to make sense when
   * dealing with "direct" dispatches.
   *
   * @return {*} The return value of executing the single dispatch.
   */
  
  
  /**
   * @param {SyntheticEvent} event
   * @return {boolean} True iff number of dispatches accumulated is greater than 0.
   */
  
  /**
   * Accumulates items that must not be null or undefined into the first one. This
   * is used to conserve memory by avoiding array allocations, and thus sacrifices
   * API cleanness. Since `current` can be null before being passed in and not
   * null after this function, make sure to assign it back to `current`:
   *
   * `a = accumulateInto(a, b);`
   *
   * This API should be sparingly used. Try `accumulate` for something cleaner.
   *
   * @return {*|array<*>} An accumulation of items.
   */
  
  function accumulateInto(current, next) {
    !(next != null) ? invariant(false, 'accumulateInto(...): Accumulated items must not be null or undefined.') : void 0;
  
    if (current == null) {
      return next;
    }
  
    // Both are not empty. Warning: Never call x.concat(y) when you are not
    // certain that x is an Array (x could be a string with concat method).
    if (Array.isArray(current)) {
      if (Array.isArray(next)) {
        current.push.apply(current, next);
        return current;
      }
      current.push(next);
      return current;
    }
  
    if (Array.isArray(next)) {
      // A bit too dangerous to mutate `next`.
      return [current].concat(next);
    }
  
    return [current, next];
  }
  
  /**
   * @param {array} arr an "accumulation" of items which is either an Array or
   * a single item. Useful when paired with the `accumulate` module. This is a
   * simple utility that allows us to reason about a collection of items, but
   * handling the case when there is exactly one item (and we do not need to
   * allocate an array).
   * @param {function} cb Callback invoked with each element or a collection.
   * @param {?} [scope] Scope used as `this` in a callback.
   */
  function forEachAccumulated(arr, cb, scope) {
    if (Array.isArray(arr)) {
      arr.forEach(cb, scope);
    } else if (arr) {
      cb.call(scope, arr);
    }
  }
  
  /**
   * Internal queue of events that have accumulated their dispatches and are
   * waiting to have their dispatches executed.
   */
  var eventQueue = null;
  
  /**
   * Dispatches an event and releases it back into the pool, unless persistent.
   *
   * @param {?object} event Synthetic event to be dispatched.
   * @param {boolean} simulated If the event is simulated (changes exn behavior)
   * @private
   */
  var executeDispatchesAndRelease = function (event, simulated) {
    if (event) {
      executeDispatchesInOrder(event, simulated);
  
      if (!event.isPersistent()) {
        event.constructor.release(event);
      }
    }
  };
  var executeDispatchesAndReleaseSimulated = function (e) {
    return executeDispatchesAndRelease(e, true);
  };
  var executeDispatchesAndReleaseTopLevel = function (e) {
    return executeDispatchesAndRelease(e, false);
  };
  
  function isInteractive(tag) {
    return tag === 'button' || tag === 'input' || tag === 'select' || tag === 'textarea';
  }
  
  function shouldPreventMouseEvent(name, type, props) {
    switch (name) {
      case 'onClick':
      case 'onClickCapture':
      case 'onDoubleClick':
      case 'onDoubleClickCapture':
      case 'onMouseDown':
      case 'onMouseDownCapture':
      case 'onMouseMove':
      case 'onMouseMoveCapture':
      case 'onMouseUp':
      case 'onMouseUpCapture':
        return !!(props.disabled && isInteractive(type));
      default:
        return false;
    }
  }
  
  /**
   * This is a unified interface for event plugins to be installed and configured.
   *
   * Event plugins can implement the following properties:
   *
   *   `extractEvents` {function(string, DOMEventTarget, string, object): *}
   *     Required. When a top-level event is fired, this method is expected to
   *     extract synthetic events that will in turn be queued and dispatched.
   *
   *   `eventTypes` {object}
   *     Optional, plugins that fire events must publish a mapping of registration
   *     names that are used to register listeners. Values of this mapping must
   *     be objects that contain `registrationName` or `phasedRegistrationNames`.
   *
   *   `executeDispatch` {function(object, function, string)}
   *     Optional, allows plugins to override how an event gets dispatched. By
   *     default, the listener is simply invoked.
   *
   * Each plugin that is injected into `EventsPluginHub` is immediately operable.
   *
   * @public
   */
  
  /**
   * Methods for injecting dependencies.
   */
  var injection$1 = {
    /**
     * @param {array} InjectedEventPluginOrder
     * @public
     */
    injectEventPluginOrder: injectEventPluginOrder,
  
    /**
     * @param {object} injectedNamesToPlugins Map from names to plugin modules.
     */
    injectEventPluginsByName: injectEventPluginsByName
  };
  
  /**
   * @param {object} inst The instance, which is the source of events.
   * @param {string} registrationName Name of listener (e.g. `onClick`).
   * @return {?function} The stored callback.
   */
  function getListener(inst, registrationName) {
    var listener;
  
    // TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
    // live here; needs to be moved to a better place soon
    var stateNode = inst.stateNode;
    if (!stateNode) {
      // Work in progress (ex: onload events in incremental mode).
      return null;
    }
    var props = getFiberCurrentPropsFromNode(stateNode);
    if (!props) {
      // Work in progress.
      return null;
    }
    listener = props[registrationName];
    if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
      return null;
    }
    !(!listener || typeof listener === 'function') ? invariant(false, 'Expected `%s` listener to be a function, instead got a value of `%s` type.', registrationName, typeof listener) : void 0;
    return listener;
  }
  
  /**
   * Allows registered plugins an opportunity to extract events from top-level
   * native browser events.
   *
   * @return {*} An accumulation of synthetic events.
   * @internal
   */
  function extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
    var events;
    for (var i = 0; i < plugins.length; i++) {
      // Not every plugin in the ordering may be loaded at runtime.
      var possiblePlugin = plugins[i];
      if (possiblePlugin) {
        var extractedEvents = possiblePlugin.extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
        if (extractedEvents) {
          events = accumulateInto(events, extractedEvents);
        }
      }
    }
    return events;
  }
  
  /**
   * Enqueues a synthetic event that should be dispatched when
   * `processEventQueue` is invoked.
   *
   * @param {*} events An accumulation of synthetic events.
   * @internal
   */
  function enqueueEvents(events) {
    if (events) {
      eventQueue = accumulateInto(eventQueue, events);
    }
  }
  
  /**
   * Dispatches all synthetic events on the event queue.
   *
   * @internal
   */
  function processEventQueue(simulated) {
    // Set `eventQueue` to null before processing it so that we can tell if more
    // events get enqueued while processing.
    var processingEventQueue = eventQueue;
    eventQueue = null;
  
    if (!processingEventQueue) {
      return;
    }
  
    if (simulated) {
      forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseSimulated);
    } else {
      forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
    }
    !!eventQueue ? invariant(false, 'processEventQueue(): Additional events were enqueued while processing an event queue. Support for this has not yet been implemented.') : void 0;
    // This would be a good time to rethrow if any of the event handlers threw.
    ReactErrorUtils.rethrowCaughtError();
  }
  
  var EventPluginHub = Object.freeze({
    injection: injection$1,
    getListener: getListener,
    extractEvents: extractEvents,
    enqueueEvents: enqueueEvents,
    processEventQueue: processEventQueue
  });
  
  var IndeterminateComponent = 0; // Before we know whether it is functional or class
  var FunctionalComponent = 1;
  var ClassComponent = 2;
  var HostRoot = 3; // Root of a host tree. Could be nested inside another node.
  var HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
  var HostComponent = 5;
  var HostText = 6;
  var CallComponent = 7;
  var CallHandlerPhase = 8;
  var ReturnComponent = 9;
  var Fragment = 10;
  
  var randomKey = Math.random().toString(36).slice(2);
  var internalInstanceKey = '__reactInternalInstance$' + randomKey;
  var internalEventHandlersKey = '__reactEventHandlers$' + randomKey;
  
  function precacheFiberNode$1(hostInst, node) {
    node[internalInstanceKey] = hostInst;
  }
  
  /**
   * Given a DOM node, return the closest ReactDOMComponent or
   * ReactDOMTextComponent instance ancestor.
   */
  function getClosestInstanceFromNode(node) {
    if (node[internalInstanceKey]) {
      return node[internalInstanceKey];
    }
  
    // Walk up the tree until we find an ancestor whose instance we have cached.
    var parents = [];
    while (!node[internalInstanceKey]) {
      parents.push(node);
      if (node.parentNode) {
        node = node.parentNode;
      } else {
        // Top of the tree. This node must not be part of a React tree (or is
        // unmounted, potentially).
        return null;
      }
    }
  
    var closest = void 0;
    var inst = node[internalInstanceKey];
    if (inst.tag === HostComponent || inst.tag === HostText) {
      // In Fiber, this will always be the deepest root.
      return inst;
    }
    for (; node && (inst = node[internalInstanceKey]); node = parents.pop()) {
      closest = inst;
    }
  
    return closest;
  }
  
  /**
   * Given a DOM node, return the ReactDOMComponent or ReactDOMTextComponent
   * instance, or null if the node was not rendered by this React.
   */
  function getInstanceFromNode$1(node) {
    var inst = node[internalInstanceKey];
    if (inst) {
      if (inst.tag === HostComponent || inst.tag === HostText) {
        return inst;
      } else {
        return null;
      }
    }
    return null;
  }
  
  /**
   * Given a ReactDOMComponent or ReactDOMTextComponent, return the corresponding
   * DOM node.
   */
  function getNodeFromInstance$1(inst) {
    if (inst.tag === HostComponent || inst.tag === HostText) {
      // In Fiber this, is just the state node right now. We assume it will be
      // a host component or host text.
      return inst.stateNode;
    }
  
    // Without this first invariant, passing a non-DOM-component triggers the next
    // invariant for a missing parent, which is super confusing.
    invariant(false, 'getNodeFromInstance: Invalid argument.');
  }
  
  function getFiberCurrentPropsFromNode$1(node) {
    return node[internalEventHandlersKey] || null;
  }
  
  function updateFiberProps$1(node, props) {
    node[internalEventHandlersKey] = props;
  }
  
  var ReactDOMComponentTree = Object.freeze({
    precacheFiberNode: precacheFiberNode$1,
    getClosestInstanceFromNode: getClosestInstanceFromNode,
    getInstanceFromNode: getInstanceFromNode$1,
    getNodeFromInstance: getNodeFromInstance$1,
    getFiberCurrentPropsFromNode: getFiberCurrentPropsFromNode$1,
    updateFiberProps: updateFiberProps$1
  });
  
  function getParent(inst) {
    do {
      inst = inst['return'];
      // TODO: If this is a HostRoot we might want to bail out.
      // That is depending on if we want nested subtrees (layers) to bubble
      // events to their parent. We could also go through parentNode on the
      // host node but that wouldn't work for React Native and doesn't let us
      // do the portal feature.
    } while (inst && inst.tag !== HostComponent);
    if (inst) {
      return inst;
    }
    return null;
  }
  
  /**
   * Return the lowest common ancestor of A and B, or null if they are in
   * different trees.
   */
  function getLowestCommonAncestor(instA, instB) {
    var depthA = 0;
    for (var tempA = instA; tempA; tempA = getParent(tempA)) {
      depthA++;
    }
    var depthB = 0;
    for (var tempB = instB; tempB; tempB = getParent(tempB)) {
      depthB++;
    }
  
    // If A is deeper, crawl up.
    while (depthA - depthB > 0) {
      instA = getParent(instA);
      depthA--;
    }
  
    // If B is deeper, crawl up.
    while (depthB - depthA > 0) {
      instB = getParent(instB);
      depthB--;
    }
  
    // Walk in lockstep until we find a match.
    var depth = depthA;
    while (depth--) {
      if (instA === instB || instA === instB.alternate) {
        return instA;
      }
      instA = getParent(instA);
      instB = getParent(instB);
    }
    return null;
  }
  
  /**
   * Return if A is an ancestor of B.
   */
  
  
  /**
   * Return the parent instance of the passed-in instance.
   */
  function getParentInstance(inst) {
    return getParent(inst);
  }
  
  /**
   * Simulates the traversal of a two-phase, capture/bubble event dispatch.
   */
  function traverseTwoPhase(inst, fn, arg) {
    var path = [];
    while (inst) {
      path.push(inst);
      inst = getParent(inst);
    }
    var i;
    for (i = path.length; i-- > 0;) {
      fn(path[i], 'captured', arg);
    }
    for (i = 0; i < path.length; i++) {
      fn(path[i], 'bubbled', arg);
    }
  }
  
  /**
   * Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that
   * should would receive a `mouseEnter` or `mouseLeave` event.
   *
   * Does not invoke the callback on the nearest common ancestor because nothing
   * "entered" or "left" that element.
   */
  function traverseEnterLeave(from, to, fn, argFrom, argTo) {
    var common = from && to ? getLowestCommonAncestor(from, to) : null;
    var pathFrom = [];
    while (true) {
      if (!from) {
        break;
      }
      if (from === common) {
        break;
      }
      var alternate = from.alternate;
      if (alternate !== null && alternate === common) {
        break;
      }
      pathFrom.push(from);
      from = getParent(from);
    }
    var pathTo = [];
    while (true) {
      if (!to) {
        break;
      }
      if (to === common) {
        break;
      }
      var _alternate = to.alternate;
      if (_alternate !== null && _alternate === common) {
        break;
      }
      pathTo.push(to);
      to = getParent(to);
    }
    for (var i = 0; i < pathFrom.length; i++) {
      fn(pathFrom[i], 'bubbled', argFrom);
    }
    for (var _i = pathTo.length; _i-- > 0;) {
      fn(pathTo[_i], 'captured', argTo);
    }
  }
  
  /**
   * Some event types have a notion of different registration names for different
   * "phases" of propagation. This finds listeners by a given phase.
   */
  function listenerAtPhase(inst, event, propagationPhase) {
    var registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase];
    return getListener(inst, registrationName);
  }
  
  /**
   * A small set of propagation patterns, each of which will accept a small amount
   * of information, and generate a set of "dispatch ready event objects" - which
   * are sets of events that have already been annotated with a set of dispatched
   * listener functions/ids. The API is designed this way to discourage these
   * propagation strategies from actually executing the dispatches, since we
   * always want to collect the entire set of dispatches before executing even a
   * single one.
   */
  
  /**
   * Tags a `SyntheticEvent` with dispatched listeners. Creating this function
   * here, allows us to not have to bind or create functions for each event.
   * Mutating the event's members allows us to not have to create a wrapping
   * "dispatch" object that pairs the event with the listener.
   */
  function accumulateDirectionalDispatches(inst, phase, event) {
    {
      warning(inst, 'Dispatching inst must not be null');
    }
    var listener = listenerAtPhase(inst, event, phase);
    if (listener) {
      event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
      event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
    }
  }
  
  /**
   * Collect dispatches (must be entirely collected before dispatching - see unit
   * tests). Lazily allocate the array to conserve memory.  We must loop through
   * each event and perform the traversal for each one. We cannot perform a
   * single traversal for the entire collection of events because each event may
   * have a different target.
   */
  function accumulateTwoPhaseDispatchesSingle(event) {
    if (event && event.dispatchConfig.phasedRegistrationNames) {
      traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
    }
  }
  
  /**
   * Same as `accumulateTwoPhaseDispatchesSingle`, but skips over the targetID.
   */
  function accumulateTwoPhaseDispatchesSingleSkipTarget(event) {
    if (event && event.dispatchConfig.phasedRegistrationNames) {
      var targetInst = event._targetInst;
      var parentInst = targetInst ? getParentInstance(targetInst) : null;
      traverseTwoPhase(parentInst, accumulateDirectionalDispatches, event);
    }
  }
  
  /**
   * Accumulates without regard to direction, does not look for phased
   * registration names. Same as `accumulateDirectDispatchesSingle` but without
   * requiring that the `dispatchMarker` be the same as the dispatched ID.
   */
  function accumulateDispatches(inst, ignoredDirection, event) {
    if (inst && event && event.dispatchConfig.registrationName) {
      var registrationName = event.dispatchConfig.registrationName;
      var listener = getListener(inst, registrationName);
      if (listener) {
        event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
        event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
      }
    }
  }
  
  /**
   * Accumulates dispatches on an `SyntheticEvent`, but only for the
   * `dispatchMarker`.
   * @param {SyntheticEvent} event
   */
  function accumulateDirectDispatchesSingle(event) {
    if (event && event.dispatchConfig.registrationName) {
      accumulateDispatches(event._targetInst, null, event);
    }
  }
  
  function accumulateTwoPhaseDispatches(events) {
    forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
  }
  
  function accumulateTwoPhaseDispatchesSkipTarget(events) {
    forEachAccumulated(events, accumulateTwoPhaseDispatchesSingleSkipTarget);
  }
  
  function accumulateEnterLeaveDispatches(leave, enter, from, to) {
    traverseEnterLeave(from, to, accumulateDispatches, leave, enter);
  }
  
  function accumulateDirectDispatches(events) {
    forEachAccumulated(events, accumulateDirectDispatchesSingle);
  }
  
  var EventPropagators = Object.freeze({
    accumulateTwoPhaseDispatches: accumulateTwoPhaseDispatches,
    accumulateTwoPhaseDispatchesSkipTarget: accumulateTwoPhaseDispatchesSkipTarget,
    accumulateEnterLeaveDispatches: accumulateEnterLeaveDispatches,
    accumulateDirectDispatches: accumulateDirectDispatches
  });
  
  var contentKey = null;
  
  /**
   * Gets the key used to access text content on a DOM node.
   *
   * @return {?string} Key used to access text content.
   * @internal
   */
  function getTextContentAccessor() {
    if (!contentKey && ExecutionEnvironment.canUseDOM) {
      // Prefer textContent to innerText because many browsers support both but
      // SVG <text> elements don't support innerText even when <div> does.
      contentKey = 'textContent' in document.documentElement ? 'textContent' : 'innerText';
    }
    return contentKey;
  }
  
  /**
   * This helper object stores information about text content of a target node,
   * allowing comparison of content before and after a given event.
   *
   * Identify the node where selection currently begins, then observe
   * both its text content and its current position in the DOM. Since the
   * browser may natively replace the target node during composition, we can
   * use its position to find its replacement.
   *
   *
   */
  var compositionState = {
    _root: null,
    _startText: null,
    _fallbackText: null
  };
  
  function initialize(nativeEventTarget) {
    compositionState._root = nativeEventTarget;
    compositionState._startText = getText();
    return true;
  }
  
  function reset() {
    compositionState._root = null;
    compositionState._startText = null;
    compositionState._fallbackText = null;
  }
  
  function getData() {
    if (compositionState._fallbackText) {
      return compositionState._fallbackText;
    }
  
    var start;
    var startValue = compositionState._startText;
    var startLength = startValue.length;
    var end;
    var endValue = getText();
    var endLength = endValue.length;
  
    for (start = 0; start < startLength; start++) {
      if (startValue[start] !== endValue[start]) {
        break;
      }
    }
  
    var minEnd = startLength - start;
    for (end = 1; end <= minEnd; end++) {
      if (startValue[startLength - end] !== endValue[endLength - end]) {
        break;
      }
    }
  
    var sliceTail = end > 1 ? 1 - end : undefined;
    compositionState._fallbackText = endValue.slice(start, sliceTail);
    return compositionState._fallbackText;
  }
  
  function getText() {
    if ('value' in compositionState._root) {
      return compositionState._root.value;
    }
    return compositionState._root[getTextContentAccessor()];
  }
  
  /* eslint valid-typeof: 0 */
  
  var didWarnForAddedNewProperty = false;
  var isProxySupported = typeof Proxy === 'function';
  var EVENT_POOL_SIZE = 10;
  
  var shouldBeReleasedProperties = ['dispatchConfig', '_targetInst', 'nativeEvent', 'isDefaultPrevented', 'isPropagationStopped', '_dispatchListeners', '_dispatchInstances'];
  
  /**
   * @interface Event
   * @see http://www.w3.org/TR/DOM-Level-3-Events/
   */
  var EventInterface = {
    type: null,
    target: null,
    // currentTarget is set when dispatching; no use in copying it here
    currentTarget: emptyFunction.thatReturnsNull,
    eventPhase: null,
    bubbles: null,
    cancelable: null,
    timeStamp: function (event) {
      return event.timeStamp || Date.now();
    },
    defaultPrevented: null,
    isTrusted: null
  };
  
  /**
   * Synthetic events are dispatched by event plugins, typically in response to a
   * top-level event delegation handler.
   *
   * These systems should generally use pooling to reduce the frequency of garbage
   * collection. The system should check `isPersistent` to determine whether the
   * event should be released into the pool after being dispatched. Users that
   * need a persisted event should invoke `persist`.
   *
   * Synthetic events (and subclasses) implement the DOM Level 3 Events API by
   * normalizing browser quirks. Subclasses do not necessarily have to implement a
   * DOM interface; custom application-specific events can also subclass this.
   *
   * @param {object} dispatchConfig Configuration used to dispatch this event.
   * @param {*} targetInst Marker identifying the event target.
   * @param {object} nativeEvent Native browser event.
   * @param {DOMEventTarget} nativeEventTarget Target node.
   */
  function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarget) {
    {
      // these have a getter/setter for warnings
      delete this.nativeEvent;
      delete this.preventDefault;
      delete this.stopPropagation;
    }
  
    this.dispatchConfig = dispatchConfig;
    this._targetInst = targetInst;
    this.nativeEvent = nativeEvent;
  
    var Interface = this.constructor.Interface;
    for (var propName in Interface) {
      if (!Interface.hasOwnProperty(propName)) {
        continue;
      }
      {
        delete this[propName]; // this has a getter/setter for warnings
      }
      var normalize = Interface[propName];
      if (normalize) {
        this[propName] = normalize(nativeEvent);
      } else {
        if (propName === 'target') {
          this.target = nativeEventTarget;
        } else {
          this[propName] = nativeEvent[propName];
        }
      }
    }
  
    var defaultPrevented = nativeEvent.defaultPrevented != null ? nativeEvent.defaultPrevented : nativeEvent.returnValue === false;
    if (defaultPrevented) {
      this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
    } else {
      this.isDefaultPrevented = emptyFunction.thatReturnsFalse;
    }
    this.isPropagationStopped = emptyFunction.thatReturnsFalse;
    return this;
  }
  
  _assign(SyntheticEvent.prototype, {
    preventDefault: function () {
      this.defaultPrevented = true;
      var event = this.nativeEvent;
      if (!event) {
        return;
      }
  
      if (event.preventDefault) {
        event.preventDefault();
      } else if (typeof event.returnValue !== 'unknown') {
        event.returnValue = false;
      }
      this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
    },
  
    stopPropagation: function () {
      var event = this.nativeEvent;
      if (!event) {
        return;
      }
  
      if (event.stopPropagation) {
        event.stopPropagation();
      } else if (typeof event.cancelBubble !== 'unknown') {
        // The ChangeEventPlugin registers a "propertychange" event for
        // IE. This event does not support bubbling or cancelling, and
        // any references to cancelBubble throw "Member not found".  A
        // typeof check of "unknown" circumvents this issue (and is also
        // IE specific).
        event.cancelBubble = true;
      }
  
      this.isPropagationStopped = emptyFunction.thatReturnsTrue;
    },
  
    /**
     * We release all dispatched `SyntheticEvent`s after each event loop, adding
     * them back into the pool. This allows a way to hold onto a reference that
     * won't be added back into the pool.
     */
    persist: function () {
      this.isPersistent = emptyFunction.thatReturnsTrue;
    },
  
    /**
     * Checks if this event should be released back into the pool.
     *
     * @return {boolean} True if this should not be released, false otherwise.
     */
    isPersistent: emptyFunction.thatReturnsFalse,
  
    /**
     * `PooledClass` looks for `destructor` on each instance it releases.
     */
    destructor: function () {
      var Interface = this.constructor.Interface;
      for (var propName in Interface) {
        {
          Object.defineProperty(this, propName, getPooledWarningPropertyDefinition(propName, Interface[propName]));
        }
      }
      for (var i = 0; i < shouldBeReleasedProperties.length; i++) {
        this[shouldBeReleasedProperties[i]] = null;
      }
      {
        Object.defineProperty(this, 'nativeEvent', getPooledWarningPropertyDefinition('nativeEvent', null));
        Object.defineProperty(this, 'preventDefault', getPooledWarningPropertyDefinition('preventDefault', emptyFunction));
        Object.defineProperty(this, 'stopPropagation', getPooledWarningPropertyDefinition('stopPropagation', emptyFunction));
      }
    }
  });
  
  SyntheticEvent.Interface = EventInterface;
  
  /**
   * Helper to reduce boilerplate when creating subclasses.
   *
   * @param {function} Class
   * @param {?object} Interface
   */
  SyntheticEvent.augmentClass = function (Class, Interface) {
    var Super = this;
  
    var E = function () {};
    E.prototype = Super.prototype;
    var prototype = new E();
  
    _assign(prototype, Class.prototype);
    Class.prototype = prototype;
    Class.prototype.constructor = Class;
  
    Class.Interface = _assign({}, Super.Interface, Interface);
    Class.augmentClass = Super.augmentClass;
    addEventPoolingTo(Class);
  };
  
  /** Proxying after everything set on SyntheticEvent
   * to resolve Proxy issue on some WebKit browsers
   * in which some Event properties are set to undefined (GH#10010)
   */
  {
    if (isProxySupported) {
      /*eslint-disable no-func-assign */
      SyntheticEvent = new Proxy(SyntheticEvent, {
        construct: function (target, args) {
          return this.apply(target, Object.create(target.prototype), args);
        },
        apply: function (constructor, that, args) {
          return new Proxy(constructor.apply(that, args), {
            set: function (target, prop, value) {
              if (prop !== 'isPersistent' && !target.constructor.Interface.hasOwnProperty(prop) && shouldBeReleasedProperties.indexOf(prop) === -1) {
                warning(didWarnForAddedNewProperty || target.isPersistent(), "This synthetic event is reused for performance reasons. If you're " + "seeing this, you're adding a new property in the synthetic event object. " + 'The property is never released. See ' + 'https://fb.me/react-event-pooling for more information.');
                didWarnForAddedNewProperty = true;
              }
              target[prop] = value;
              return true;
            }
          });
        }
      });
      /*eslint-enable no-func-assign */
    }
  }
  
  addEventPoolingTo(SyntheticEvent);
  
  /**
   * Helper to nullify syntheticEvent instance properties when destructing
   *
   * @param {String} propName
   * @param {?object} getVal
   * @return {object} defineProperty object
   */
  function getPooledWarningPropertyDefinition(propName, getVal) {
    var isFunction = typeof getVal === 'function';
    return {
      configurable: true,
      set: set,
      get: get
    };
  
    function set(val) {
      var action = isFunction ? 'setting the method' : 'setting the property';
      warn(action, 'This is effectively a no-op');
      return val;
    }
  
    function get() {
      var action = isFunction ? 'accessing the method' : 'accessing the property';
      var result = isFunction ? 'This is a no-op function' : 'This is set to null';
      warn(action, result);
      return getVal;
    }
  
    function warn(action, result) {
      var warningCondition = false;
      warning(warningCondition, "This synthetic event is reused for performance reasons. If you're seeing this, " + "you're %s `%s` on a released/nullified synthetic event. %s. " + 'If you must keep the original synthetic event around, use event.persist(). ' + 'See https://fb.me/react-event-pooling for more information.', action, propName, result);
    }
  }
  
  function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {
    var EventConstructor = this;
    if (EventConstructor.eventPool.length) {
      var instance = EventConstructor.eventPool.pop();
      EventConstructor.call(instance, dispatchConfig, targetInst, nativeEvent, nativeInst);
      return instance;
    }
    return new EventConstructor(dispatchConfig, targetInst, nativeEvent, nativeInst);
  }
  
  function releasePooledEvent(event) {
    var EventConstructor = this;
    !(event instanceof EventConstructor) ? invariant(false, 'Trying to release an event instance  into a pool of a different type.') : void 0;
    event.destructor();
    if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
      EventConstructor.eventPool.push(event);
    }
  }
  
  function addEventPoolingTo(EventConstructor) {
    EventConstructor.eventPool = [];
    EventConstructor.getPooled = getPooledEvent;
    EventConstructor.release = releasePooledEvent;
  }
  
  var SyntheticEvent$1 = SyntheticEvent;
  
  /**
   * @interface Event
   * @see http://www.w3.org/TR/DOM-Level-3-Events/#events-compositionevents
   */
  var CompositionEventInterface = {
    data: null
  };
  
  /**
   * @param {object} dispatchConfig Configuration used to dispatch this event.
   * @param {string} dispatchMarker Marker identifying the event target.
   * @param {object} nativeEvent Native browser event.
   * @extends {SyntheticEvent}
   */
  function SyntheticCompositionEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
    return SyntheticEvent$1.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
  }
  
  SyntheticEvent$1.augmentClass(SyntheticCompositionEvent, CompositionEventInterface);
  
  /**
   * @interface Event
   * @see http://www.w3.org/TR/2013/WD-DOM-Level-3-Events-20131105
   *      /#events-inputevents
   */
  var InputEventInterface = {
    data: null
  };
  
  /**
   * @param {object} dispatchConfig Configuration used to dispatch this event.
   * @param {string} dispatchMarker Marker identifying the event target.
   * @param {object} nativeEvent Native browser event.
   * @extends {SyntheticEvent}
   */
  function SyntheticInputEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
    return SyntheticEvent$1.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
  }
  
  SyntheticEvent$1.augmentClass(SyntheticInputEvent, InputEventInterface);
  
  var END_KEYCODES = [9, 13, 27, 32]; // Tab, Return, Esc, Space
  var START_KEYCODE = 229;
  
  var canUseCompositionEvent = ExecutionEnvironment.canUseDOM && 'CompositionEvent' in window;
  
  var documentMode = null;
  if (ExecutionEnvironment.canUseDOM && 'documentMode' in document) {
    documentMode = document.documentMode;
  }
  
  // Webkit offers a very useful `textInput` event that can be used to
  // directly represent `beforeInput`. The IE `textinput` event is not as
  // useful, so we don't use it.
  var canUseTextInputEvent = ExecutionEnvironment.canUseDOM && 'TextEvent' in window && !documentMode && !isPresto();
  
  // In IE9+, we have access to composition events, but the data supplied
  // by the native compositionend event may be incorrect. Japanese ideographic
  // spaces, for instance (\u3000) are not recorded correctly.
  var useFallbackCompositionData = ExecutionEnvironment.canUseDOM && (!canUseCompositionEvent || documentMode && documentMode > 8 && documentMode <= 11);
  
  /**
   * Opera <= 12 includes TextEvent in window, but does not fire
   * text input events. Rely on keypress instead.
   */
  function isPresto() {
    var opera = window.opera;
    return typeof opera === 'object' && typeof opera.version === 'function' && parseInt(opera.version(), 10) <= 12;
  }
  
  var SPACEBAR_CODE = 32;
  var SPACEBAR_CHAR = String.fromCharCode(SPACEBAR_CODE);
  
  // Events and their corresponding property names.
  var eventTypes = {
    beforeInput: {
      phasedRegistrationNames: {
        bubbled: 'onBeforeInput',
        captured: 'onBeforeInputCapture'
      },
      dependencies: ['topCompositionEnd', 'topKeyPress', 'topTextInput', 'topPaste']
    },
    compositionEnd: {
      phasedRegistrationNames: {
        bubbled: 'onCompositionEnd',
        captured: 'onCompositionEndCapture'
      },
      dependencies: ['topBlur', 'topCompositionEnd', 'topKeyDown', 'topKeyPress', 'topKeyUp', 'topMouseDown']
    },
    compositionStart: {
      phasedRegistrationNames: {
        bubbled: 'onCompositionStart',
        captured: 'onCompositionStartCapture'
      },
      dependencies: ['topBlur', 'topCompositionStart', 'topKeyDown', 'topKeyPress', 'topKeyUp', 'topMouseDown']
    },
    compositionUpdate: {
      phasedRegistrationNames: {
        bubbled: 'onCompositionUpdate',
        captured: 'onCompositionUpdateCapture'
      },
      dependencies: ['topBlur', 'topCompositionUpdate', 'topKeyDown', 'topKeyPress', 'topKeyUp', 'topMouseDown']
    }
  };
  
  // Track whether we've ever handled a keypress on the space key.
  var hasSpaceKeypress = false;
  
  /**
   * Return whether a native keypress event is assumed to be a command.
   * This is required because Firefox fires `keypress` events for key commands
   * (cut, copy, select-all, etc.) even though no character is inserted.
   */
  function isKeypressCommand(nativeEvent) {
    return (nativeEvent.ctrlKey || nativeEvent.altKey || nativeEvent.metaKey) &&
    // ctrlKey && altKey is equivalent to AltGr, and is not a command.
    !(nativeEvent.ctrlKey && nativeEvent.altKey);
  }
  
  /**
   * Translate native top level events into event types.
   *
   * @param {string} topLevelType
   * @return {object}
   */
  function getCompositionEventType(topLevelType) {
    switch (topLevelType) {
      case 'topCompositionStart':
        return eventTypes.compositionStart;
      case 'topCompositionEnd':
        return eventTypes.compositionEnd;
      case 'topCompositionUpdate':
        return eventTypes.compositionUpdate;
    }
  }
  
  /**
   * Does our fallback best-guess model think this event signifies that
   * composition has begun?
   *
   * @param {string} topLevelType
   * @param {object} nativeEvent
   * @return {boolean}
   */
  function isFallbackCompositionStart(topLevelType, nativeEvent) {
    return topLevelType === 'topKeyDown' && nativeEvent.keyCode === START_KEYCODE;
  }
  
  /**
   * Does our fallback mode think that this event is the end of composition?
   *
   * @param {string} topLevelType
   * @param {object} nativeEvent
   * @return {boolean}
   */
  function isFallbackCompositionEnd(topLevelType, nativeEvent) {
    switch (topLevelType) {
      case 'topKeyUp':
        // Command keys insert or clear IME input.
        return END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1;
      case 'topKeyDown':
        // Expect IME keyCode on each keydown. If we get any other
        // code we must have exited earlier.
        return nativeEvent.keyCode !== START_KEYCODE;
      case 'topKeyPress':
      case 'topMouseDown':
      case 'topBlur':
        // Events are not possible without cancelling IME.
        return true;
      default:
        return false;
    }
  }
  
  /**
   * Google Input Tools provides composition data via a CustomEvent,
   * with the `data` property populated in the `detail` object. If this
   * is available on the event object, use it. If not, this is a plain
   * composition event and we have nothing special to extract.
   *
   * @param {object} nativeEvent
   * @return {?string}
   */
  function getDataFromCustomEvent(nativeEvent) {
    var detail = nativeEvent.detail;
    if (typeof detail === 'object' && 'data' in detail) {
      return detail.data;
    }
    return null;
  }
  
  // Track the current IME composition status, if any.
  var isComposing = false;
  
  /**
   * @return {?object} A SyntheticCompositionEvent.
   */
  function extractCompositionEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
    var eventType;
    var fallbackData;
  
    if (canUseCompositionEvent) {
      eventType = getCompositionEventType(topLevelType);
    } else if (!isComposing) {
      if (isFallbackCompositionStart(topLevelType, nativeEvent)) {
        eventType = eventTypes.compositionStart;
      }
    } else if (isFallbackCompositionEnd(topLevelType, nativeEvent)) {
      eventType = eventTypes.compositionEnd;
    }
  
    if (!eventType) {
      return null;
    }
  
    if (useFallbackCompositionData) {
      // The current composition is stored statically and must not be
      // overwritten while composition continues.
      if (!isComposing && eventType === eventTypes.compositionStart) {
        isComposing = initialize(nativeEventTarget);
      } else if (eventType === eventTypes.compositionEnd) {
        if (isComposing) {
          fallbackData = getData();
        }
      }
    }
  
    var event = SyntheticCompositionEvent.getPooled(eventType, targetInst, nativeEvent, nativeEventTarget);
  
    if (fallbackData) {
      // Inject data generated from fallback path into the synthetic event.
      // This matches the property of native CompositionEventInterface.
      event.data = fallbackData;
    } else {
      var customData = getDataFromCustomEvent(nativeEvent);
      if (customData !== null) {
        event.data = customData;
      }
    }
  
    accumulateTwoPhaseDispatches(event);
    return event;
  }
  
  /**
   * @param {TopLevelTypes} topLevelType Record from `BrowserEventConstants`.
   * @param {object} nativeEvent Native browser event.
   * @return {?string} The string corresponding to this `beforeInput` event.
   */
  function getNativeBeforeInputChars(topLevelType, nativeEvent) {
    switch (topLevelType) {
      case 'topCompositionEnd':
        return getDataFromCustomEvent(nativeEvent);
      case 'topKeyPress':
        /**
         * If native `textInput` events are available, our goal is to make
         * use of them. However, there is a special case: the spacebar key.
         * In Webkit, preventing default on a spacebar `textInput` event
         * cancels character insertion, but it *also* causes the browser
         * to fall back to its default spacebar behavior of scrolling the
         * page.
         *
         * Tracking at:
         * https://code.google.com/p/chromium/issues/detail?id=355103
         *
         * To avoid this issue, use the keypress event as if no `textInput`
         * event is available.
         */
        var which = nativeEvent.which;
        if (which !== SPACEBAR_CODE) {
          return null;
        }
  
        hasSpaceKeypress = true;
        return SPACEBAR_CHAR;
  
      case 'topTextInput':
        // Record the characters to be added to the DOM.
        var chars = nativeEvent.data;
  
        // If it's a spacebar character, assume that we have already handled
        // it at the keypress level and bail immediately. Android Chrome
        // doesn't give us keycodes, so we need to blacklist it.
        if (chars === SPACEBAR_CHAR && hasSpaceKeypress) {
          return null;
        }
  
        return chars;
  
      default:
        // For other native event types, do nothing.
        return null;
    }
  }
  
  /**
   * For browsers that do not provide the `textInput` event, extract the
   * appropriate string to use for SyntheticInputEvent.
   *
   * @param {string} topLevelType Record from `BrowserEventConstants`.
   * @param {object} nativeEvent Native browser event.
   * @return {?string} The fallback string for this `beforeInput` event.
   */
  function getFallbackBeforeInputChars(topLevelType, nativeEvent) {
    // If we are currently composing (IME) and using a fallback to do so,
    // try to extract the composed characters from the fallback object.
    // If composition event is available, we extract a string only at
    // compositionevent, otherwise extract it at fallback events.
    if (isComposing) {
      if (topLevelType === 'topCompositionEnd' || !canUseCompositionEvent && isFallbackCompositionEnd(topLevelType, nativeEvent)) {
        var chars = getData();
        reset();
        isComposing = false;
        return chars;
      }
      return null;
    }
  
    switch (topLevelType) {
      case 'topPaste':
        // If a paste event occurs after a keypress, throw out the input
        // chars. Paste events should not lead to BeforeInput events.
        return null;
      case 'topKeyPress':
        /**
         * As of v27, Firefox may fire keypress events even when no character
         * will be inserted. A few possibilities:
         *
         * - `which` is `0`. Arrow keys, Esc key, etc.
         *
         * - `which` is the pressed key code, but no char is available.
         *   Ex: 'AltGr + d` in Polish. There is no modified character for
         *   this key combination and no character is inserted into the
         *   document, but FF fires the keypress for char code `100` anyway.
         *   No `input` event will occur.
         *
         * - `which` is the pressed key code, but a command combination is
         *   being used. Ex: `Cmd+C`. No character is inserted, and no
         *   `input` event will occur.
         */
        if (!isKeypressCommand(nativeEvent)) {
          // IE fires the `keypress` event when a user types an emoji via
          // Touch keyboard of Windows.  In such a case, the `char` property
          // holds an emoji character like `\uD83D\uDE0A`.  Because its length
          // is 2, the property `which` does not represent an emoji correctly.
          // In such a case, we directly return the `char` property instead of
          // using `which`.
          if (nativeEvent.char && nativeEvent.char.length > 1) {
            return nativeEvent.char;
          } else if (nativeEvent.which) {
            return String.fromCharCode(nativeEvent.which);
          }
        }
        return null;
      case 'topCompositionEnd':
        return useFallbackCompositionData ? null : nativeEvent.data;
      default:
        return null;
    }
  }
  
  /**
   * Extract a SyntheticInputEvent for `beforeInput`, based on either native
   * `textInput` or fallback behavior.
   *
   * @return {?object} A SyntheticInputEvent.
   */
  function extractBeforeInputEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
    var chars;
  
    if (canUseTextInputEvent) {
      chars = getNativeBeforeInputChars(topLevelType, nativeEvent);
    } else {
      chars = getFallbackBeforeInputChars(topLevelType, nativeEvent);
    }
  
    // If no characters are being inserted, no BeforeInput event should
    // be fired.
    if (!chars) {
      return null;
    }
  
    var event = SyntheticInputEvent.getPooled(eventTypes.beforeInput, targetInst, nativeEvent, nativeEventTarget);
  
    event.data = chars;
    accumulateTwoPhaseDispatches(event);
    return event;
  }
  
  /**
   * Create an `onBeforeInput` event to match
   * http://www.w3.org/TR/2013/WD-DOM-Level-3-Events-20131105/#events-inputevents.
   *
   * This event plugin is based on the native `textInput` event
   * available in Chrome, Safari, Opera, and IE. This event fires after
   * `onKeyPress` and `onCompositionEnd`, but before `onInput`.
   *
   * `beforeInput` is spec'd but not implemented in any browsers, and
   * the `input` event does not provide any useful information about what has
   * actually been added, contrary to the spec. Thus, `textInput` is the best
   * available event to identify the characters that have actually been inserted
   * into the target node.
   *
   * This plugin is also responsible for emitting `composition` events, thus
   * allowing us to share composition fallback code for both `beforeInput` and
   * `composition` event types.
   */
  var BeforeInputEventPlugin = {
    eventTypes: eventTypes,
  
    extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
      return [extractCompositionEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget), extractBeforeInputEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget)];
    }
  };
  
  // Use to restore controlled state after a change event has fired.
  
  var fiberHostComponent = null;
  
  var ReactControlledComponentInjection = {
    injectFiberControlledHostComponent: function (hostComponentImpl) {
      // The fiber implementation doesn't use dynamic dispatch so we need to
      // inject the implementation.
      fiberHostComponent = hostComponentImpl;
    }
  };
  
  var restoreTarget = null;
  var restoreQueue = null;
  
  function restoreStateOfTarget(target) {
    // We perform this translation at the end of the event loop so that we
    // always receive the correct fiber here
    var internalInstance = getInstanceFromNode(target);
    if (!internalInstance) {
      // Unmounted
      return;
    }
    !(fiberHostComponent && typeof fiberHostComponent.restoreControlledState === 'function') ? invariant(false, 'Fiber needs to be injected to handle a fiber target for controlled events. This error is likely caused by a bug in React. Please file an issue.') : void 0;
    var props = getFiberCurrentPropsFromNode(internalInstance.stateNode);
    fiberHostComponent.restoreControlledState(internalInstance.stateNode, internalInstance.type, props);
  }
  
  var injection$3 = ReactControlledComponentInjection;
  
  function enqueueStateRestore(target) {
    if (restoreTarget) {
      if (restoreQueue) {
        restoreQueue.push(target);
      } else {
        restoreQueue = [target];
      }
    } else {
      restoreTarget = target;
    }
  }
  
  function restoreStateIfNeeded() {
    if (!restoreTarget) {
      return;
    }
    var target = restoreTarget;
    var queuedTargets = restoreQueue;
    restoreTarget = null;
    restoreQueue = null;
  
    restoreStateOfTarget(target);
    if (queuedTargets) {
      for (var i = 0; i < queuedTargets.length; i++) {
        restoreStateOfTarget(queuedTargets[i]);
      }
    }
  }
  
  var ReactControlledComponent = Object.freeze({
    injection: injection$3,
    enqueueStateRestore: enqueueStateRestore,
    restoreStateIfNeeded: restoreStateIfNeeded
  });
  
  // Used as a way to call batchedUpdates when we don't have a reference to
  // the renderer. Such as when we're dispatching events or if third party
  // libraries need to call batchedUpdates. Eventually, this API will go away when
  // everything is batched by default. We'll then have a similar API to opt-out of
  // scheduled work and instead do synchronous work.
  
  // Defaults
  var fiberBatchedUpdates = function (fn, bookkeeping) {
    return fn(bookkeeping);
  };
  
  var isNestingBatched = false;
  function batchedUpdates(fn, bookkeeping) {
    if (isNestingBatched) {
      // If we are currently inside another batch, we need to wait until it
      // fully completes before restoring state. Therefore, we add the target to
      // a queue of work.
      return fiberBatchedUpdates(fn, bookkeeping);
    }
    isNestingBatched = true;
    try {
      return fiberBatchedUpdates(fn, bookkeeping);
    } finally {
      // Here we wait until all updates have propagated, which is important
      // when using controlled components within layers:
      // https://github.com/facebook/react/issues/1698
      // Then we restore state of any controlled component.
      isNestingBatched = false;
      restoreStateIfNeeded();
    }
  }
  
  var ReactGenericBatchingInjection = {
    injectFiberBatchedUpdates: function (_batchedUpdates) {
      fiberBatchedUpdates = _batchedUpdates;
    }
  };
  
  var injection$4 = ReactGenericBatchingInjection;
  
  /**
   * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary
   */
  var supportedInputTypes = {
    color: true,
    date: true,
    datetime: true,
    'datetime-local': true,
    email: true,
    month: true,
    number: true,
    password: true,
    range: true,
    search: true,
    tel: true,
    text: true,
    time: true,
    url: true,
    week: true
  };
  
  function isTextInputElement(elem) {
    var nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
  
    if (nodeName === 'input') {
      return !!supportedInputTypes[elem.type];
    }
  
    if (nodeName === 'textarea') {
      return true;
    }
  
    return false;
  }
  
  /**
   * HTML nodeType values that represent the type of the node
   */
  
  var ELEMENT_NODE = 1;
  var TEXT_NODE = 3;
  var COMMENT_NODE = 8;
  var DOCUMENT_NODE = 9;
  var DOCUMENT_FRAGMENT_NODE = 11;
  
  /**
   * Gets the target node from a native browser event by accounting for
   * inconsistencies in browser DOM APIs.
   *
   * @param {object} nativeEvent Native browser event.
   * @return {DOMEventTarget} Target node.
   */
  function getEventTarget(nativeEvent) {
    var target = nativeEvent.target || nativeEvent.srcElement || window;
  
    // Normalize SVG <use> element events #4963
    if (target.correspondingUseElement) {
      target = target.correspondingUseElement;
    }
  
    // Safari may fire events on text nodes (Node.TEXT_NODE is 3).
    // @see http://www.quirksmode.org/js/events_properties.html
    return target.nodeType === TEXT_NODE ? target.parentNode : target;
  }
  
  var useHasFeature;
  if (ExecutionEnvironment.canUseDOM) {
    useHasFeature = document.implementation && document.implementation.hasFeature &&
    // always returns true in newer browsers as per the standard.
    // @see http://dom.spec.whatwg.org/#dom-domimplementation-hasfeature
    document.implementation.hasFeature('', '') !== true;
  }
  
  /**
   * Checks if an event is supported in the current execution environment.
   *
   * NOTE: This will not work correctly for non-generic events such as `change`,
   * `reset`, `load`, `error`, and `select`.
   *
   * Borrows from Modernizr.
   *
   * @param {string} eventNameSuffix Event name, e.g. "click".
   * @param {?boolean} capture Check if the capture phase is supported.
   * @return {boolean} True if the event is supported.
   * @internal
   * @license Modernizr 3.0.0pre (Custom Build) | MIT
   */
  function isEventSupported(eventNameSuffix, capture) {
    if (!ExecutionEnvironment.canUseDOM || capture && !('addEventListener' in document)) {
      return false;
    }
  
    var eventName = 'on' + eventNameSuffix;
    var isSupported = eventName in document;
  
    if (!isSupported) {
      var element = document.createElement('div');
      element.setAttribute(eventName, 'return;');
      isSupported = typeof element[eventName] === 'function';
    }
  
    if (!isSupported && useHasFeature && eventNameSuffix === 'wheel') {
      // This is the only way to test support for the `wheel` event in IE9+.
      isSupported = document.implementation.hasFeature('Events.wheel', '3.0');
    }
  
    return isSupported;
  }
  
  function isCheckable(elem) {
    var type = elem.type;
    var nodeName = elem.nodeName;
    return nodeName && nodeName.toLowerCase() === 'input' && (type === 'checkbox' || type === 'radio');
  }
  
  function getTracker(node) {
    return node._valueTracker;
  }
  
  function detachTracker(node) {
    node._valueTracker = null;
  }
  
  function getValueFromNode(node) {
    var value = '';
    if (!node) {
      return value;
    }
  
    if (isCheckable(node)) {
      value = node.checked ? 'true' : 'false';
    } else {
      value = node.value;
    }
  
    return value;
  }
  
  function trackValueOnNode(node) {
    var valueField = isCheckable(node) ? 'checked' : 'value';
    var descriptor = Object.getOwnPropertyDescriptor(node.constructor.prototype, valueField);
  
    var currentValue = '' + node[valueField];
  
    // if someone has already defined a value or Safari, then bail
    // and don't track value will cause over reporting of changes,
    // but it's better then a hard failure
    // (needed for certain tests that spyOn input values and Safari)
    if (node.hasOwnProperty(valueField) || typeof descriptor.get !== 'function' || typeof descriptor.set !== 'function') {
      return;
    }
  
    Object.defineProperty(node, valueField, {
      enumerable: descriptor.enumerable,
      configurable: true,
      get: function () {
        return descriptor.get.call(this);
      },
      set: function (value) {
        currentValue = '' + value;
        descriptor.set.call(this, value);
      }
    });
  
    var tracker = {
      getValue: function () {
        return currentValue;
      },
      setValue: function (value) {
        currentValue = '' + value;
      },
      stopTracking: function () {
        detachTracker(node);
        delete node[valueField];
      }
    };
    return tracker;
  }
  
  function track(node) {
    if (getTracker(node)) {
      return;
    }
  
    // TODO: Once it's just Fiber we can move this to node._wrapperState
    node._valueTracker = trackValueOnNode(node);
  }
  
  function updateValueIfChanged(node) {
    if (!node) {
      return false;
    }
  
    var tracker = getTracker(node);
    // if there is no tracker at this point it's unlikely
    // that trying again will succeed
    if (!tracker) {
      return true;
    }
  
    var lastValue = tracker.getValue();
    var nextValue = getValueFromNode(node);
    if (nextValue !== lastValue) {
      tracker.setValue(nextValue);
      return true;
    }
    return false;
  }
  
  var eventTypes$1 = {
    change: {
      phasedRegistrationNames: {
        bubbled: 'onChange',
        captured: 'onChangeCapture'
      },
      dependencies: ['topBlur', 'topChange', 'topClick', 'topFocus', 'topInput', 'topKeyDown', 'topKeyUp', 'topSelectionChange']
    }
  };
  
  function createAndAccumulateChangeEvent(inst, nativeEvent, target) {
    var event = SyntheticEvent$1.getPooled(eventTypes$1.change, inst, nativeEvent, target);
    event.type = 'change';
    // Flag this event loop as needing state restore.
    enqueueStateRestore(target);
    accumulateTwoPhaseDispatches(event);
    return event;
  }
  /**
   * For IE shims
   */
  var activeElement = null;
  var activeElementInst = null;
  
  /**
   * SECTION: handle `change` event
   */
  function shouldUseChangeEvent(elem) {
    var nodeName = elem.nodeName && elem.nodeName.toLowerCase();
    return nodeName === 'select' || nodeName === 'input' && elem.type === 'file';
  }
  
  function manualDispatchChangeEvent(nativeEvent) {
    var event = createAndAccumulateChangeEvent(activeElementInst, nativeEvent, getEventTarget(nativeEvent));
  
    // If change and propertychange bubbled, we'd just bind to it like all the
    // other events and have it go through ReactBrowserEventEmitter. Since it
    // doesn't, we manually listen for the events and so we have to enqueue and
    // process the abstract event manually.
    //
    // Batching is necessary here in order to ensure that all event handlers run
    // before the next rerender (including event handlers attached to ancestor
    // elements instead of directly on the input). Without this, controlled
    // components don't work properly in conjunction with event bubbling because
    // the component is rerendered and the value reverted before all the event
    // handlers can run. See https://github.com/facebook/react/issues/708.
    batchedUpdates(runEventInBatch, event);
  }
  
  function runEventInBatch(event) {
    enqueueEvents(event);
    processEventQueue(false);
  }
  
  function getInstIfValueChanged(targetInst) {
    var targetNode = getNodeFromInstance$1(targetInst);
    if (updateValueIfChanged(targetNode)) {
      return targetInst;
    }
  }
  
  function getTargetInstForChangeEvent(topLevelType, targetInst) {
    if (topLevelType === 'topChange') {
      return targetInst;
    }
  }
  
  /**
   * SECTION: handle `input` event
   */
  var isInputEventSupported = false;
  if (ExecutionEnvironment.canUseDOM) {
    // IE9 claims to support the input event but fails to trigger it when
    // deleting text, so we ignore its input events.
    isInputEventSupported = isEventSupported('input') && (!document.documentMode || document.documentMode > 9);
  }
  
  /**
   * (For IE <=9) Starts tracking propertychange events on the passed-in element
   * and override the value property so that we can distinguish user events from
   * value changes in JS.
   */
  function startWatchingForValueChange(target, targetInst) {
    activeElement = target;
    activeElementInst = targetInst;
    activeElement.attachEvent('onpropertychange', handlePropertyChange);
  }
  
  /**
   * (For IE <=9) Removes the event listeners from the currently-tracked element,
   * if any exists.
   */
  function stopWatchingForValueChange() {
    if (!activeElement) {
      return;
    }
    activeElement.detachEvent('onpropertychange', handlePropertyChange);
    activeElement = null;
    activeElementInst = null;
  }
  
  /**
   * (For IE <=9) Handles a propertychange event, sending a `change` event if
   * the value of the active element has changed.
   */
  function handlePropertyChange(nativeEvent) {
    if (nativeEvent.propertyName !== 'value') {
      return;
    }
    if (getInstIfValueChanged(activeElementInst)) {
      manualDispatchChangeEvent(nativeEvent);
    }
  }
  
  function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
    if (topLevelType === 'topFocus') {
      // In IE9, propertychange fires for most input events but is buggy and
      // doesn't fire when text is deleted, but conveniently, selectionchange
      // appears to fire in all of the remaining cases so we catch those and
      // forward the event if the value has changed
      // In either case, we don't want to call the event handler if the value
      // is changed from JS so we redefine a setter for `.value` that updates
      // our activeElementValue variable, allowing us to ignore those changes
      //
      // stopWatching() should be a noop here but we call it just in case we
      // missed a blur event somehow.
      stopWatchingForValueChange();
      startWatchingForValueChange(target, targetInst);
    } else if (topLevelType === 'topBlur') {
      stopWatchingForValueChange();
    }
  }
  
  // For IE8 and IE9.
  function getTargetInstForInputEventPolyfill(topLevelType, targetInst) {
    if (topLevelType === 'topSelectionChange' || topLevelType === 'topKeyUp' || topLevelType === 'topKeyDown') {
      // On the selectionchange event, the target is just document which isn't
      // helpful for us so just check activeElement instead.
      //
      // 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire
      // propertychange on the first input event after setting `value` from a
      // script and fires only keydown, keypress, keyup. Catching keyup usually
      // gets it and catching keydown lets us fire an event for the first
      // keystroke if user does a key repeat (it'll be a little delayed: right
      // before the second keystroke). Other input methods (e.g., paste) seem to
      // fire selectionchange normally.
      return getInstIfValueChanged(activeElementInst);
    }
  }
  
  /**
   * SECTION: handle `click` event
   */
  function shouldUseClickEvent(elem) {
    // Use the `click` event to detect changes to checkbox and radio inputs.
    // This approach works across all browsers, whereas `change` does not fire
    // until `blur` in IE8.
    var nodeName = elem.nodeName;
    return nodeName && nodeName.toLowerCase() === 'input' && (elem.type === 'checkbox' || elem.type === 'radio');
  }
  
  function getTargetInstForClickEvent(topLevelType, targetInst) {
    if (topLevelType === 'topClick') {
      return getInstIfValueChanged(targetInst);
    }
  }
  
  function getTargetInstForInputOrChangeEvent(topLevelType, targetInst) {
    if (topLevelType === 'topInput' || topLevelType === 'topChange') {
      return getInstIfValueChanged(targetInst);
    }
  }
  
  function handleControlledInputBlur(inst, node) {
    // TODO: In IE, inst is occasionally null. Why?
    if (inst == null) {
      return;
    }
  
    // Fiber and ReactDOM keep wrapper state in separate places
    var state = inst._wrapperState || node._wrapperState;
  
    if (!state || !state.controlled || node.type !== 'number') {
      return;
    }
  
    // If controlled, assign the value attribute to the current value on blur
    var value = '' + node.value;
    if (node.getAttribute('value') !== value) {
      node.setAttribute('value', value);
    }
  }
  
  /**
   * This plugin creates an `onChange` event that normalizes change events
   * across form elements. This event fires at a time when it's possible to
   * change the element's value without seeing a flicker.
   *
   * Supported elements are:
   * - input (see `isTextInputElement`)
   * - textarea
   * - select
   */
  var ChangeEventPlugin = {
    eventTypes: eventTypes$1,
  
    _isInputEventSupported: isInputEventSupported,
  
    extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
      var targetNode = targetInst ? getNodeFromInstance$1(targetInst) : window;
  
      var getTargetInstFunc, handleEventFunc;
      if (shouldUseChangeEvent(targetNode)) {
        getTargetInstFunc = getTargetInstForChangeEvent;
      } else if (isTextInputElement(targetNode)) {
        if (isInputEventSupported) {
          getTargetInstFunc = getTargetInstForInputOrChangeEvent;
        } else {
          getTargetInstFunc = getTargetInstForInputEventPolyfill;
          handleEventFunc = handleEventsForInputEventPolyfill;
        }
      } else if (shouldUseClickEvent(targetNode)) {
        getTargetInstFunc = getTargetInstForClickEvent;
      }
  
      if (getTargetInstFunc) {
        var inst = getTargetInstFunc(topLevelType, targetInst);
        if (inst) {
          var event = createAndAccumulateChangeEvent(inst, nativeEvent, nativeEventTarget);
          return event;
        }
      }
  
      if (handleEventFunc) {
        handleEventFunc(topLevelType, targetNode, targetInst);
      }
  
      // When blurring, set the value attribute for number inputs
      if (topLevelType === 'topBlur') {
        handleControlledInputBlur(targetInst, targetNode);
      }
    }
  };
  
  /**
   * Module that is injectable into `EventPluginHub`, that specifies a
   * deterministic ordering of `EventPlugin`s. A convenient way to reason about
   * plugins, without having to package every one of them. This is better than
   * having plugins be ordered in the same order that they are injected because
   * that ordering would be influenced by the packaging order.
   * `ResponderEventPlugin` must occur before `SimpleEventPlugin` so that
   * preventing default on events is convenient in `SimpleEventPlugin` handlers.
   */
  var DOMEventPluginOrder = ['ResponderEventPlugin', 'SimpleEventPlugin', 'TapEventPlugin', 'EnterLeaveEventPlugin', 'ChangeEventPlugin', 'SelectEventPlugin', 'BeforeInputEventPlugin'];
  
  /**
   * @interface UIEvent
   * @see http://www.w3.org/TR/DOM-Level-3-Events/
   */
  var UIEventInterface = {
    view: null,
    detail: null
  };
  
  /**
   * @param {object} dispatchConfig Configuration used to dispatch this event.
   * @param {string} dispatchMarker Marker identifying the event target.
   * @param {object} nativeEvent Native browser event.
   * @extends {SyntheticEvent}
   */
  function SyntheticUIEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
    return SyntheticEvent$1.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
  }
  
  SyntheticEvent$1.augmentClass(SyntheticUIEvent, UIEventInterface);
  
  /**
   * Translation from modifier key to the associated property in the event.
   * @see http://www.w3.org/TR/DOM-Level-3-Events/#keys-Modifiers
   */
  
  var modifierKeyToProp = {
    Alt: 'altKey',
    Control: 'ctrlKey',
    Meta: 'metaKey',
    Shift: 'shiftKey'
  };
  
  // IE8 does not implement getModifierState so we simply map it to the only
  // modifier keys exposed by the event itself, does not support Lock-keys.
  // Currently, all major browsers except Chrome seems to support Lock-keys.
  function modifierStateGetter(keyArg) {
    var syntheticEvent = this;
    var nativeEvent = syntheticEvent.nativeEvent;
    if (nativeEvent.getModifierState) {
      return nativeEvent.getModifierState(keyArg);
    }
    var keyProp = modifierKeyToProp[keyArg];
    return keyProp ? !!nativeEvent[keyProp] : false;
  }
  
  function getEventModifierState(nativeEvent) {
    return modifierStateGetter;
  }
  
  /**
   * @interface MouseEvent
   * @see http://www.w3.org/TR/DOM-Level-3-Events/
   */
  var MouseEventInterface = {
    screenX: null,
    screenY: null,
    clientX: null,
    clientY: null,
    pageX: null,
    pageY: null,
    ctrlKey: null,
    shiftKey: null,
    altKey: null,
    metaKey: null,
    getModifierState: getEventModifierState,
    button: null,
    buttons: null,
    relatedTarget: function (event) {
      return event.relatedTarget || (event.fromElement === event.srcElement ? event.toElement : event.fromElement);
    }
  };
  
  /**
   * @param {object} dispatchConfig Configuration used to dispatch this event.
   * @param {string} dispatchMarker Marker identifying the event target.
   * @param {object} nativeEvent Native browser event.
   * @extends {SyntheticUIEvent}
   */
  function SyntheticMouseEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
    return SyntheticUIEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
  }
  
  SyntheticUIEvent.augmentClass(SyntheticMouseEvent, MouseEventInterface);
  
  var eventTypes$2 = {
    mouseEnter: {
      registrationName: 'onMouseEnter',
      dependencies: ['topMouseOut', 'topMouseOver']
    },
    mouseLeave: {
      registrationName: 'onMouseLeave',
      dependencies: ['topMouseOut', 'topMouseOver']
    }
  };
  
  var EnterLeaveEventPlugin = {
    eventTypes: eventTypes$2,
  
    /**
     * For almost every interaction we care about, there will be both a top-level
     * `mouseover` and `mouseout` event that occurs. Only use `mouseout` so that
     * we do not extract duplicate events. However, moving the mouse into the
     * browser from outside will not fire a `mouseout` event. In this case, we use
     * the `mouseover` top-level event.
     */
    extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
      if (topLevelType === 'topMouseOver' && (nativeEvent.relatedTarget || nativeEvent.fromElement)) {
        return null;
      }
      if (topLevelType !== 'topMouseOut' && topLevelType !== 'topMouseOver') {
        // Must not be a mouse in or mouse out - ignoring.
        return null;
      }
  
      var win;
      if (nativeEventTarget.window === nativeEventTarget) {
        // `nativeEventTarget` is probably a window object.
        win = nativeEventTarget;
      } else {
        // TODO: Figure out why `ownerDocument` is sometimes undefined in IE8.
        var doc = nativeEventTarget.ownerDocument;
        if (doc) {
          win = doc.defaultView || doc.parentWindow;
        } else {
          win = window;
        }
      }
  
      var from;
      var to;
      if (topLevelType === 'topMouseOut') {
        from = targetInst;
        var related = nativeEvent.relatedTarget || nativeEvent.toElement;
        to = related ? getClosestInstanceFromNode(related) : null;
      } else {
        // Moving to a node from outside the window.
        from = null;
        to = targetInst;
      }
  
      if (from === to) {
        // Nothing pertains to our managed components.
        return null;
      }
  
      var fromNode = from == null ? win : getNodeFromInstance$1(from);
      var toNode = to == null ? win : getNodeFromInstance$1(to);
  
      var leave = SyntheticMouseEvent.getPooled(eventTypes$2.mouseLeave, from, nativeEvent, nativeEventTarget);
      leave.type = 'mouseleave';
      leave.target = fromNode;
      leave.relatedTarget = toNode;
  
      var enter = SyntheticMouseEvent.getPooled(eventTypes$2.mouseEnter, to, nativeEvent, nativeEventTarget);
      enter.type = 'mouseenter';
      enter.target = toNode;
      enter.relatedTarget = fromNode;
  
      accumulateEnterLeaveDispatches(leave, enter, from, to);
  
      return [leave, enter];
    }
  };
  
  /**
   * `ReactInstanceMap` maintains a mapping from a public facing stateful
   * instance (key) and the internal representation (value). This allows public
   * methods to accept the user facing instance as an argument and map them back
   * to internal methods.
   *
   * Note that this module is currently shared and assumed to be stateless.
   * If this becomes an actual Map, that will break.
   */
  
  /**
   * This API should be called `delete` but we'd have to make sure to always
   * transform these to strings for IE support. When this transform is fully
   * supported we can rename it.
   */
  
  
  function get(key) {
    return key._reactInternalFiber;
  }
  
  function has(key) {
    return key._reactInternalFiber !== undefined;
  }
  
  function set(key, value) {
    key._reactInternalFiber = value;
  }
  
  var ReactInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
  
  var ReactCurrentOwner = ReactInternals.ReactCurrentOwner;
  var ReactDebugCurrentFrame = ReactInternals.ReactDebugCurrentFrame;
  
  function getComponentName(fiber) {
    var type = fiber.type;
  
    if (typeof type === 'string') {
      return type;
    }
    if (typeof type === 'function') {
      return type.displayName || type.name;
    }
    return null;
  }
  
  // Don't change these two values:
  var NoEffect = 0; //           0b00000000
  var PerformedWork = 1; //      0b00000001
  
  // You can change the rest (and add more).
  var Placement = 2; //          0b00000010
  var Update = 4; //             0b00000100
  var PlacementAndUpdate = 6; // 0b00000110
  var Deletion = 8; //           0b00001000
  var ContentReset = 16; //      0b00010000
  var Callback = 32; //          0b00100000
  var Err = 64; //               0b01000000
  var Ref = 128; //              0b10000000
  
  var MOUNTING = 1;
  var MOUNTED = 2;
  var UNMOUNTED = 3;
  
  function isFiberMountedImpl(fiber) {
    var node = fiber;
    if (!fiber.alternate) {
      // If there is no alternate, this might be a new tree that isn't inserted
      // yet. If it is, then it will have a pending insertion effect on it.
      if ((node.effectTag & Placement) !== NoEffect) {
        return MOUNTING;
      }
      while (node['return']) {
        node = node['return'];
        if ((node.effectTag & Placement) !== NoEffect) {
          return MOUNTING;
        }
      }
    } else {
      while (node['return']) {
        node = node['return'];
      }
    }
    if (node.tag === HostRoot) {
      // TODO: Check if this was a nested HostRoot when used with
      // renderContainerIntoSubtree.
      return MOUNTED;
    }
    // If we didn't hit the root, that means that we're in an disconnected tree
    // that has been unmounted.
    return UNMOUNTED;
  }
  
  function isFiberMounted(fiber) {
    return isFiberMountedImpl(fiber) === MOUNTED;
  }
  
  function isMounted(component) {
    {
      var owner = ReactCurrentOwner.current;
      if (owner !== null && owner.tag === ClassComponent) {
        var ownerFiber = owner;
        var instance = ownerFiber.stateNode;
        warning(instance._warnedAboutRefsInRender, '%s is accessing isMounted inside its render() function. ' + 'render() should be a pure function of props and state. It should ' + 'never access something that requires stale data from the previous ' + 'render, such as refs. Move this logic to componentDidMount and ' + 'componentDidUpdate instead.', getComponentName(ownerFiber) || 'A component');
        instance._warnedAboutRefsInRender = true;
      }
    }
  
    var fiber = get(component);
    if (!fiber) {
      return false;
    }
    return isFiberMountedImpl(fiber) === MOUNTED;
  }
  
  function assertIsMounted(fiber) {
    !(isFiberMountedImpl(fiber) === MOUNTED) ? invariant(false, 'Unable to find node on an unmounted component.') : void 0;
  }
  
  function findCurrentFiberUsingSlowPath(fiber) {
    var alternate = fiber.alternate;
    if (!alternate) {
      // If there is no alternate, then we only need to check if it is mounted.
      var state = isFiberMountedImpl(fiber);
      !(state !== UNMOUNTED) ? invariant(false, 'Unable to find node on an unmounted component.') : void 0;
      if (state === MOUNTING) {
        return null;
      }
      return fiber;
    }
    // If we have two possible branches, we'll walk backwards up to the root
    // to see what path the root points to. On the way we may hit one of the
    // special cases and we'll deal with them.
    var a = fiber;
    var b = alternate;
    while (true) {
      var parentA = a['return'];
      var parentB = parentA ? parentA.alternate : null;
      if (!parentA || !parentB) {
        // We're at the root.
        break;
      }
  
      // If both copies of the parent fiber point to the same child, we can
      // assume that the child is current. This happens when we bailout on low
      // priority: the bailed out fiber's child reuses the current child.
      if (parentA.child === parentB.child) {
        var child = parentA.child;
        while (child) {
          if (child === a) {
            // We've determined that A is the current branch.
            assertIsMounted(parentA);
            return fiber;
          }
          if (child === b) {
            // We've determined that B is the current branch.
            assertIsMounted(parentA);
            return alternate;
          }
          child = child.sibling;
        }
        // We should never have an alternate for any mounting node. So the only
        // way this could possibly happen is if this was unmounted, if at all.
        invariant(false, 'Unable to find node on an unmounted component.');
      }
  
      if (a['return'] !== b['return']) {
        // The return pointer of A and the return pointer of B point to different
        // fibers. We assume that return pointers never criss-cross, so A must
        // belong to the child set of A.return, and B must belong to the child
        // set of B.return.
        a = parentA;
        b = parentB;
      } else {
        // The return pointers point to the same fiber. We'll have to use the
        // default, slow path: scan the child sets of each parent alternate to see
        // which child belongs to which set.
        //
        // Search parent A's child set
        var didFindChild = false;
        var _child = parentA.child;
        while (_child) {
          if (_child === a) {
            didFindChild = true;
            a = parentA;
            b = parentB;
            break;
          }
          if (_child === b) {
            didFindChild = true;
            b = parentA;
            a = parentB;
            break;
          }
          _child = _child.sibling;
        }
        if (!didFindChild) {
          // Search parent B's child set
          _child = parentB.child;
          while (_child) {
            if (_child === a) {
              didFindChild = true;
              a = parentB;
              b = parentA;
              break;
            }
            if (_child === b) {
              didFindChild = true;
              b = parentB;
              a = parentA;
              break;
            }
            _child = _child.sibling;
          }
          !didFindChild ? invariant(false, 'Child was not found in either parent set. This indicates a bug in React related to the return pointer. Please file an issue.') : void 0;
        }
      }
  
      !(a.alternate === b) ? invariant(false, 'Return fibers should always be each others\' alternates. This error is likely caused by a bug in React. Please file an issue.') : void 0;
    }
    // If the root is not a host container, we're in a disconnected tree. I.e.
    // unmounted.
    !(a.tag === HostRoot) ? invariant(false, 'Unable to find node on an unmounted component.') : void 0;
    if (a.stateNode.current === a) {
      // We've determined that A is the current branch.
      return fiber;
    }
    // Otherwise B has to be current branch.
    return alternate;
  }
  
  function findCurrentHostFiber(parent) {
    var currentParent = findCurrentFiberUsingSlowPath(parent);
    if (!currentParent) {
      return null;
    }
  
    // Next we'll drill down this component to find the first HostComponent/Text.
    var node = currentParent;
    while (true) {
      if (node.tag === HostComponent || node.tag === HostText) {
        return node;
      } else if (node.child) {
        node.child['return'] = node;
        node = node.child;
        continue;
      }
      if (node === currentParent) {
        return null;
      }
      while (!node.sibling) {
        if (!node['return'] || node['return'] === currentParent) {
          return null;
        }
        node = node['return'];
      }
      node.sibling['return'] = node['return'];
      node = node.sibling;
    }
    // Flow needs the return null here, but ESLint complains about it.
    // eslint-disable-next-line no-unreachable
    return null;
  }
  
  function findCurrentHostFiberWithNoPortals(parent) {
    var currentParent = findCurrentFiberUsingSlowPath(parent);
    if (!currentParent) {
      return null;
    }
  
    // Next we'll drill down this component to find the first HostComponent/Text.
    var node = currentParent;
    while (true) {
      if (node.tag === HostComponent || node.tag === HostText) {
        return node;
      } else if (node.child && node.tag !== HostPortal) {
        node.child['return'] = node;
        node = node.child;
        continue;
      }
      if (node === currentParent) {
        return null;
      }
      while (!node.sibling) {
        if (!node['return'] || node['return'] === currentParent) {
          return null;
        }
        node = node['return'];
      }
      node.sibling['return'] = node['return'];
      node = node.sibling;
    }
    // Flow needs the return null here, but ESLint complains about it.
    // eslint-disable-next-line no-unreachable
    return null;
  }
  
  var CALLBACK_BOOKKEEPING_POOL_SIZE = 10;
  var callbackBookkeepingPool = [];
  
  /**
   * Find the deepest React component completely containing the root of the
   * passed-in instance (for use when entire React trees are nested within each
   * other). If React trees are not nested, returns null.
   */
  function findRootContainerNode(inst) {
    // TODO: It may be a good idea to cache this to prevent unnecessary DOM
    // traversal, but caching is difficult to do correctly without using a
    // mutation observer to listen for all DOM changes.
    while (inst['return']) {
      inst = inst['return'];
    }
    if (inst.tag !== HostRoot) {
      // This can happen if we're in a detached tree.
      return null;
    }
    return inst.stateNode.containerInfo;
  }
  
  // Used to store ancestor hierarchy in top level callback
  function getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst) {
    if (callbackBookkeepingPool.length) {
      var instance = callbackBookkeepingPool.pop();
      instance.topLevelType = topLevelType;
      instance.nativeEvent = nativeEvent;
      instance.targetInst = targetInst;
      return instance;
    }
    return {
      topLevelType: topLevelType,
      nativeEvent: nativeEvent,
      targetInst: targetInst,
      ancestors: []
    };
  }
  
  function releaseTopLevelCallbackBookKeeping(instance) {
    instance.topLevelType = null;
    instance.nativeEvent = null;
    instance.targetInst = null;
    instance.ancestors.length = 0;
    if (callbackBookkeepingPool.length < CALLBACK_BOOKKEEPING_POOL_SIZE) {
      callbackBookkeepingPool.push(instance);
    }
  }
  
  function handleTopLevelImpl(bookKeeping) {
    var targetInst = bookKeeping.targetInst;
  
    // Loop through the hierarchy, in case there's any nested components.
    // It's important that we build the array of ancestors before calling any
    // event handlers, because event handlers can modify the DOM, leading to
    // inconsistencies with ReactMount's node cache. See #1105.
    var ancestor = targetInst;
    do {
      if (!ancestor) {
        bookKeeping.ancestors.push(ancestor);
        break;
      }
      var root = findRootContainerNode(ancestor);
      if (!root) {
        break;
      }
      bookKeeping.ancestors.push(ancestor);
      ancestor = getClosestInstanceFromNode(root);
    } while (ancestor);
  
    for (var i = 0; i < bookKeeping.ancestors.length; i++) {
      targetInst = bookKeeping.ancestors[i];
      _handleTopLevel(bookKeeping.topLevelType, targetInst, bookKeeping.nativeEvent, getEventTarget(bookKeeping.nativeEvent));
    }
  }
  
  // TODO: can we stop exporting these?
  var _enabled = true;
  var _handleTopLevel = void 0;
  
  function setHandleTopLevel(handleTopLevel) {
    _handleTopLevel = handleTopLevel;
  }
  
  function setEnabled(enabled) {
    _enabled = !!enabled;
  }
  
  function isEnabled() {
    return _enabled;
  }
  
  /**
   * Traps top-level events by using event bubbling.
   *
   * @param {string} topLevelType Record from `BrowserEventConstants`.
   * @param {string} handlerBaseName Event name (e.g. "click").
   * @param {object} element Element on which to attach listener.
   * @return {?object} An object with a remove function which will forcefully
   *                  remove the listener.
   * @internal
   */
  function trapBubbledEvent(topLevelType, handlerBaseName, element) {
    if (!element) {
      return null;
    }
    return EventListener.listen(element, handlerBaseName, dispatchEvent.bind(null, topLevelType));
  }
  
  /**
   * Traps a top-level event by using event capturing.
   *
   * @param {string} topLevelType Record from `BrowserEventConstants`.
   * @param {string} handlerBaseName Event name (e.g. "click").
   * @param {object} element Element on which to attach listener.
   * @return {?object} An object with a remove function which will forcefully
   *                  remove the listener.
   * @internal
   */
  function trapCapturedEvent(topLevelType, handlerBaseName, element) {
    if (!element) {
      return null;
    }
    return EventListener.capture(element, handlerBaseName, dispatchEvent.bind(null, topLevelType));
  }
  
  function dispatchEvent(topLevelType, nativeEvent) {
    if (!_enabled) {
      return;
    }
  
    var nativeEventTarget = getEventTarget(nativeEvent);
    var targetInst = getClosestInstanceFromNode(nativeEventTarget);
    if (targetInst !== null && typeof targetInst.tag === 'number' && !isFiberMounted(targetInst)) {
      // If we get an event (ex: img onload) before committing that
      // component's mount, ignore it for now (that is, treat it as if it was an
      // event on a non-React tree). We might also consider queueing events and
      // dispatching them after the mount.
      targetInst = null;
    }
  
    var bookKeeping = getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst);
  
    try {
      // Event queue being processed in the same cycle allows
      // `preventDefault`.
      batchedUpdates(handleTopLevelImpl, bookKeeping);
    } finally {
      releaseTopLevelCallbackBookKeeping(bookKeeping);
    }
  }
  
  var ReactDOMEventListener = Object.freeze({
    get _enabled () { return _enabled; },
    get _handleTopLevel () { return _handleTopLevel; },
    setHandleTopLevel: setHandleTopLevel,
    setEnabled: setEnabled,
    isEnabled: isEnabled,
    trapBubbledEvent: trapBubbledEvent,
    trapCapturedEvent: trapCapturedEvent,
    dispatchEvent: dispatchEvent
  });
  
  /**
   * Generate a mapping of standard vendor prefixes using the defined style property and event name.
   *
   * @param {string} styleProp
   * @param {string} eventName
   * @returns {object}
   */
  function makePrefixMap(styleProp, eventName) {
    var prefixes = {};
  
    prefixes[styleProp.toLowerCase()] = eventName.toLowerCase();
    prefixes['Webkit' + styleProp] = 'webkit' + eventName;
    prefixes['Moz' + styleProp] = 'moz' + eventName;
    prefixes['ms' + styleProp] = 'MS' + eventName;
    prefixes['O' + styleProp] = 'o' + eventName.toLowerCase();
  
    return prefixes;
  }
  
  /**
   * A list of event names to a configurable list of vendor prefixes.
   */
  var vendorPrefixes = {
    animationend: makePrefixMap('Animation', 'AnimationEnd'),
    animationiteration: makePrefixMap('Animation', 'AnimationIteration'),
    animationstart: makePrefixMap('Animation', 'AnimationStart'),
    transitionend: makePrefixMap('Transition', 'TransitionEnd')
  };
  
  /**
   * Event names that have already been detected and prefixed (if applicable).
   */
  var prefixedEventNames = {};
  
  /**
   * Element to check for prefixes on.
   */
  var style = {};
  
  /**
   * Bootstrap if a DOM exists.
   */
  if (ExecutionEnvironment.canUseDOM) {
    style = document.createElement('div').style;
  
    // On some platforms, in particular some releases of Android 4.x,
    // the un-prefixed "animation" and "transition" properties are defined on the
    // style object but the events that fire will still be prefixed, so we need
    // to check if the un-prefixed events are usable, and if not remove them from the map.
    if (!('AnimationEvent' in window)) {
      delete vendorPrefixes.animationend.animation;
      delete vendorPrefixes.animationiteration.animation;
      delete vendorPrefixes.animationstart.animation;
    }
  
    // Same as above
    if (!('TransitionEvent' in window)) {
      delete vendorPrefixes.transitionend.transition;
    }
  }
  
  /**
   * Attempts to determine the correct vendor prefixed event name.
   *
   * @param {string} eventName
   * @returns {string}
   */
  function getVendorPrefixedEventName(eventName) {
    if (prefixedEventNames[eventName]) {
      return prefixedEventNames[eventName];
    } else if (!vendorPrefixes[eventName]) {
      return eventName;
    }
  
    var prefixMap = vendorPrefixes[eventName];
  
    for (var styleProp in prefixMap) {
      if (prefixMap.hasOwnProperty(styleProp) && styleProp in style) {
        return prefixedEventNames[eventName] = prefixMap[styleProp];
      }
    }
  
    return '';
  }
  
  /**
   * Types of raw signals from the browser caught at the top level.
   *
   * For events like 'submit' which don't consistently bubble (which we
   * trap at a lower node than `document`), binding at `document` would
   * cause duplicate events so we don't include them here.
   */
  var topLevelTypes$1 = {
    topAbort: 'abort',
    topAnimationEnd: getVendorPrefixedEventName('animationend') || 'animationend',
    topAnimationIteration: getVendorPrefixedEventName('animationiteration') || 'animationiteration',
    topAnimationStart: getVendorPrefixedEventName('animationstart') || 'animationstart',
    topBlur: 'blur',
    topCancel: 'cancel',
    topCanPlay: 'canplay',
    topCanPlayThrough: 'canplaythrough',
    topChange: 'change',
    topClick: 'click',
    topClose: 'close',
    topCompositionEnd: 'compositionend',
    topCompositionStart: 'compositionstart',
    topCompositionUpdate: 'compositionupdate',
    topContextMenu: 'contextmenu',
    topCopy: 'copy',
    topCut: 'cut',
    topDoubleClick: 'dblclick',
    topDrag: 'drag',
    topDragEnd: 'dragend',
    topDragEnter: 'dragenter',
    topDragExit: 'dragexit',
    topDragLeave: 'dragleave',
    topDragOver: 'dragover',
    topDragStart: 'dragstart',
    topDrop: 'drop',
    topDurationChange: 'durationchange',
    topEmptied: 'emptied',
    topEncrypted: 'encrypted',
    topEnded: 'ended',
    topError: 'error',
    topFocus: 'focus',
    topInput: 'input',
    topKeyDown: 'keydown',
    topKeyPress: 'keypress',
    topKeyUp: 'keyup',
    topLoadedData: 'loadeddata',
    topLoad: 'load',
    topLoadedMetadata: 'loadedmetadata',
    topLoadStart: 'loadstart',
    topMouseDown: 'mousedown',
    topMouseMove: 'mousemove',
    topMouseOut: 'mouseout',
    topMouseOver: 'mouseover',
    topMouseUp: 'mouseup',
    topPaste: 'paste',
    topPause: 'pause',
    topPlay: 'play',
    topPlaying: 'playing',
    topProgress: 'progress',
    topRateChange: 'ratechange',
    topScroll: 'scroll',
    topSeeked: 'seeked',
    topSeeking: 'seeking',
    topSelectionChange: 'selectionchange',
    topStalled: 'stalled',
    topSuspend: 'suspend',
    topTextInput: 'textInput',
    topTimeUpdate: 'timeupdate',
    topToggle: 'toggle',
    topTouchCancel: 'touchcancel',
    topTouchEnd: 'touchend',
    topTouchMove: 'touchmove',
    topTouchStart: 'touchstart',
    topTransitionEnd: getVendorPrefixedEventName('transitionend') || 'transitionend',
    topVolumeChange: 'volumechange',
    topWaiting: 'waiting',
    topWheel: 'wheel'
  };
  
  var BrowserEventConstants = {
    topLevelTypes: topLevelTypes$1
  };
  
  function runEventQueueInBatch(events) {
    enqueueEvents(events);
    processEventQueue(false);
  }
  
  /**
   * Streams a fired top-level event to `EventPluginHub` where plugins have the
   * opportunity to create `ReactEvent`s to be dispatched.
   */
  function handleTopLevel(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
    var events = extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
    runEventQueueInBatch(events);
  }
  
  var topLevelTypes = BrowserEventConstants.topLevelTypes;
  
  /**
   * Summary of `ReactBrowserEventEmitter` event handling:
   *
   *  - Top-level delegation is used to trap most native browser events. This
   *    may only occur in the main thread and is the responsibility of
   *    ReactDOMEventListener, which is injected and can therefore support
   *    pluggable event sources. This is the only work that occurs in the main
   *    thread.
   *
   *  - We normalize and de-duplicate events to account for browser quirks. This
   *    may be done in the worker thread.
   *
   *  - Forward these native events (with the associated top-level type used to
   *    trap it) to `EventPluginHub`, which in turn will ask plugins if they want
   *    to extract any synthetic events.
   *
   *  - The `EventPluginHub` will then process each event by annotating them with
   *    "dispatches", a sequence of listeners and IDs that care about that event.
   *
   *  - The `EventPluginHub` then dispatches the events.
   *
   * Overview of React and the event system:
   *
   * +------------+    .
   * |    DOM     |    .
   * +------------+    .
   *       |           .
   *       v           .
   * +------------+    .
   * | ReactEvent |    .
   * |  Listener  |    .
   * +------------+    .                         +-----------+
   *       |           .               +--------+|SimpleEvent|
   *       |           .               |         |Plugin     |
   * +-----|------+    .               v         +-----------+
   * |     |      |    .    +--------------+                    +------------+
   * |     +-----------.--->|EventPluginHub|                    |    Event   |
   * |            |    .    |              |     +-----------+  | Propagators|
   * | ReactEvent |    .    |              |     |TapEvent   |  |------------|
   * |  Emitter   |    .    |              |<---+|Plugin     |  |other plugin|
   * |            |    .    |              |     +-----------+  |  utilities |
   * |     +-----------.--->|              |                    +------------+
   * |     |      |    .    +--------------+
   * +-----|------+    .                ^        +-----------+
   *       |           .                |        |Enter/Leave|
   *       +           .                +-------+|Plugin     |
   * +-------------+   .                         +-----------+
   * | application |   .
   * |-------------|   .
   * |             |   .
   * |             |   .
   * +-------------+   .
   *                   .
   *    React Core     .  General Purpose Event Plugin System
   */
  
  var alreadyListeningTo = {};
  var reactTopListenersCounter = 0;
  
  /**
   * To ensure no conflicts with other potential React instances on the page
   */
  var topListenersIDKey = '_reactListenersID' + ('' + Math.random()).slice(2);
  
  function getListeningForDocument(mountAt) {
    // In IE8, `mountAt` is a host object and doesn't have `hasOwnProperty`
    // directly.
    if (!Object.prototype.hasOwnProperty.call(mountAt, topListenersIDKey)) {
      mountAt[topListenersIDKey] = reactTopListenersCounter++;
      alreadyListeningTo[mountAt[topListenersIDKey]] = {};
    }
    return alreadyListeningTo[mountAt[topListenersIDKey]];
  }
  
  /**
   * We listen for bubbled touch events on the document object.
   *
   * Firefox v8.01 (and possibly others) exhibited strange behavior when
   * mounting `onmousemove` events at some node that was not the document
   * element. The symptoms were that if your mouse is not moving over something
   * contained within that mount point (for example on the background) the
   * top-level listeners for `onmousemove` won't be called. However, if you
   * register the `mousemove` on the document object, then it will of course
   * catch all `mousemove`s. This along with iOS quirks, justifies restricting
   * top-level listeners to the document object only, at least for these
   * movement types of events and possibly all events.
   *
   * @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
   *
   * Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
   * they bubble to document.
   *
   * @param {string} registrationName Name of listener (e.g. `onClick`).
   * @param {object} contentDocumentHandle Document which owns the container
   */
  function listenTo(registrationName, contentDocumentHandle) {
    var mountAt = contentDocumentHandle;
    var isListening = getListeningForDocument(mountAt);
    var dependencies = registrationNameDependencies[registrationName];
  
    for (var i = 0; i < dependencies.length; i++) {
      var dependency = dependencies[i];
      if (!(isListening.hasOwnProperty(dependency) && isListening[dependency])) {
        if (dependency === 'topScroll') {
          trapCapturedEvent('topScroll', 'scroll', mountAt);
        } else if (dependency === 'topFocus' || dependency === 'topBlur') {
          trapCapturedEvent('topFocus', 'focus', mountAt);
          trapCapturedEvent('topBlur', 'blur', mountAt);
  
          // to make sure blur and focus event listeners are only attached once
          isListening.topBlur = true;
          isListening.topFocus = true;
        } else if (dependency === 'topCancel') {
          if (isEventSupported('cancel', true)) {
            trapCapturedEvent('topCancel', 'cancel', mountAt);
          }
          isListening.topCancel = true;
        } else if (dependency === 'topClose') {
          if (isEventSupported('close', true)) {
            trapCapturedEvent('topClose', 'close', mountAt);
          }
          isListening.topClose = true;
        } else if (topLevelTypes.hasOwnProperty(dependency)) {
          trapBubbledEvent(dependency, topLevelTypes[dependency], mountAt);
        }
  
        isListening[dependency] = true;
      }
    }
  }
  
  function isListeningToAllDependencies(registrationName, mountAt) {
    var isListening = getListeningForDocument(mountAt);
    var dependencies = registrationNameDependencies[registrationName];
    for (var i = 0; i < dependencies.length; i++) {
      var dependency = dependencies[i];
      if (!(isListening.hasOwnProperty(dependency) && isListening[dependency])) {
        return false;
      }
    }
    return true;
  }
  
  /**
   * Given any node return the first leaf node without children.
   *
   * @param {DOMElement|DOMTextNode} node
   * @return {DOMElement|DOMTextNode}
   */
  function getLeafNode(node) {
    while (node && node.firstChild) {
      node = node.firstChild;
    }
    return node;
  }
  
  /**
   * Get the next sibling within a container. This will walk up the
   * DOM if a node's siblings have been exhausted.
   *
   * @param {DOMElement|DOMTextNode} node
   * @return {?DOMElement|DOMTextNode}
   */
  function getSiblingNode(node) {
    while (node) {
      if (node.nextSibling) {
        return node.nextSibling;
      }
      node = node.parentNode;
    }
  }
  
  /**
   * Get object describing the nodes which contain characters at offset.
   *
   * @param {DOMElement|DOMTextNode} root
   * @param {number} offset
   * @return {?object}
   */
  function getNodeForCharacterOffset(root, offset) {
    var node = getLeafNode(root);
    var nodeStart = 0;
    var nodeEnd = 0;
  
    while (node) {
      if (node.nodeType === TEXT_NODE) {
        nodeEnd = nodeStart + node.textContent.length;
  
        if (nodeStart <= offset && nodeEnd >= offset) {
          return {
            node: node,
            offset: offset - nodeStart
          };
        }
  
        nodeStart = nodeEnd;
      }
  
      node = getLeafNode(getSiblingNode(node));
    }
  }
  
  /**
   * @param {DOMElement} outerNode
   * @return {?object}
   */
  function getOffsets(outerNode) {
    var selection = window.getSelection && window.getSelection();
  
    if (!selection || selection.rangeCount === 0) {
      return null;
    }
  
    var anchorNode = selection.anchorNode,
        anchorOffset = selection.anchorOffset,
        focusNode$$1 = selection.focusNode,
        focusOffset = selection.focusOffset;
  
    // In Firefox, anchorNode and focusNode can be "anonymous divs", e.g. the
    // up/down buttons on an <input type="number">. Anonymous divs do not seem to
    // expose properties, triggering a "Permission denied error" if any of its
    // properties are accessed. The only seemingly possible way to avoid erroring
    // is to access a property that typically works for non-anonymous divs and
    // catch any error that may otherwise arise. See
    // https://bugzilla.mozilla.org/show_bug.cgi?id=208427
  
    try {
      /* eslint-disable no-unused-expressions */
      anchorNode.nodeType;
      focusNode$$1.nodeType;
      /* eslint-enable no-unused-expressions */
    } catch (e) {
      return null;
    }
  
    return getModernOffsetsFromPoints(outerNode, anchorNode, anchorOffset, focusNode$$1, focusOffset);
  }
  
  /**
   * Returns {start, end} where `start` is the character/codepoint index of
   * (anchorNode, anchorOffset) within the textContent of `outerNode`, and
   * `end` is the index of (focusNode, focusOffset).
   *
   * Returns null if you pass in garbage input but we should probably just crash.
   *
   * Exported only for testing.
   */
  function getModernOffsetsFromPoints(outerNode, anchorNode, anchorOffset, focusNode$$1, focusOffset) {
    var length = 0;
    var start = -1;
    var end = -1;
    var indexWithinAnchor = 0;
    var indexWithinFocus = 0;
    var node = outerNode;
    var parentNode = null;
  
    outer: while (true) {
      var next = null;
  
      while (true) {
        if (node === anchorNode && (anchorOffset === 0 || node.nodeType === TEXT_NODE)) {
          start = length + anchorOffset;
        }
        if (node === focusNode$$1 && (focusOffset === 0 || node.nodeType === TEXT_NODE)) {
          end = length + focusOffset;
        }
  
        if (node.nodeType === TEXT_NODE) {
          length += node.nodeValue.length;
        }
  
        if ((next = node.firstChild) === null) {
          break;
        }
        // Moving from `node` to its first child `next`.
        parentNode = node;
        node = next;
      }
  
      while (true) {
        if (node === outerNode) {
          // If `outerNode` has children, this is always the second time visiting
          // it. If it has no children, this is still the first loop, and the only
          // valid selection is anchorNode and focusNode both equal to this node
          // and both offsets 0, in which case we will have handled above.
          break outer;
        }
        if (parentNode === anchorNode && ++indexWithinAnchor === anchorOffset) {
          start = length;
        }
        if (parentNode === focusNode$$1 && ++indexWithinFocus === focusOffset) {
          end = length;
        }
        if ((next = node.nextSibling) !== null) {
          break;
        }
        node = parentNode;
        parentNode = node.parentNode;
      }
  
      // Moving from `node` to its next sibling `next`.
      node = next;
    }
  
    if (start === -1 || end === -1) {
      // This should never happen. (Would happen if the anchor/focus nodes aren't
      // actually inside the passed-in node.)
      return null;
    }
  
    return {
      start: start,
      end: end
    };
  }
  
  /**
   * In modern non-IE browsers, we can support both forward and backward
   * selections.
   *
   * Note: IE10+ supports the Selection object, but it does not support
   * the `extend` method, which means that even in modern IE, it's not possible
   * to programmatically create a backward selection. Thus, for all IE
   * versions, we use the old IE API to create our selections.
   *
   * @param {DOMElement|DOMTextNode} node
   * @param {object} offsets
   */
  function setOffsets(node, offsets) {
    if (!window.getSelection) {
      return;
    }
  
    var selection = window.getSelection();
    var length = node[getTextContentAccessor()].length;
    var start = Math.min(offsets.start, length);
    var end = offsets.end === undefined ? start : Math.min(offsets.end, length);
  
    // IE 11 uses modern selection, but doesn't support the extend method.
    // Flip backward selections, so we can set with a single range.
    if (!selection.extend && start > end) {
      var temp = end;
      end = start;
      start = temp;
    }
  
    var startMarker = getNodeForCharacterOffset(node, start);
    var endMarker = getNodeForCharacterOffset(node, end);
  
    if (startMarker && endMarker) {
      if (selection.rangeCount === 1 && selection.anchorNode === startMarker.node && selection.anchorOffset === startMarker.offset && selection.focusNode === endMarker.node && selection.focusOffset === endMarker.offset) {
        return;
      }
      var range = document.createRange();
      range.setStart(startMarker.node, startMarker.offset);
      selection.removeAllRanges();
  
      if (start > end) {
        selection.addRange(range);
        selection.extend(endMarker.node, endMarker.offset);
      } else {
        range.setEnd(endMarker.node, endMarker.offset);
        selection.addRange(range);
      }
    }
  }
  
  function isInDocument(node) {
    return containsNode(document.documentElement, node);
  }
  
  /**
   * @ReactInputSelection: React input selection module. Based on Selection.js,
   * but modified to be suitable for react and has a couple of bug fixes (doesn't
   * assume buttons have range selections allowed).
   * Input selection module for React.
   */
  
  function hasSelectionCapabilities(elem) {
    var nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
    return nodeName && (nodeName === 'input' && elem.type === 'text' || nodeName === 'textarea' || elem.contentEditable === 'true');
  }
  
  function getSelectionInformation() {
    var focusedElem = getActiveElement();
    return {
      focusedElem: focusedElem,
      selectionRange: hasSelectionCapabilities(focusedElem) ? getSelection$1(focusedElem) : null
    };
  }
  
  /**
   * @restoreSelection: If any selection information was potentially lost,
   * restore it. This is useful when performing operations that could remove dom
   * nodes and place them back in, resulting in focus being lost.
   */
  function restoreSelection(priorSelectionInformation) {
    var curFocusedElem = getActiveElement();
    var priorFocusedElem = priorSelectionInformation.focusedElem;
    var priorSelectionRange = priorSelectionInformation.selectionRange;
    if (curFocusedElem !== priorFocusedElem && isInDocument(priorFocusedElem)) {
      if (hasSelectionCapabilities(priorFocusedElem)) {
        setSelection(priorFocusedElem, priorSelectionRange);
      }
  
      // Focusing a node can change the scroll position, which is undesirable
      var ancestors = [];
      var ancestor = priorFocusedElem;
      while (ancestor = ancestor.parentNode) {
        if (ancestor.nodeType === ELEMENT_NODE) {
          ancestors.push({
            element: ancestor,
            left: ancestor.scrollLeft,
            top: ancestor.scrollTop
          });
        }
      }
  
      focusNode(priorFocusedElem);
  
      for (var i = 0; i < ancestors.length; i++) {
        var info = ancestors[i];
        info.element.scrollLeft = info.left;
        info.element.scrollTop = info.top;
      }
    }
  }
  
  /**
   * @getSelection: Gets the selection bounds of a focused textarea, input or
   * contentEditable node.
   * -@input: Look up selection bounds of this input
   * -@return {start: selectionStart, end: selectionEnd}
   */
  function getSelection$1(input) {
    var selection = void 0;
  
    if ('selectionStart' in input) {
      // Modern browser with input or textarea.
      selection = {
        start: input.selectionStart,
        end: input.selectionEnd
      };
    } else {
      // Content editable or old IE textarea.
      selection = getOffsets(input);
    }
  
    return selection || { start: 0, end: 0 };
  }
  
  /**
   * @setSelection: Sets the selection bounds of a textarea or input and focuses
   * the input.
   * -@input     Set selection bounds of this input or textarea
   * -@offsets   Object of same form that is returned from get*
   */
  function setSelection(input, offsets) {
    var start = offsets.start,
        end = offsets.end;
  
    if (end === undefined) {
      end = start;
    }
  
    if ('selectionStart' in input) {
      input.selectionStart = start;
      input.selectionEnd = Math.min(end, input.value.length);
    } else {
      setOffsets(input, offsets);
    }
  }
  
  var skipSelectionChangeEvent = ExecutionEnvironment.canUseDOM && 'documentMode' in document && document.documentMode <= 11;
  
  var eventTypes$3 = {
    select: {
      phasedRegistrationNames: {
        bubbled: 'onSelect',
        captured: 'onSelectCapture'
      },
      dependencies: ['topBlur', 'topContextMenu', 'topFocus', 'topKeyDown', 'topKeyUp', 'topMouseDown', 'topMouseUp', 'topSelectionChange']
    }
  };
  
  var activeElement$1 = null;
  var activeElementInst$1 = null;
  var lastSelection = null;
  var mouseDown = false;
  
  /**
   * Get an object which is a unique representation of the current selection.
   *
   * The return value will not be consistent across nodes or browsers, but
   * two identical selections on the same node will return identical objects.
   *
   * @param {DOMElement} node
   * @return {object}
   */
  function getSelection(node) {
    if ('selectionStart' in node && hasSelectionCapabilities(node)) {
      return {
        start: node.selectionStart,
        end: node.selectionEnd
      };
    } else if (window.getSelection) {
      var selection = window.getSelection();
      return {
        anchorNode: selection.anchorNode,
        anchorOffset: selection.anchorOffset,
        focusNode: selection.focusNode,
        focusOffset: selection.focusOffset
      };
    }
  }
  
  /**
   * Poll selection to see whether it's changed.
   *
   * @param {object} nativeEvent
   * @return {?SyntheticEvent}
   */
  function constructSelectEvent(nativeEvent, nativeEventTarget) {
    // Ensure we have the right element, and that the user is not dragging a
    // selection (this matches native `select` event behavior). In HTML5, select
    // fires only on input and textarea thus if there's no focused element we
    // won't dispatch.
    if (mouseDown || activeElement$1 == null || activeElement$1 !== getActiveElement()) {
      return null;
    }
  
    // Only fire when selection has actually changed.
    var currentSelection = getSelection(activeElement$1);
    if (!lastSelection || !shallowEqual(lastSelection, currentSelection)) {
      lastSelection = currentSelection;
  
      var syntheticEvent = SyntheticEvent$1.getPooled(eventTypes$3.select, activeElementInst$1, nativeEvent, nativeEventTarget);
  
      syntheticEvent.type = 'select';
      syntheticEvent.target = activeElement$1;
  
      accumulateTwoPhaseDispatches(syntheticEvent);
  
      return syntheticEvent;
    }
  
    return null;
  }
  
  /**
   * This plugin creates an `onSelect` event that normalizes select events
   * across form elements.
   *
   * Supported elements are:
   * - input (see `isTextInputElement`)
   * - textarea
   * - contentEditable
   *
   * This differs from native browser implementations in the following ways:
   * - Fires on contentEditable fields as well as inputs.
   * - Fires for collapsed selection.
   * - Fires after user input.
   */
  var SelectEventPlugin = {
    eventTypes: eventTypes$3,
  
    extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
      var doc = nativeEventTarget.window === nativeEventTarget ? nativeEventTarget.document : nativeEventTarget.nodeType === DOCUMENT_NODE ? nativeEventTarget : nativeEventTarget.ownerDocument;
      // Track whether all listeners exists for this plugin. If none exist, we do
      // not extract events. See #3639.
      if (!doc || !isListeningToAllDependencies('onSelect', doc)) {
        return null;
      }
  
      var targetNode = targetInst ? getNodeFromInstance$1(targetInst) : window;
  
      switch (topLevelType) {
        // Track the input node that has focus.
        case 'topFocus':
          if (isTextInputElement(targetNode) || targetNode.contentEditable === 'true') {
            activeElement$1 = targetNode;
            activeElementInst$1 = targetInst;
            lastSelection = null;
          }
          break;
        case 'topBlur':
          activeElement$1 = null;
          activeElementInst$1 = null;
          lastSelection = null;
          break;
        // Don't fire the event while the user is dragging. This matches the
        // semantics of the native select event.
        case 'topMouseDown':
          mouseDown = true;
          break;
        case 'topContextMenu':
        case 'topMouseUp':
          mouseDown = false;
          return constructSelectEvent(nativeEvent, nativeEventTarget);
        // Chrome and IE fire non-standard event when selection is changed (and
        // sometimes when it hasn't). IE's event fires out of order with respect
        // to key and input events on deletion, so we discard it.
        //
        // Firefox doesn't support selectionchange, so check selection status
        // after each key entry. The selection changes after keydown and before
        // keyup, but we check on keydown as well in the case of holding down a
        // key, when multiple keydown events are fired but only one keyup is.
        // This is also our approach for IE handling, for the reason above.
        case 'topSelectionChange':
          if (skipSelectionChangeEvent) {
            break;
          }
        // falls through
        case 'topKeyDown':
        case 'topKeyUp':
          return constructSelectEvent(nativeEvent, nativeEventTarget);
      }
  
      return null;
    }
  };
  
  /**
   * @interface Event
   * @see http://www.w3.org/TR/css3-animations/#AnimationEvent-interface
   * @see https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent
   */
  var AnimationEventInterface = {
    animationName: null,
    elapsedTime: null,
    pseudoElement: null
  };
  
  /**
   * @param {object} dispatchConfig Configuration used to dispatch this event.
   * @param {string} dispatchMarker Marker identifying the event target.
   * @param {object} nativeEvent Native browser event.
   * @extends {SyntheticEvent}
   */
  function SyntheticAnimationEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
    return SyntheticEvent$1.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
  }
  
  SyntheticEvent$1.augmentClass(SyntheticAnimationEvent, AnimationEventInterface);
  
  /**
   * @interface Event
   * @see http://www.w3.org/TR/clipboard-apis/
   */
  var ClipboardEventInterface = {
    clipboardData: function (event) {
      return 'clipboardData' in event ? event.clipboardData : window.clipboardData;
    }
  };
  
  /**
   * @param {object} dispatchConfig Configuration used to dispatch this event.
   * @param {string} dispatchMarker Marker identifying the event target.
   * @param {object} nativeEvent Native browser event.
   * @extends {SyntheticEvent}
   */
  function SyntheticClipboardEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
    return SyntheticEvent$1.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
  }
  
  SyntheticEvent$1.augmentClass(SyntheticClipboardEvent, ClipboardEventInterface);
  
  /**
   * @interface FocusEvent
   * @see http://www.w3.org/TR/DOM-Level-3-Events/
   */
  var FocusEventInterface = {
    relatedTarget: null
  };
  
  /**
   * @param {object} dispatchConfig Configuration used to dispatch this event.
   * @param {string} dispatchMarker Marker identifying the event target.
   * @param {object} nativeEvent Native browser event.
   * @extends {SyntheticUIEvent}
   */
  function SyntheticFocusEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
    return SyntheticUIEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
  }
  
  SyntheticUIEvent.augmentClass(SyntheticFocusEvent, FocusEventInterface);
  
  /**
   * `charCode` represents the actual "character code" and is safe to use with
   * `String.fromCharCode`. As such, only keys that correspond to printable
   * characters produce a valid `charCode`, the only exception to this is Enter.
   * The Tab-key is considered non-printable and does not have a `charCode`,
   * presumably because it does not produce a tab-character in browsers.
   *
   * @param {object} nativeEvent Native browser event.
   * @return {number} Normalized `charCode` property.
   */
  function getEventCharCode(nativeEvent) {
    var charCode;
    var keyCode = nativeEvent.keyCode;
  
    if ('charCode' in nativeEvent) {
      charCode = nativeEvent.charCode;
  
      // FF does not set `charCode` for the Enter-key, check against `keyCode`.
      if (charCode === 0 && keyCode === 13) {
        charCode = 13;
      }
    } else {
      // IE8 does not implement `charCode`, but `keyCode` has the correct value.
      charCode = keyCode;
    }
  
    // Some non-printable keys are reported in `charCode`/`keyCode`, discard them.
    // Must not discard the (non-)printable Enter-key.
    if (charCode >= 32 || charCode === 13) {
      return charCode;
    }
  
    return 0;
  }
  
  /**
   * Normalization of deprecated HTML5 `key` values
   * @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names
   */
  var normalizeKey = {
    Esc: 'Escape',
    Spacebar: ' ',
    Left: 'ArrowLeft',
    Up: 'ArrowUp',
    Right: 'ArrowRight',
    Down: 'ArrowDown',
    Del: 'Delete',
    Win: 'OS',
    Menu: 'ContextMenu',
    Apps: 'ContextMenu',
    Scroll: 'ScrollLock',
    MozPrintableKey: 'Unidentified'
  };
  
  /**
   * Translation from legacy `keyCode` to HTML5 `key`
   * Only special keys supported, all others depend on keyboard layout or browser
   * @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names
   */
  var translateToKey = {
    '8': 'Backspace',
    '9': 'Tab',
    '12': 'Clear',
    '13': 'Enter',
    '16': 'Shift',
    '17': 'Control',
    '18': 'Alt',
    '19': 'Pause',
    '20': 'CapsLock',
    '27': 'Escape',
    '32': ' ',
    '33': 'PageUp',
    '34': 'PageDown',
    '35': 'End',
    '36': 'Home',
    '37': 'ArrowLeft',
    '38': 'ArrowUp',
    '39': 'ArrowRight',
    '40': 'ArrowDown',
    '45': 'Insert',
    '46': 'Delete',
    '112': 'F1',
    '113': 'F2',
    '114': 'F3',
    '115': 'F4',
    '116': 'F5',
    '117': 'F6',
    '118': 'F7',
    '119': 'F8',
    '120': 'F9',
    '121': 'F10',
    '122': 'F11',
    '123': 'F12',
    '144': 'NumLock',
    '145': 'ScrollLock',
    '224': 'Meta'
  };
  
  /**
   * @param {object} nativeEvent Native browser event.
   * @return {string} Normalized `key` property.
   */
  function getEventKey(nativeEvent) {
    if (nativeEvent.key) {
      // Normalize inconsistent values reported by browsers due to
      // implementations of a working draft specification.
  
      // FireFox implements `key` but returns `MozPrintableKey` for all
      // printable characters (normalized to `Unidentified`), ignore it.
      var key = normalizeKey[nativeEvent.key] || nativeEvent.key;
      if (key !== 'Unidentified') {
        return key;
      }
    }
  
    // Browser does not implement `key`, polyfill as much of it as we can.
    if (nativeEvent.type === 'keypress') {
      var charCode = getEventCharCode(nativeEvent);
  
      // The enter-key is technically both printable and non-printable and can
      // thus be captured by `keypress`, no other non-printable key should.
      return charCode === 13 ? 'Enter' : String.fromCharCode(charCode);
    }
    if (nativeEvent.type === 'keydown' || nativeEvent.type === 'keyup') {
      // While user keyboard layout determines the actual meaning of each
      // `keyCode` value, almost all function keys have a universal value.
      return translateToKey[nativeEvent.keyCode] || 'Unidentified';
    }
    return '';
  }
  
  /**
   * @interface KeyboardEvent
   * @see http://www.w3.org/TR/DOM-Level-3-Events/
   */
  var KeyboardEventInterface = {
    key: getEventKey,
    location: null,
    ctrlKey: null,
    shiftKey: null,
    altKey: null,
    metaKey: null,
    repeat: null,
    locale: null,
    getModifierState: getEventModifierState,
    // Legacy Interface
    charCode: function (event) {
      // `charCode` is the result of a KeyPress event and represents the value of
      // the actual printable character.
  
      // KeyPress is deprecated, but its replacement is not yet final and not
      // implemented in any major browser. Only KeyPress has charCode.
      if (event.type === 'keypress') {
        return getEventCharCode(event);
      }
      return 0;
    },
    keyCode: function (event) {
      // `keyCode` is the result of a KeyDown/Up event and represents the value of
      // physical keyboard key.
  
      // The actual meaning of the value depends on the users' keyboard layout
      // which cannot be detected. Assuming that it is a US keyboard layout
      // provides a surprisingly accurate mapping for US and European users.
      // Due to this, it is left to the user to implement at this time.
      if (event.type === 'keydown' || event.type === 'keyup') {
        return event.keyCode;
      }
      return 0;
    },
    which: function (event) {
      // `which` is an alias for either `keyCode` or `charCode` depending on the
      // type of the event.
      if (event.type === 'keypress') {
        return getEventCharCode(event);
      }
      if (event.type === 'keydown' || event.type === 'keyup') {
        return event.keyCode;
      }
      return 0;
    }
  };
  
  /**
   * @param {object} dispatchConfig Configuration used to dispatch this event.
   * @param {string} dispatchMarker Marker identifying the event target.
   * @param {object} nativeEvent Native browser event.
   * @extends {SyntheticUIEvent}
   */
  function SyntheticKeyboardEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
    return SyntheticUIEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
  }
  
  SyntheticUIEvent.augmentClass(SyntheticKeyboardEvent, KeyboardEventInterface);
  
  /**
   * @interface DragEvent
   * @see http://www.w3.org/TR/DOM-Level-3-Events/
   */
  var DragEventInterface = {
    dataTransfer: null
  };
  
  /**
   * @param {object} dispatchConfig Configuration used to dispatch this event.
   * @param {string} dispatchMarker Marker identifying the event target.
   * @param {object} nativeEvent Native browser event.
   * @extends {SyntheticMouseEvent}
   */
  function SyntheticDragEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
    return SyntheticMouseEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
  }
  
  SyntheticMouseEvent.augmentClass(SyntheticDragEvent, DragEventInterface);
  
  /**
   * @interface TouchEvent
   * @see http://www.w3.org/TR/touch-events/
   */
  var TouchEventInterface = {
    touches: null,
    targetTouches: null,
    changedTouches: null,
    altKey: null,
    metaKey: null,
    ctrlKey: null,
    shiftKey: null,
    getModifierState: getEventModifierState
  };
  
  /**
   * @param {object} dispatchConfig Configuration used to dispatch this event.
   * @param {string} dispatchMarker Marker identifying the event target.
   * @param {object} nativeEvent Native browser event.
   * @extends {SyntheticUIEvent}
   */
  function SyntheticTouchEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
    return SyntheticUIEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
  }
  
  SyntheticUIEvent.augmentClass(SyntheticTouchEvent, TouchEventInterface);
  
  /**
   * @interface Event
   * @see http://www.w3.org/TR/2009/WD-css3-transitions-20090320/#transition-events-
   * @see https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent
   */
  var TransitionEventInterface = {
    propertyName: null,
    elapsedTime: null,
    pseudoElement: null
  };
  
  /**
   * @param {object} dispatchConfig Configuration used to dispatch this event.
   * @param {string} dispatchMarker Marker identifying the event target.
   * @param {object} nativeEvent Native browser event.
   * @extends {SyntheticEvent}
   */
  function SyntheticTransitionEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
    return SyntheticEvent$1.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
  }
  
  SyntheticEvent$1.augmentClass(SyntheticTransitionEvent, TransitionEventInterface);
  
  /**
   * @interface WheelEvent
   * @see http://www.w3.org/TR/DOM-Level-3-Events/
   */
  var WheelEventInterface = {
    deltaX: function (event) {
      return 'deltaX' in event ? event.deltaX : // Fallback to `wheelDeltaX` for Webkit and normalize (right is positive).
      'wheelDeltaX' in event ? -event.wheelDeltaX : 0;
    },
    deltaY: function (event) {
      return 'deltaY' in event ? event.deltaY : // Fallback to `wheelDeltaY` for Webkit and normalize (down is positive).
      'wheelDeltaY' in event ? -event.wheelDeltaY : // Fallback to `wheelDelta` for IE<9 and normalize (down is positive).
      'wheelDelta' in event ? -event.wheelDelta : 0;
    },
    deltaZ: null,
  
    // Browsers without "deltaMode" is reporting in raw wheel delta where one
    // notch on the scroll is always +/- 120, roughly equivalent to pixels.
    // A good approximation of DOM_DELTA_LINE (1) is 5% of viewport size or
    // ~40 pixels, for DOM_DELTA_SCREEN (2) it is 87.5% of viewport size.
    deltaMode: null
  };
  
  /**
   * @param {object} dispatchConfig Configuration used to dispatch this event.
   * @param {string} dispatchMarker Marker identifying the event target.
   * @param {object} nativeEvent Native browser event.
   * @extends {SyntheticMouseEvent}
   */
  function SyntheticWheelEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
    return SyntheticMouseEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
  }
  
  SyntheticMouseEvent.augmentClass(SyntheticWheelEvent, WheelEventInterface);
  
  /**
   * Turns
   * ['abort', ...]
   * into
   * eventTypes = {
   *   'abort': {
   *     phasedRegistrationNames: {
   *       bubbled: 'onAbort',
   *       captured: 'onAbortCapture',
   *     },
   *     dependencies: ['topAbort'],
   *   },
   *   ...
   * };
   * topLevelEventsToDispatchConfig = {
   *   'topAbort': { sameConfig }
   * };
   */
  var eventTypes$4 = {};
  var topLevelEventsToDispatchConfig = {};
  ['abort', 'animationEnd', 'animationIteration', 'animationStart', 'blur', 'cancel', 'canPlay', 'canPlayThrough', 'click', 'close', 'contextMenu', 'copy', 'cut', 'doubleClick', 'drag', 'dragEnd', 'dragEnter', 'dragExit', 'dragLeave', 'dragOver', 'dragStart', 'drop', 'durationChange', 'emptied', 'encrypted', 'ended', 'error', 'focus', 'input', 'invalid', 'keyDown', 'keyPress', 'keyUp', 'load', 'loadedData', 'loadedMetadata', 'loadStart', 'mouseDown', 'mouseMove', 'mouseOut', 'mouseOver', 'mouseUp', 'paste', 'pause', 'play', 'playing', 'progress', 'rateChange', 'reset', 'scroll', 'seeked', 'seeking', 'stalled', 'submit', 'suspend', 'timeUpdate', 'toggle', 'touchCancel', 'touchEnd', 'touchMove', 'touchStart', 'transitionEnd', 'volumeChange', 'waiting', 'wheel'].forEach(function (event) {
    var capitalizedEvent = event[0].toUpperCase() + event.slice(1);
    var onEvent = 'on' + capitalizedEvent;
    var topEvent = 'top' + capitalizedEvent;
  
    var type = {
      phasedRegistrationNames: {
        bubbled: onEvent,
        captured: onEvent + 'Capture'
      },
      dependencies: [topEvent]
    };
    eventTypes$4[event] = type;
    topLevelEventsToDispatchConfig[topEvent] = type;
  });
  
  // Only used in DEV for exhaustiveness validation.
  var knownHTMLTopLevelTypes = ['topAbort', 'topCancel', 'topCanPlay', 'topCanPlayThrough', 'topClose', 'topDurationChange', 'topEmptied', 'topEncrypted', 'topEnded', 'topError', 'topInput', 'topInvalid', 'topLoad', 'topLoadedData', 'topLoadedMetadata', 'topLoadStart', 'topPause', 'topPlay', 'topPlaying', 'topProgress', 'topRateChange', 'topReset', 'topSeeked', 'topSeeking', 'topStalled', 'topSubmit', 'topSuspend', 'topTimeUpdate', 'topToggle', 'topVolumeChange', 'topWaiting'];
  
  var SimpleEventPlugin = {
    eventTypes: eventTypes$4,
  
    extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
      var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
      if (!dispatchConfig) {
        return null;
      }
      var EventConstructor;
      switch (topLevelType) {
        case 'topKeyPress':
          // Firefox creates a keypress event for function keys too. This removes
          // the unwanted keypress events. Enter is however both printable and
          // non-printable. One would expect Tab to be as well (but it isn't).
          if (getEventCharCode(nativeEvent) === 0) {
            return null;
          }
        /* falls through */
        case 'topKeyDown':
        case 'topKeyUp':
          EventConstructor = SyntheticKeyboardEvent;
          break;
        case 'topBlur':
        case 'topFocus':
          EventConstructor = SyntheticFocusEvent;
          break;
        case 'topClick':
          // Firefox creates a click event on right mouse clicks. This removes the
          // unwanted click events.
          if (nativeEvent.button === 2) {
            return null;
          }
        /* falls through */
        case 'topDoubleClick':
        case 'topMouseDown':
        case 'topMouseMove':
        case 'topMouseUp':
        // TODO: Disabled elements should not respond to mouse events
        /* falls through */
        case 'topMouseOut':
        case 'topMouseOver':
        case 'topContextMenu':
          EventConstructor = SyntheticMouseEvent;
          break;
        case 'topDrag':
        case 'topDragEnd':
        case 'topDragEnter':
        case 'topDragExit':
        case 'topDragLeave':
        case 'topDragOver':
        case 'topDragStart':
        case 'topDrop':
          EventConstructor = SyntheticDragEvent;
          break;
        case 'topTouchCancel':
        case 'topTouchEnd':
        case 'topTouchMove':
        case 'topTouchStart':
          EventConstructor = SyntheticTouchEvent;
          break;
        case 'topAnimationEnd':
        case 'topAnimationIteration':
        case 'topAnimationStart':
          EventConstructor = SyntheticAnimationEvent;
          break;
        case 'topTransitionEnd':
          EventConstructor = SyntheticTransitionEvent;
          break;
        case 'topScroll':
          EventConstructor = SyntheticUIEvent;
          break;
        case 'topWheel':
          EventConstructor = SyntheticWheelEvent;
          break;
        case 'topCopy':
        case 'topCut':
        case 'topPaste':
          EventConstructor = SyntheticClipboardEvent;
          break;
        default:
          {
            if (knownHTMLTopLevelTypes.indexOf(topLevelType) === -1) {
              warning(false, 'SimpleEventPlugin: Unhandled event type, `%s`. This warning ' + 'is likely caused by a bug in React. Please file an issue.', topLevelType);
            }
          }
          // HTML Events
          // @see http://www.w3.org/TR/html5/index.html#events-0
          EventConstructor = SyntheticEvent$1;
          break;
      }
      var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);
      accumulateTwoPhaseDispatches(event);
      return event;
    }
  };
  
  setHandleTopLevel(handleTopLevel);
  
  /**
   * Inject modules for resolving DOM hierarchy and plugin ordering.
   */
  injection$1.injectEventPluginOrder(DOMEventPluginOrder);
  injection$2.injectComponentTree(ReactDOMComponentTree);
  
  /**
   * Some important event plugins included by default (without having to require
   * them).
   */
  injection$1.injectEventPluginsByName({
    SimpleEventPlugin: SimpleEventPlugin,
    EnterLeaveEventPlugin: EnterLeaveEventPlugin,
    ChangeEventPlugin: ChangeEventPlugin,
    SelectEventPlugin: SelectEventPlugin,
    BeforeInputEventPlugin: BeforeInputEventPlugin
  });
  
  var enableAsyncSubtreeAPI = true;
  var enableAsyncSchedulingByDefaultInReactDOM = false;
  // Exports ReactDOM.createRoot
  var enableCreateRoot = false;
  var enableUserTimingAPI = true;
  
  // Mutating mode (React DOM, React ART, React Native):
  var enableMutatingReconciler = true;
  // Experimental noop mode (currently unused):
  var enableNoopReconciler = false;
  // Experimental persistent mode (CS):
  var enablePersistentReconciler = false;
  
  // Helps identify side effects in begin-phase lifecycle hooks and setState reducers:
  var debugRenderPhaseSideEffects = false;
  
  // Only used in www builds.
  
  var valueStack = [];
  
  {
    var fiberStack = [];
  }
  
  var index = -1;
  
  function createCursor(defaultValue) {
    return {
      current: defaultValue
    };
  }
  
  
  
  function pop(cursor, fiber) {
    if (index < 0) {
      {
        warning(false, 'Unexpected pop.');
      }
      return;
    }
  
    {
      if (fiber !== fiberStack[index]) {
        warning(false, 'Unexpected Fiber popped.');
      }
    }
  
    cursor.current = valueStack[index];
  
    valueStack[index] = null;
  
    {
      fiberStack[index] = null;
    }
  
    index--;
  }
  
  function push(cursor, value, fiber) {
    index++;
  
    valueStack[index] = cursor.current;
  
    {
      fiberStack[index] = fiber;
    }
  
    cursor.current = value;
  }
  
  function reset$1() {
    while (index > -1) {
      valueStack[index] = null;
  
      {
        fiberStack[index] = null;
      }
  
      index--;
    }
  }
  
  var describeComponentFrame = function (name, source, ownerName) {
    return '\n    in ' + (name || 'Unknown') + (source ? ' (at ' + source.fileName.replace(/^.*[\\\/]/, '') + ':' + source.lineNumber + ')' : ownerName ? ' (created by ' + ownerName + ')' : '');
  };
  
  function describeFiber(fiber) {
    switch (fiber.tag) {
      case IndeterminateComponent:
      case FunctionalComponent:
      case ClassComponent:
      case HostComponent:
        var owner = fiber._debugOwner;
        var source = fiber._debugSource;
        var name = getComponentName(fiber);
        var ownerName = null;
        if (owner) {
          ownerName = getComponentName(owner);
        }
        return describeComponentFrame(name, source, ownerName);
      default:
        return '';
    }
  }
  
  // This function can only be called with a work-in-progress fiber and
  // only during begin or complete phase. Do not call it under any other
  // circumstances.
  function getStackAddendumByWorkInProgressFiber(workInProgress) {
    var info = '';
    var node = workInProgress;
    do {
      info += describeFiber(node);
      // Otherwise this return pointer might point to the wrong tree:
      node = node['return'];
    } while (node);
    return info;
  }
  
  function getCurrentFiberOwnerName() {
    {
      var fiber = ReactDebugCurrentFiber.current;
      if (fiber === null) {
        return null;
      }
      var owner = fiber._debugOwner;
      if (owner !== null && typeof owner !== 'undefined') {
        return getComponentName(owner);
      }
    }
    return null;
  }
  
  function getCurrentFiberStackAddendum() {
    {
      var fiber = ReactDebugCurrentFiber.current;
      if (fiber === null) {
        return null;
      }
      // Safe because if current fiber exists, we are reconciling,
      // and it is guaranteed to be the work-in-progress version.
      return getStackAddendumByWorkInProgressFiber(fiber);
    }
    return null;
  }
  
  function resetCurrentFiber() {
    ReactDebugCurrentFrame.getCurrentStack = null;
    ReactDebugCurrentFiber.current = null;
    ReactDebugCurrentFiber.phase = null;
  }
  
  function setCurrentFiber(fiber) {
    ReactDebugCurrentFrame.getCurrentStack = getCurrentFiberStackAddendum;
    ReactDebugCurrentFiber.current = fiber;
    ReactDebugCurrentFiber.phase = null;
  }
  
  function setCurrentPhase(phase) {
    ReactDebugCurrentFiber.phase = phase;
  }
  
  var ReactDebugCurrentFiber = {
    current: null,
    phase: null,
    resetCurrentFiber: resetCurrentFiber,
    setCurrentFiber: setCurrentFiber,
    setCurrentPhase: setCurrentPhase,
    getCurrentFiberOwnerName: getCurrentFiberOwnerName,
    getCurrentFiberStackAddendum: getCurrentFiberStackAddendum
  };
  
  // Prefix measurements so that it's possible to filter them.
  // Longer prefixes are hard to read in DevTools.
  var reactEmoji = '\u269B';
  var warningEmoji = '\u26D4';
  var supportsUserTiming = typeof performance !== 'undefined' && typeof performance.mark === 'function' && typeof performance.clearMarks === 'function' && typeof performance.measure === 'function' && typeof performance.clearMeasures === 'function';
  
  // Keep track of current fiber so that we know the path to unwind on pause.
  // TODO: this looks the same as nextUnitOfWork in scheduler. Can we unify them?
  var currentFiber = null;
  // If we're in the middle of user code, which fiber and method is it?
  // Reusing `currentFiber` would be confusing for this because user code fiber
  // can change during commit phase too, but we don't need to unwind it (since
  // lifecycles in the commit phase don't resemble a tree).
  var currentPhase = null;
  var currentPhaseFiber = null;
  // Did lifecycle hook schedule an update? This is often a performance problem,
  // so we will keep track of it, and include it in the report.
  // Track commits caused by cascading updates.
  var isCommitting = false;
  var hasScheduledUpdateInCurrentCommit = false;
  var hasScheduledUpdateInCurrentPhase = false;
  var commitCountInCurrentWorkLoop = 0;
  var effectCountInCurrentCommit = 0;
  var isWaitingForCallback = false;
  // During commits, we only show a measurement once per method name
  // to avoid stretch the commit phase with measurement overhead.
  var labelsInCurrentCommit = new Set();
  
  var formatMarkName = function (markName) {
    return reactEmoji + ' ' + markName;
  };
  
  var formatLabel = function (label, warning$$1) {
    var prefix = warning$$1 ? warningEmoji + ' ' : reactEmoji + ' ';
    var suffix = warning$$1 ? ' Warning: ' + warning$$1 : '';
    return '' + prefix + label + suffix;
  };
  
  var beginMark = function (markName) {
    performance.mark(formatMarkName(markName));
  };
  
  var clearMark = function (markName) {
    performance.clearMarks(formatMarkName(markName));
  };
  
  var endMark = function (label, markName, warning$$1) {
    var formattedMarkName = formatMarkName(markName);
    var formattedLabel = formatLabel(label, warning$$1);
    try {
      performance.measure(formattedLabel, formattedMarkName);
    } catch (err) {}
    // If previous mark was missing for some reason, this will throw.
    // This could only happen if React crashed in an unexpected place earlier.
    // Don't pile on with more errors.
  
    // Clear marks immediately to avoid growing buffer.
    performance.clearMarks(formattedMarkName);
    performance.clearMeasures(formattedLabel);
  };
  
  var getFiberMarkName = function (label, debugID) {
    return label + ' (#' + debugID + ')';
  };
  
  var getFiberLabel = function (componentName, isMounted, phase) {
    if (phase === null) {
      // These are composite component total time measurements.
      return componentName + ' [' + (isMounted ? 'update' : 'mount') + ']';
    } else {
      // Composite component methods.
      return componentName + '.' + phase;
    }
  };
  
  var beginFiberMark = function (fiber, phase) {
    var componentName = getComponentName(fiber) || 'Unknown';
    var debugID = fiber._debugID;
    var isMounted = fiber.alternate !== null;
    var label = getFiberLabel(componentName, isMounted, phase);
  
    if (isCommitting && labelsInCurrentCommit.has(label)) {
      // During the commit phase, we don't show duplicate labels because
      // there is a fixed overhead for every measurement, and we don't
      // want to stretch the commit phase beyond necessary.
      return false;
    }
    labelsInCurrentCommit.add(label);
  
    var markName = getFiberMarkName(label, debugID);
    beginMark(markName);
    return true;
  };
  
  var clearFiberMark = function (fiber, phase) {
    var componentName = getComponentName(fiber) || 'Unknown';
    var debugID = fiber._debugID;
    var isMounted = fiber.alternate !== null;
    var label = getFiberLabel(componentName, isMounted, phase);
    var markName = getFiberMarkName(label, debugID);
    clearMark(markName);
  };
  
  var endFiberMark = function (fiber, phase, warning$$1) {
    var componentName = getComponentName(fiber) || 'Unknown';
    var debugID = fiber._debugID;
    var isMounted = fiber.alternate !== null;
    var label = getFiberLabel(componentName, isMounted, phase);
    var markName = getFiberMarkName(label, debugID);
    endMark(label, markName, warning$$1);
  };
  
  var shouldIgnoreFiber = function (fiber) {
    // Host components should be skipped in the timeline.
    // We could check typeof fiber.type, but does this work with RN?
    switch (fiber.tag) {
      case HostRoot:
      case HostComponent:
      case HostText:
      case HostPortal:
      case ReturnComponent:
      case Fragment:
        return true;
      default:
        return false;
    }
  };
  
  var clearPendingPhaseMeasurement = function () {
    if (currentPhase !== null && currentPhaseFiber !== null) {
      clearFiberMark(currentPhaseFiber, currentPhase);
    }
    currentPhaseFiber = null;
    currentPhase = null;
    hasScheduledUpdateInCurrentPhase = false;
  };
  
  var pauseTimers = function () {
    // Stops all currently active measurements so that they can be resumed
    // if we continue in a later deferred loop from the same unit of work.
    var fiber = currentFiber;
    while (fiber) {
      if (fiber._debugIsCurrentlyTiming) {
        endFiberMark(fiber, null, null);
      }
      fiber = fiber['return'];
    }
  };
  
  var resumeTimersRecursively = function (fiber) {
    if (fiber['return'] !== null) {
      resumeTimersRecursively(fiber['return']);
    }
    if (fiber._debugIsCurrentlyTiming) {
      beginFiberMark(fiber, null);
    }
  };
  
  var resumeTimers = function () {
    // Resumes all measurements that were active during the last deferred loop.
    if (currentFiber !== null) {
      resumeTimersRecursively(currentFiber);
    }
  };
  
  function recordEffect() {
    if (enableUserTimingAPI) {
      effectCountInCurrentCommit++;
    }
  }
  
  function recordScheduleUpdate() {
    if (enableUserTimingAPI) {
      if (isCommitting) {
        hasScheduledUpdateInCurrentCommit = true;
      }
      if (currentPhase !== null && currentPhase !== 'componentWillMount' && currentPhase !== 'componentWillReceiveProps') {
        hasScheduledUpdateInCurrentPhase = true;
      }
    }
  }
  
  function startRequestCallbackTimer() {
    if (enableUserTimingAPI) {
      if (supportsUserTiming && !isWaitingForCallback) {
        isWaitingForCallback = true;
        beginMark('(Waiting for async callback...)');
      }
    }
  }
  
  function stopRequestCallbackTimer(didExpire) {
    if (enableUserTimingAPI) {
      if (supportsUserTiming) {
        isWaitingForCallback = false;
        var warning$$1 = didExpire ? 'React was blocked by main thread' : null;
        endMark('(Waiting for async callback...)', '(Waiting for async callback...)', warning$$1);
      }
    }
  }
  
  function startWorkTimer(fiber) {
    if (enableUserTimingAPI) {
      if (!supportsUserTiming || shouldIgnoreFiber(fiber)) {
        return;
      }
      // If we pause, this is the fiber to unwind from.
      currentFiber = fiber;
      if (!beginFiberMark(fiber, null)) {
        return;
      }
      fiber._debugIsCurrentlyTiming = true;
    }
  }
  
  function cancelWorkTimer(fiber) {
    if (enableUserTimingAPI) {
      if (!supportsUserTiming || shouldIgnoreFiber(fiber)) {
        return;
      }
      // Remember we shouldn't complete measurement for this fiber.
      // Otherwise flamechart will be deep even for small updates.
      fiber._debugIsCurrentlyTiming = false;
      clearFiberMark(fiber, null);
    }
  }
  
  function stopWorkTimer(fiber) {
    if (enableUserTimingAPI) {
      if (!supportsUserTiming || shouldIgnoreFiber(fiber)) {
        return;
      }
      // If we pause, its parent is the fiber to unwind from.
      currentFiber = fiber['return'];
      if (!fiber._debugIsCurrentlyTiming) {
        return;
      }
      fiber._debugIsCurrentlyTiming = false;
      endFiberMark(fiber, null, null);
    }
  }
  
  function stopFailedWorkTimer(fiber) {
    if (enableUserTimingAPI) {
      if (!supportsUserTiming || shouldIgnoreFiber(fiber)) {
        return;
      }
      // If we pause, its parent is the fiber to unwind from.
      currentFiber = fiber['return'];
      if (!fiber._debugIsCurrentlyTiming) {
        return;
      }
      fiber._debugIsCurrentlyTiming = false;
      var warning$$1 = 'An error was thrown inside this error boundary';
      endFiberMark(fiber, null, warning$$1);
    }
  }
  
  function startPhaseTimer(fiber, phase) {
    if (enableUserTimingAPI) {
      if (!supportsUserTiming) {
        return;
      }
      clearPendingPhaseMeasurement();
      if (!beginFiberMark(fiber, phase)) {
        return;
      }
      currentPhaseFiber = fiber;
      currentPhase = phase;
    }
  }
  
  function stopPhaseTimer() {
    if (enableUserTimingAPI) {
      if (!supportsUserTiming) {
        return;
      }
      if (currentPhase !== null && currentPhaseFiber !== null) {
        var warning$$1 = hasScheduledUpdateInCurrentPhase ? 'Scheduled a cascading update' : null;
        endFiberMark(currentPhaseFiber, currentPhase, warning$$1);
      }
      currentPhase = null;
      currentPhaseFiber = null;
    }
  }
  
  function startWorkLoopTimer(nextUnitOfWork) {
    if (enableUserTimingAPI) {
      currentFiber = nextUnitOfWork;
      if (!supportsUserTiming) {
        return;
      }
      commitCountInCurrentWorkLoop = 0;
      // This is top level call.
      // Any other measurements are performed within.
      beginMark('(React Tree Reconciliation)');
      // Resume any measurements that were in progress during the last loop.
      resumeTimers();
    }
  }
  
  function stopWorkLoopTimer(interruptedBy) {
    if (enableUserTimingAPI) {
      if (!supportsUserTiming) {
        return;
      }
      var warning$$1 = null;
      if (interruptedBy !== null) {
        if (interruptedBy.tag === HostRoot) {
          warning$$1 = 'A top-level update interrupted the previous render';
        } else {
          var componentName = getComponentName(interruptedBy) || 'Unknown';
          warning$$1 = 'An update to ' + componentName + ' interrupted the previous render';
        }
      } else if (commitCountInCurrentWorkLoop > 1) {
        warning$$1 = 'There were cascading updates';
      }
      commitCountInCurrentWorkLoop = 0;
      // Pause any measurements until the next loop.
      pauseTimers();
      endMark('(React Tree Reconciliation)', '(React Tree Reconciliation)', warning$$1);
    }
  }
  
  function startCommitTimer() {
    if (enableUserTimingAPI) {
      if (!supportsUserTiming) {
        return;
      }
      isCommitting = true;
      hasScheduledUpdateInCurrentCommit = false;
      labelsInCurrentCommit.clear();
      beginMark('(Committing Changes)');
    }
  }
  
  function stopCommitTimer() {
    if (enableUserTimingAPI) {
      if (!supportsUserTiming) {
        return;
      }
  
      var warning$$1 = null;
      if (hasScheduledUpdateInCurrentCommit) {
        warning$$1 = 'Lifecycle hook scheduled a cascading update';
      } else if (commitCountInCurrentWorkLoop > 0) {
        warning$$1 = 'Caused by a cascading update in earlier commit';
      }
      hasScheduledUpdateInCurrentCommit = false;
      commitCountInCurrentWorkLoop++;
      isCommitting = false;
      labelsInCurrentCommit.clear();
  
      endMark('(Committing Changes)', '(Committing Changes)', warning$$1);
    }
  }
  
  function startCommitHostEffectsTimer() {
    if (enableUserTimingAPI) {
      if (!supportsUserTiming) {
        return;
      }
      effectCountInCurrentCommit = 0;
      beginMark('(Committing Host Effects)');
    }
  }
  
  function stopCommitHostEffectsTimer() {
    if (enableUserTimingAPI) {
      if (!supportsUserTiming) {
        return;
      }
      var count = effectCountInCurrentCommit;
      effectCountInCurrentCommit = 0;
      endMark('(Committing Host Effects: ' + count + ' Total)', '(Committing Host Effects)', null);
    }
  }
  
  function startCommitLifeCyclesTimer() {
    if (enableUserTimingAPI) {
      if (!supportsUserTiming) {
        return;
      }
      effectCountInCurrentCommit = 0;
      beginMark('(Calling Lifecycle Methods)');
    }
  }
  
  function stopCommitLifeCyclesTimer() {
    if (enableUserTimingAPI) {
      if (!supportsUserTiming) {
        return;
      }
      var count = effectCountInCurrentCommit;
      effectCountInCurrentCommit = 0;
      endMark('(Calling Lifecycle Methods: ' + count + ' Total)', '(Calling Lifecycle Methods)', null);
    }
  }
  
  {
    var warnedAboutMissingGetChildContext = {};
  }
  
  // A cursor to the current merged context object on the stack.
  var contextStackCursor = createCursor(emptyObject);
  // A cursor to a boolean indicating whether the context has changed.
  var didPerformWorkStackCursor = createCursor(false);
  // Keep track of the previous context object that was on the stack.
  // We use this to get access to the parent context after we have already
  // pushed the next context provider, and now need to merge their contexts.
  var previousContext = emptyObject;
  
  function getUnmaskedContext(workInProgress) {
    var hasOwnContext = isContextProvider(workInProgress);
    if (hasOwnContext) {
      // If the fiber is a context provider itself, when we read its context
      // we have already pushed its own child context on the stack. A context
      // provider should not "see" its own child context. Therefore we read the
      // previous (parent) context instead for a context provider.
      return previousContext;
    }
    return contextStackCursor.current;
  }
  
  function cacheContext(workInProgress, unmaskedContext, maskedContext) {
    var instance = workInProgress.stateNode;
    instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
    instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
  }
  
  function getMaskedContext(workInProgress, unmaskedContext) {
    var type = workInProgress.type;
    var contextTypes = type.contextTypes;
    if (!contextTypes) {
      return emptyObject;
    }
  
    // Avoid recreating masked context unless unmasked context has changed.
    // Failing to do this will result in unnecessary calls to componentWillReceiveProps.
    // This may trigger infinite loops if componentWillReceiveProps calls setState.
    var instance = workInProgress.stateNode;
    if (instance && instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext) {
      return instance.__reactInternalMemoizedMaskedChildContext;
    }
  
    var context = {};
    for (var key in contextTypes) {
      context[key] = unmaskedContext[key];
    }
  
    {
      var name = getComponentName(workInProgress) || 'Unknown';
      checkPropTypes(contextTypes, context, 'context', name, ReactDebugCurrentFiber.getCurrentFiberStackAddendum);
    }
  
    // Cache unmasked context so we can avoid recreating masked context unless necessary.
    // Context is created before the class component is instantiated so check for instance.
    if (instance) {
      cacheContext(workInProgress, unmaskedContext, context);
    }
  
    return context;
  }
  
  function hasContextChanged() {
    return didPerformWorkStackCursor.current;
  }
  
  function isContextConsumer(fiber) {
    return fiber.tag === ClassComponent && fiber.type.contextTypes != null;
  }
  
  function isContextProvider(fiber) {
    return fiber.tag === ClassComponent && fiber.type.childContextTypes != null;
  }
  
  function popContextProvider(fiber) {
    if (!isContextProvider(fiber)) {
      return;
    }
  
    pop(didPerformWorkStackCursor, fiber);
    pop(contextStackCursor, fiber);
  }
  
  function popTopLevelContextObject(fiber) {
    pop(didPerformWorkStackCursor, fiber);
    pop(contextStackCursor, fiber);
  }
  
  function pushTopLevelContextObject(fiber, context, didChange) {
    !(contextStackCursor.cursor == null) ? invariant(false, 'Unexpected context found on stack. This error is likely caused by a bug in React. Please file an issue.') : void 0;
  
    push(contextStackCursor, context, fiber);
    push(didPerformWorkStackCursor, didChange, fiber);
  }
  
  function processChildContext(fiber, parentContext) {
    var instance = fiber.stateNode;
    var childContextTypes = fiber.type.childContextTypes;
  
    // TODO (bvaughn) Replace this behavior with an invariant() in the future.
    // It has only been added in Fiber to match the (unintentional) behavior in Stack.
    if (typeof instance.getChildContext !== 'function') {
      {
        var componentName = getComponentName(fiber) || 'Unknown';
  
        if (!warnedAboutMissingGetChildContext[componentName]) {
          warnedAboutMissingGetChildContext[componentName] = true;
          warning(false, '%s.childContextTypes is specified but there is no getChildContext() method ' + 'on the instance. You can either define getChildContext() on %s or remove ' + 'childContextTypes from it.', componentName, componentName);
        }
      }
      return parentContext;
    }
  
    var childContext = void 0;
    {
      ReactDebugCurrentFiber.setCurrentPhase('getChildContext');
    }
    startPhaseTimer(fiber, 'getChildContext');
    childContext = instance.getChildContext();
    stopPhaseTimer();
    {
      ReactDebugCurrentFiber.setCurrentPhase(null);
    }
    for (var contextKey in childContext) {
      !(contextKey in childContextTypes) ? invariant(false, '%s.getChildContext(): key "%s" is not defined in childContextTypes.', getComponentName(fiber) || 'Unknown', contextKey) : void 0;
    }
    {
      var name = getComponentName(fiber) || 'Unknown';
      checkPropTypes(childContextTypes, childContext, 'child context', name,
      // In practice, there is one case in which we won't get a stack. It's when
      // somebody calls unstable_renderSubtreeIntoContainer() and we process
      // context from the parent component instance. The stack will be missing
      // because it's outside of the reconciliation, and so the pointer has not
      // been set. This is rare and doesn't matter. We'll also remove that API.
      ReactDebugCurrentFiber.getCurrentFiberStackAddendum);
    }
  
    return _assign({}, parentContext, childContext);
  }
  
  function pushContextProvider(workInProgress) {
    if (!isContextProvider(workInProgress)) {
      return false;
    }
  
    var instance = workInProgress.stateNode;
    // We push the context as early as possible to ensure stack integrity.
    // If the instance does not exist yet, we will push null at first,
    // and replace it on the stack later when invalidating the context.
    var memoizedMergedChildContext = instance && instance.__reactInternalMemoizedMergedChildContext || emptyObject;
  
    // Remember the parent context so we can merge with it later.
    // Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
    previousContext = contextStackCursor.current;
    push(contextStackCursor, memoizedMergedChildContext, workInProgress);
    push(didPerformWorkStackCursor, didPerformWorkStackCursor.current, workInProgress);
  
    return true;
  }
  
  function invalidateContextProvider(workInProgress, didChange) {
    var instance = workInProgress.stateNode;
    !instance ? invariant(false, 'Expected to have an instance by this point. This error is likely caused by a bug in React. Please file an issue.') : void 0;
  
    if (didChange) {
      // Merge parent and own context.
      // Skip this if we're not updating due to sCU.
      // This avoids unnecessarily recomputing memoized values.
      var mergedContext = processChildContext(workInProgress, previousContext);
      instance.__reactInternalMemoizedMergedChildContext = mergedContext;
  
      // Replace the old (or empty) context with the new one.
      // It is important to unwind the context in the reverse order.
      pop(didPerformWorkStackCursor, workInProgress);
      pop(contextStackCursor, workInProgress);
      // Now push the new context and mark that it has changed.
      push(contextStackCursor, mergedContext, workInProgress);
      push(didPerformWorkStackCursor, didChange, workInProgress);
    } else {
      pop(didPerformWorkStackCursor, workInProgress);
      push(didPerformWorkStackCursor, didChange, workInProgress);
    }
  }
  
  function resetContext() {
    previousContext = emptyObject;
    contextStackCursor.current = emptyObject;
    didPerformWorkStackCursor.current = false;
  }
  
  function findCurrentUnmaskedContext(fiber) {
    // Currently this is only used with renderSubtreeIntoContainer; not sure if it
    // makes sense elsewhere
    !(isFiberMounted(fiber) && fiber.tag === ClassComponent) ? invariant(false, 'Expected subtree parent to be a mounted class component. This error is likely caused by a bug in React. Please file an issue.') : void 0;
  
    var node = fiber;
    while (node.tag !== HostRoot) {
      if (isContextProvider(node)) {
        return node.stateNode.__reactInternalMemoizedMergedChildContext;
      }
      var parent = node['return'];
      !parent ? invariant(false, 'Found unexpected detached subtree parent. This error is likely caused by a bug in React. Please file an issue.') : void 0;
      node = parent;
    }
    return node.stateNode.context;
  }
  
  var NoWork = 0; // TODO: Use an opaque type once ESLint et al support the syntax
  
  var Sync = 1;
  var Never = 2147483647; // Max int32: Math.pow(2, 31) - 1
  
  var UNIT_SIZE = 10;
  var MAGIC_NUMBER_OFFSET = 2;
  
  // 1 unit of expiration time represents 10ms.
  function msToExpirationTime(ms) {
    // Always add an offset so that we don't clash with the magic number for NoWork.
    return (ms / UNIT_SIZE | 0) + MAGIC_NUMBER_OFFSET;
  }
  
  function expirationTimeToMs(expirationTime) {
    return (expirationTime - MAGIC_NUMBER_OFFSET) * UNIT_SIZE;
  }
  
  function ceiling(num, precision) {
    return ((num / precision | 0) + 1) * precision;
  }
  
  function computeExpirationBucket(currentTime, expirationInMs, bucketSizeMs) {
    return ceiling(currentTime + expirationInMs / UNIT_SIZE, bucketSizeMs / UNIT_SIZE);
  }
  
  var NoContext = 0;
  var AsyncUpdates = 1;
  
  {
    var hasBadMapPolyfill = false;
    try {
      var nonExtensibleObject = Object.preventExtensions({});
      /* eslint-disable no-new */
      
      /* eslint-enable no-new */
    } catch (e) {
      // TODO: Consider warning about bad polyfills
      hasBadMapPolyfill = true;
    }
  }
  
  // A Fiber is work on a Component that needs to be done or was done. There can
  // be more than one per component.
  
  
  {
    var debugCounter = 1;
  }
  
  function FiberNode(tag, key, internalContextTag) {
    // Instance
    this.tag = tag;
    this.key = key;
    this.type = null;
    this.stateNode = null;
  
    // Fiber
    this['return'] = null;
    this.child = null;
    this.sibling = null;
    this.index = 0;
  
    this.ref = null;
  
    this.pendingProps = null;
    this.memoizedProps = null;
    this.updateQueue = null;
    this.memoizedState = null;
  
    this.internalContextTag = internalContextTag;
  
    // Effects
    this.effectTag = NoEffect;
    this.nextEffect = null;
  
    this.firstEffect = null;
    this.lastEffect = null;
  
    this.expirationTime = NoWork;
  
    this.alternate = null;
  
    {
      this._debugID = debugCounter++;
      this._debugSource = null;
      this._debugOwner = null;
      this._debugIsCurrentlyTiming = false;
      if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
        Object.preventExtensions(this);
      }
    }
  }
  
  // This is a constructor function, rather than a POJO constructor, still
  // please ensure we do the following:
  // 1) Nobody should add any instance methods on this. Instance methods can be
  //    more difficult to predict when they get optimized and they are almost
  //    never inlined properly in static compilers.
  // 2) Nobody should rely on `instanceof Fiber` for type testing. We should
  //    always know when it is a fiber.
  // 3) We might want to experiment with using numeric keys since they are easier
  //    to optimize in a non-JIT environment.
  // 4) We can easily go from a constructor to a createFiber object literal if that
  //    is faster.
  // 5) It should be easy to port this to a C struct and keep a C implementation
  //    compatible.
  var createFiber = function (tag, key, internalContextTag) {
    // $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
    return new FiberNode(tag, key, internalContextTag);
  };
  
  function shouldConstruct(Component) {
    return !!(Component.prototype && Component.prototype.isReactComponent);
  }
  
  // This is used to create an alternate fiber to do work on.
  function createWorkInProgress(current, pendingProps, expirationTime) {
    var workInProgress = current.alternate;
    if (workInProgress === null) {
      // We use a double buffering pooling technique because we know that we'll
      // only ever need at most two versions of a tree. We pool the "other" unused
      // node that we're free to reuse. This is lazily created to avoid allocating
      // extra objects for things that are never updated. It also allow us to
      // reclaim the extra memory if needed.
      workInProgress = createFiber(current.tag, current.key, current.internalContextTag);
      workInProgress.type = current.type;
      workInProgress.stateNode = current.stateNode;
  
      {
        // DEV-only fields
        workInProgress._debugID = current._debugID;
        workInProgress._debugSource = current._debugSource;
        workInProgress._debugOwner = current._debugOwner;
      }
  
      workInProgress.alternate = current;
      current.alternate = workInProgress;
    } else {
      // We already have an alternate.
      // Reset the effect tag.
      workInProgress.effectTag = NoEffect;
  
      // The effect list is no longer valid.
      workInProgress.nextEffect = null;
      workInProgress.firstEffect = null;
      workInProgress.lastEffect = null;
    }
  
    workInProgress.expirationTime = expirationTime;
    workInProgress.pendingProps = pendingProps;
  
    workInProgress.child = current.child;
    workInProgress.memoizedProps = current.memoizedProps;
    workInProgress.memoizedState = current.memoizedState;
    workInProgress.updateQueue = current.updateQueue;
  
    // These will be overridden during the parent's reconciliation
    workInProgress.sibling = current.sibling;
    workInProgress.index = current.index;
    workInProgress.ref = current.ref;
  
    return workInProgress;
  }
  
  function createHostRootFiber() {
    var fiber = createFiber(HostRoot, null, NoContext);
    return fiber;
  }
  
  function createFiberFromElement(element, internalContextTag, expirationTime) {
    var owner = null;
    {
      owner = element._owner;
    }
  
    var fiber = void 0;
    var type = element.type,
        key = element.key;
  
    if (typeof type === 'function') {
      fiber = shouldConstruct(type) ? createFiber(ClassComponent, key, internalContextTag) : createFiber(IndeterminateComponent, key, internalContextTag);
      fiber.type = type;
      fiber.pendingProps = element.props;
    } else if (typeof type === 'string') {
      fiber = createFiber(HostComponent, key, internalContextTag);
      fiber.type = type;
      fiber.pendingProps = element.props;
    } else if (typeof type === 'object' && type !== null && typeof type.tag === 'number') {
      // Currently assumed to be a continuation and therefore is a fiber already.
      // TODO: The yield system is currently broken for updates in some cases.
      // The reified yield stores a fiber, but we don't know which fiber that is;
      // the current or a workInProgress? When the continuation gets rendered here
      // we don't know if we can reuse that fiber or if we need to clone it.
      // There is probably a clever way to restructure this.
      fiber = type;
      fiber.pendingProps = element.props;
    } else {
      var info = '';
      {
        if (type === undefined || typeof type === 'object' && type !== null && Object.keys(type).length === 0) {
          info += ' You likely forgot to export your component from the file ' + "it's defined in, or you might have mixed up default and named imports.";
        }
        var ownerName = owner ? getComponentName(owner) : null;
        if (ownerName) {
          info += '\n\nCheck the render method of `' + ownerName + '`.';
        }
      }
      invariant(false, 'Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s', type == null ? type : typeof type, info);
    }
  
    {
      fiber._debugSource = element._source;
      fiber._debugOwner = element._owner;
    }
  
    fiber.expirationTime = expirationTime;
  
    return fiber;
  }
  
  function createFiberFromFragment(elements, internalContextTag, expirationTime, key) {
    var fiber = createFiber(Fragment, key, internalContextTag);
    fiber.pendingProps = elements;
    fiber.expirationTime = expirationTime;
    return fiber;
  }
  
  function createFiberFromText(content, internalContextTag, expirationTime) {
    var fiber = createFiber(HostText, null, internalContextTag);
    fiber.pendingProps = content;
    fiber.expirationTime = expirationTime;
    return fiber;
  }
  
  function createFiberFromHostInstanceForDeletion() {
    var fiber = createFiber(HostComponent, null, NoContext);
    fiber.type = 'DELETED';
    return fiber;
  }
  
  function createFiberFromCall(call, internalContextTag, expirationTime) {
    var fiber = createFiber(CallComponent, call.key, internalContextTag);
    fiber.type = call.handler;
    fiber.pendingProps = call;
    fiber.expirationTime = expirationTime;
    return fiber;
  }
  
  function createFiberFromReturn(returnNode, internalContextTag, expirationTime) {
    var fiber = createFiber(ReturnComponent, null, internalContextTag);
    fiber.expirationTime = expirationTime;
    return fiber;
  }
  
  function createFiberFromPortal(portal, internalContextTag, expirationTime) {
    var fiber = createFiber(HostPortal, portal.key, internalContextTag);
    fiber.pendingProps = portal.children || [];
    fiber.expirationTime = expirationTime;
    fiber.stateNode = {
      containerInfo: portal.containerInfo,
      pendingChildren: null, // Used by persistent updates
      implementation: portal.implementation
    };
    return fiber;
  }
  
  function createFiberRoot(containerInfo, hydrate) {
    // Cyclic construction. This cheats the type system right now because
    // stateNode is any.
    var uninitializedFiber = createHostRootFiber();
    var root = {
      current: uninitializedFiber,
      containerInfo: containerInfo,
      pendingChildren: null,
      remainingExpirationTime: NoWork,
      isReadyForCommit: false,
      finishedWork: null,
      context: null,
      pendingContext: null,
      hydrate: hydrate,
      nextScheduledRoot: null
    };
    uninitializedFiber.stateNode = root;
    return root;
  }
  
  var onCommitFiberRoot = null;
  var onCommitFiberUnmount = null;
  var hasLoggedError = false;
  
  function catchErrors(fn) {
    return function (arg) {
      try {
        return fn(arg);
      } catch (err) {
        if (true && !hasLoggedError) {
          hasLoggedError = true;
          warning(false, 'React DevTools encountered an error: %s', err);
        }
      }
    };
  }
  
  function injectInternals(internals) {
    if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') {
      // No DevTools
      return false;
    }
    var hook = __REACT_DEVTOOLS_GLOBAL_HOOK__;
    if (hook.isDisabled) {
      // This isn't a real property on the hook, but it can be set to opt out
      // of DevTools integration and associated warnings and logs.
      // https://github.com/facebook/react/issues/3877
      return true;
    }
    if (!hook.supportsFiber) {
      {
        warning(false, 'The installed version of React DevTools is too old and will not work ' + 'with the current version of React. Please update React DevTools. ' + 'https://fb.me/react-devtools');
      }
      // DevTools exists, even though it doesn't support Fiber.
      return true;
    }
    try {
      var rendererID = hook.inject(internals);
      // We have successfully injected, so now it is safe to set up hooks.
      onCommitFiberRoot = catchErrors(function (root) {
        return hook.onCommitFiberRoot(rendererID, root);
      });
      onCommitFiberUnmount = catchErrors(function (fiber) {
        return hook.onCommitFiberUnmount(rendererID, fiber);
      });
    } catch (err) {
      // Catch all errors because it is unsafe to throw during initialization.
      {
        warning(false, 'React DevTools encountered an error: %s.', err);
      }
    }
    // DevTools exists
    return true;
  }
  
  function onCommitRoot(root) {
    if (typeof onCommitFiberRoot === 'function') {
      onCommitFiberRoot(root);
    }
  }
  
  function onCommitUnmount(fiber) {
    if (typeof onCommitFiberUnmount === 'function') {
      onCommitFiberUnmount(fiber);
    }
  }
  
  {
    var didWarnUpdateInsideUpdate = false;
  }
  
  // Callbacks are not validated until invocation
  
  
  // Singly linked-list of updates. When an update is scheduled, it is added to
  // the queue of the current fiber and the work-in-progress fiber. The two queues
  // are separate but they share a persistent structure.
  //
  // During reconciliation, updates are removed from the work-in-progress fiber,
  // but they remain on the current fiber. That ensures that if a work-in-progress
  // is aborted, the aborted updates are recovered by cloning from current.
  //
  // The work-in-progress queue is always a subset of the current queue.
  //
  // When the tree is committed, the work-in-progress becomes the current.
  
  
  function createUpdateQueue(baseState) {
    var queue = {
      baseState: baseState,
      expirationTime: NoWork,
      first: null,
      last: null,
      callbackList: null,
      hasForceUpdate: false,
      isInitialized: false
    };
    {
      queue.isProcessing = false;
    }
    return queue;
  }
  
  function insertUpdateIntoQueue(queue, update) {
    // Append the update to the end of the list.
    if (queue.last === null) {
      // Queue is empty
      queue.first = queue.last = update;
    } else {
      queue.last.next = update;
      queue.last = update;
    }
    if (queue.expirationTime === NoWork || queue.expirationTime > update.expirationTime) {
      queue.expirationTime = update.expirationTime;
    }
  }
  
  function insertUpdateIntoFiber(fiber, update) {
    // We'll have at least one and at most two distinct update queues.
    var alternateFiber = fiber.alternate;
    var queue1 = fiber.updateQueue;
    if (queue1 === null) {
      // TODO: We don't know what the base state will be until we begin work.
      // It depends on which fiber is the next current. Initialize with an empty
      // base state, then set to the memoizedState when rendering. Not super
      // happy with this approach.
      queue1 = fiber.updateQueue = createUpdateQueue(null);
    }
  
    var queue2 = void 0;
    if (alternateFiber !== null) {
      queue2 = alternateFiber.updateQueue;
      if (queue2 === null) {
        queue2 = alternateFiber.updateQueue = createUpdateQueue(null);
      }
    } else {
      queue2 = null;
    }
    queue2 = queue2 !== queue1 ? queue2 : null;
  
    // Warn if an update is scheduled from inside an updater function.
    {
      if ((queue1.isProcessing || queue2 !== null && queue2.isProcessing) && !didWarnUpdateInsideUpdate) {
        warning(false, 'An update (setState, replaceState, or forceUpdate) was scheduled ' + 'from inside an update function. Update functions should be pure, ' + 'with zero side-effects. Consider using componentDidUpdate or a ' + 'callback.');
        didWarnUpdateInsideUpdate = true;
      }
    }
  
    // If there's only one queue, add the update to that queue and exit.
    if (queue2 === null) {
      insertUpdateIntoQueue(queue1, update);
      return;
    }
  
    // If either queue is empty, we need to add to both queues.
    if (queue1.last === null || queue2.last === null) {
      insertUpdateIntoQueue(queue1, update);
      insertUpdateIntoQueue(queue2, update);
      return;
    }
  
    // If both lists are not empty, the last update is the same for both lists
    // because of structural sharing. So, we should only append to one of
    // the lists.
    insertUpdateIntoQueue(queue1, update);
    // But we still need to update the `last` pointer of queue2.
    queue2.last = update;
  }
  
  function getUpdateExpirationTime(fiber) {
    if (fiber.tag !== ClassComponent && fiber.tag !== HostRoot) {
      return NoWork;
    }
    var updateQueue = fiber.updateQueue;
    if (updateQueue === null) {
      return NoWork;
    }
    return updateQueue.expirationTime;
  }
  
  function getStateFromUpdate(update, instance, prevState, props) {
    var partialState = update.partialState;
    if (typeof partialState === 'function') {
      var updateFn = partialState;
  
      // Invoke setState callback an extra time to help detect side-effects.
      if (debugRenderPhaseSideEffects) {
        updateFn.call(instance, prevState, props);
      }
  
      return updateFn.call(instance, prevState, props);
    } else {
      return partialState;
    }
  }
  
  function processUpdateQueue(current, workInProgress, queue, instance, props, renderExpirationTime) {
    if (current !== null && current.updateQueue === queue) {
      // We need to create a work-in-progress queue, by cloning the current queue.
      var currentQueue = queue;
      queue = workInProgress.updateQueue = {
        baseState: currentQueue.baseState,
        expirationTime: currentQueue.expirationTime,
        first: currentQueue.first,
        last: currentQueue.last,
        isInitialized: currentQueue.isInitialized,
        // These fields are no longer valid because they were already committed.
        // Reset them.
        callbackList: null,
        hasForceUpdate: false
      };
    }
  
    {
      // Set this flag so we can warn if setState is called inside the update
      // function of another setState.
      queue.isProcessing = true;
    }
  
    // Reset the remaining expiration time. If we skip over any updates, we'll
    // increase this accordingly.
    queue.expirationTime = NoWork;
  
    // TODO: We don't know what the base state will be until we begin work.
    // It depends on which fiber is the next current. Initialize with an empty
    // base state, then set to the memoizedState when rendering. Not super
    // happy with this approach.
    var state = void 0;
    if (queue.isInitialized) {
      state = queue.baseState;
    } else {
      state = queue.baseState = workInProgress.memoizedState;
      queue.isInitialized = true;
    }
    var dontMutatePrevState = true;
    var update = queue.first;
    var didSkip = false;
    while (update !== null) {
      var updateExpirationTime = update.expirationTime;
      if (updateExpirationTime > renderExpirationTime) {
        // This update does not have sufficient priority. Skip it.
        var remainingExpirationTime = queue.expirationTime;
        if (remainingExpirationTime === NoWork || remainingExpirationTime > updateExpirationTime) {
          // Update the remaining expiration time.
          queue.expirationTime = updateExpirationTime;
        }
        if (!didSkip) {
          didSkip = true;
          queue.baseState = state;
        }
        // Continue to the next update.
        update = update.next;
        continue;
      }
  
      // This update does have sufficient priority.
  
      // If no previous updates were skipped, drop this update from the queue by
      // advancing the head of the list.
      if (!didSkip) {
        queue.first = update.next;
        if (queue.first === null) {
          queue.last = null;
        }
      }
  
      // Process the update
      var _partialState = void 0;
      if (update.isReplace) {
        state = getStateFromUpdate(update, instance, state, props);
        dontMutatePrevState = true;
      } else {
        _partialState = getStateFromUpdate(update, instance, state, props);
        if (_partialState) {
          if (dontMutatePrevState) {
            // $FlowFixMe: Idk how to type this properly.
            state = _assign({}, state, _partialState);
          } else {
            state = _assign(state, _partialState);
          }
          dontMutatePrevState = false;
        }
      }
      if (update.isForced) {
        queue.hasForceUpdate = true;
      }
      if (update.callback !== null) {
        // Append to list of callbacks.
        var _callbackList = queue.callbackList;
        if (_callbackList === null) {
          _callbackList = queue.callbackList = [];
        }
        _callbackList.push(update);
      }
      update = update.next;
    }
  
    if (queue.callbackList !== null) {
      workInProgress.effectTag |= Callback;
    } else if (queue.first === null && !queue.hasForceUpdate) {
      // The queue is empty. We can reset it.
      workInProgress.updateQueue = null;
    }
  
    if (!didSkip) {
      didSkip = true;
      queue.baseState = state;
    }
  
    {
      // No longer processing.
      queue.isProcessing = false;
    }
  
    return state;
  }
  
  function commitCallbacks(queue, context) {
    var callbackList = queue.callbackList;
    if (callbackList === null) {
      return;
    }
    // Set the list to null to make sure they don't get called more than once.
    queue.callbackList = null;
    for (var i = 0; i < callbackList.length; i++) {
      var update = callbackList[i];
      var _callback = update.callback;
      // This update might be processed again. Clear the callback so it's only
      // called once.
      update.callback = null;
      !(typeof _callback === 'function') ? invariant(false, 'Invalid argument passed as callback. Expected a function. Instead received: %s', _callback) : void 0;
      _callback.call(context);
    }
  }
  
  var fakeInternalInstance = {};
  var isArray = Array.isArray;
  
  {
    var didWarnAboutStateAssignmentForComponent = {};
  
    var warnOnInvalidCallback = function (callback, callerName) {
      warning(callback === null || typeof callback === 'function', '%s(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callerName, callback);
    };
  
    // This is so gross but it's at least non-critical and can be removed if
    // it causes problems. This is meant to give a nicer error message for
    // ReactDOM15.unstable_renderSubtreeIntoContainer(reactDOM16Component,
    // ...)) which otherwise throws a "_processChildContext is not a function"
    // exception.
    Object.defineProperty(fakeInternalInstance, '_processChildContext', {
      enumerable: false,
      value: function () {
        invariant(false, '_processChildContext is not available in React 16+. This likely means you have multiple copies of React and are attempting to nest a React 15 tree inside a React 16 tree using unstable_renderSubtreeIntoContainer, which isn\'t supported. Try to make sure you have only one copy of React (and ideally, switch to ReactDOM.createPortal).');
      }
    });
    Object.freeze(fakeInternalInstance);
  }
  
  var ReactFiberClassComponent = function (scheduleWork, computeExpirationForFiber, memoizeProps, memoizeState) {
    // Class component state updater
    var updater = {
      isMounted: isMounted,
      enqueueSetState: function (instance, partialState, callback) {
        var fiber = get(instance);
        callback = callback === undefined ? null : callback;
        {
          warnOnInvalidCallback(callback, 'setState');
        }
        var expirationTime = computeExpirationForFiber(fiber);
        var update = {
          expirationTime: expirationTime,
          partialState: partialState,
          callback: callback,
          isReplace: false,
          isForced: false,
          nextCallback: null,
          next: null
        };
        insertUpdateIntoFiber(fiber, update);
        scheduleWork(fiber, expirationTime);
      },
      enqueueReplaceState: function (instance, state, callback) {
        var fiber = get(instance);
        callback = callback === undefined ? null : callback;
        {
          warnOnInvalidCallback(callback, 'replaceState');
        }
        var expirationTime = computeExpirationForFiber(fiber);
        var update = {
          expirationTime: expirationTime,
          partialState: state,
          callback: callback,
          isReplace: true,
          isForced: false,
          nextCallback: null,
          next: null
        };
        insertUpdateIntoFiber(fiber, update);
        scheduleWork(fiber, expirationTime);
      },
      enqueueForceUpdate: function (instance, callback) {
        var fiber = get(instance);
        callback = callback === undefined ? null : callback;
        {
          warnOnInvalidCallback(callback, 'forceUpdate');
        }
        var expirationTime = computeExpirationForFiber(fiber);
        var update = {
          expirationTime: expirationTime,
          partialState: null,
          callback: callback,
          isReplace: false,
          isForced: true,
          nextCallback: null,
          next: null
        };
        insertUpdateIntoFiber(fiber, update);
        scheduleWork(fiber, expirationTime);
      }
    };
  
    function checkShouldComponentUpdate(workInProgress, oldProps, newProps, oldState, newState, newContext) {
      if (oldProps === null || workInProgress.updateQueue !== null && workInProgress.updateQueue.hasForceUpdate) {
        // If the workInProgress already has an Update effect, return true
        return true;
      }
  
      var instance = workInProgress.stateNode;
      var type = workInProgress.type;
      if (typeof instance.shouldComponentUpdate === 'function') {
        startPhaseTimer(workInProgress, 'shouldComponentUpdate');
        var shouldUpdate = instance.shouldComponentUpdate(newProps, newState, newContext);
        stopPhaseTimer();
  
        // Simulate an async bailout/interruption by invoking lifecycle twice.
        if (debugRenderPhaseSideEffects) {
          instance.shouldComponentUpdate(newProps, newState, newContext);
        }
  
        {
          warning(shouldUpdate !== undefined, '%s.shouldComponentUpdate(): Returned undefined instead of a ' + 'boolean value. Make sure to return true or false.', getComponentName(workInProgress) || 'Unknown');
        }
  
        return shouldUpdate;
      }
  
      if (type.prototype && type.prototype.isPureReactComponent) {
        return !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState);
      }
  
      return true;
    }
  
    function checkClassInstance(workInProgress) {
      var instance = workInProgress.stateNode;
      var type = workInProgress.type;
      {
        var name = getComponentName(workInProgress);
        var renderPresent = instance.render;
  
        if (!renderPresent) {
          if (type.prototype && typeof type.prototype.render === 'function') {
            warning(false, '%s(...): No `render` method found on the returned component ' + 'instance: did you accidentally return an object from the constructor?', name);
          } else {
            warning(false, '%s(...): No `render` method found on the returned component ' + 'instance: you may have forgotten to define `render`.', name);
          }
        }
  
        var noGetInitialStateOnES6 = !instance.getInitialState || instance.getInitialState.isReactClassApproved || instance.state;
        warning(noGetInitialStateOnES6, 'getInitialState was defined on %s, a plain JavaScript class. ' + 'This is only supported for classes created using React.createClass. ' + 'Did you mean to define a state property instead?', name);
        var noGetDefaultPropsOnES6 = !instance.getDefaultProps || instance.getDefaultProps.isReactClassApproved;
        warning(noGetDefaultPropsOnES6, 'getDefaultProps was defined on %s, a plain JavaScript class. ' + 'This is only supported for classes created using React.createClass. ' + 'Use a static property to define defaultProps instead.', name);
        var noInstancePropTypes = !instance.propTypes;
        warning(noInstancePropTypes, 'propTypes was defined as an instance property on %s. Use a static ' + 'property to define propTypes instead.', name);
        var noInstanceContextTypes = !instance.contextTypes;
        warning(noInstanceContextTypes, 'contextTypes was defined as an instance property on %s. Use a static ' + 'property to define contextTypes instead.', name);
        var noComponentShouldUpdate = typeof instance.componentShouldUpdate !== 'function';
        warning(noComponentShouldUpdate, '%s has a method called ' + 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + 'The name is phrased as a question because the function is ' + 'expected to return a value.', name);
        if (type.prototype && type.prototype.isPureReactComponent && typeof instance.shouldComponentUpdate !== 'undefined') {
          warning(false, '%s has a method called shouldComponentUpdate(). ' + 'shouldComponentUpdate should not be used when extending React.PureComponent. ' + 'Please extend React.Component if shouldComponentUpdate is used.', getComponentName(workInProgress) || 'A pure component');
        }
        var noComponentDidUnmount = typeof instance.componentDidUnmount !== 'function';
        warning(noComponentDidUnmount, '%s has a method called ' + 'componentDidUnmount(). But there is no such lifecycle method. ' + 'Did you mean componentWillUnmount()?', name);
        var noComponentDidReceiveProps = typeof instance.componentDidReceiveProps !== 'function';
        warning(noComponentDidReceiveProps, '%s has a method called ' + 'componentDidReceiveProps(). But there is no such lifecycle method. ' + 'If you meant to update the state in response to changing props, ' + 'use componentWillReceiveProps(). If you meant to fetch data or ' + 'run side-effects or mutations after React has updated the UI, use componentDidUpdate().', name);
        var noComponentWillRecieveProps = typeof instance.componentWillRecieveProps !== 'function';
        warning(noComponentWillRecieveProps, '%s has a method called ' + 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?', name);
        var hasMutatedProps = instance.props !== workInProgress.pendingProps;
        warning(instance.props === undefined || !hasMutatedProps, '%s(...): When calling super() in `%s`, make sure to pass ' + "up the same props that your component's constructor was passed.", name, name);
        var noInstanceDefaultProps = !instance.defaultProps;
        warning(noInstanceDefaultProps, 'Setting defaultProps as an instance property on %s is not supported and will be ignored.' + ' Instead, define defaultProps as a static property on %s.', name, name);
      }
  
      var state = instance.state;
      if (state && (typeof state !== 'object' || isArray(state))) {
        warning(false, '%s.state: must be set to an object or null', getComponentName(workInProgress));
      }
      if (typeof instance.getChildContext === 'function') {
        warning(typeof workInProgress.type.childContextTypes === 'object', '%s.getChildContext(): childContextTypes must be defined in order to ' + 'use getChildContext().', getComponentName(workInProgress));
      }
    }
  
    function resetInputPointers(workInProgress, instance) {
      instance.props = workInProgress.memoizedProps;
      instance.state = workInProgress.memoizedState;
    }
  
    function adoptClassInstance(workInProgress, instance) {
      instance.updater = updater;
      workInProgress.stateNode = instance;
      // The instance needs access to the fiber so that it can schedule updates
      set(instance, workInProgress);
      {
        instance._reactInternalInstance = fakeInternalInstance;
      }
    }
  
    function constructClassInstance(workInProgress, props) {
      var ctor = workInProgress.type;
      var unmaskedContext = getUnmaskedContext(workInProgress);
      var needsContext = isContextConsumer(workInProgress);
      var context = needsContext ? getMaskedContext(workInProgress, unmaskedContext) : emptyObject;
      var instance = new ctor(props, context);
      adoptClassInstance(workInProgress, instance);
  
      // Cache unmasked context so we can avoid recreating masked context unless necessary.
      // ReactFiberContext usually updates this cache but can't for newly-created instances.
      if (needsContext) {
        cacheContext(workInProgress, unmaskedContext, context);
      }
  
      return instance;
    }
  
    function callComponentWillMount(workInProgress, instance) {
      startPhaseTimer(workInProgress, 'componentWillMount');
      var oldState = instance.state;
      instance.componentWillMount();
      stopPhaseTimer();
  
      // Simulate an async bailout/interruption by invoking lifecycle twice.
      if (debugRenderPhaseSideEffects) {
        instance.componentWillMount();
      }
  
      if (oldState !== instance.state) {
        {
          warning(false, '%s.componentWillMount(): Assigning directly to this.state is ' + "deprecated (except inside a component's " + 'constructor). Use setState instead.', getComponentName(workInProgress));
        }
        updater.enqueueReplaceState(instance, instance.state, null);
      }
    }
  
    function callComponentWillReceiveProps(workInProgress, instance, newProps, newContext) {
      startPhaseTimer(workInProgress, 'componentWillReceiveProps');
      var oldState = instance.state;
      instance.componentWillReceiveProps(newProps, newContext);
      stopPhaseTimer();
  
      // Simulate an async bailout/interruption by invoking lifecycle twice.
      if (debugRenderPhaseSideEffects) {
        instance.componentWillReceiveProps(newProps, newContext);
      }
  
      if (instance.state !== oldState) {
        {
          var componentName = getComponentName(workInProgress) || 'Component';
          if (!didWarnAboutStateAssignmentForComponent[componentName]) {
            warning(false, '%s.componentWillReceiveProps(): Assigning directly to ' + "this.state is deprecated (except inside a component's " + 'constructor). Use setState instead.', componentName);
            didWarnAboutStateAssignmentForComponent[componentName] = true;
          }
        }
        updater.enqueueReplaceState(instance, instance.state, null);
      }
    }
  
    // Invokes the mount life-cycles on a previously never rendered instance.
    function mountClassInstance(workInProgress, renderExpirationTime) {
      var current = workInProgress.alternate;
  
      {
        checkClassInstance(workInProgress);
      }
  
      var instance = workInProgress.stateNode;
      var state = instance.state || null;
  
      var props = workInProgress.pendingProps;
      !props ? invariant(false, 'There must be pending props for an initial mount. This error is likely caused by a bug in React. Please file an issue.') : void 0;
  
      var unmaskedContext = getUnmaskedContext(workInProgress);
  
      instance.props = props;
      instance.state = workInProgress.memoizedState = state;
      instance.refs = emptyObject;
      instance.context = getMaskedContext(workInProgress, unmaskedContext);
  
      if (enableAsyncSubtreeAPI && workInProgress.type != null && workInProgress.type.prototype != null && workInProgress.type.prototype.unstable_isAsyncReactComponent === true) {
        workInProgress.internalContextTag |= AsyncUpdates;
      }
  
      if (typeof instance.componentWillMount === 'function') {
        callComponentWillMount(workInProgress, instance);
        // If we had additional state updates during this life-cycle, let's
        // process them now.
        var updateQueue = workInProgress.updateQueue;
        if (updateQueue !== null) {
          instance.state = processUpdateQueue(current, workInProgress, updateQueue, instance, props, renderExpirationTime);
        }
      }
      if (typeof instance.componentDidMount === 'function') {
        workInProgress.effectTag |= Update;
      }
    }
  
    // Called on a preexisting class instance. Returns false if a resumed render
    // could be reused.
    // function resumeMountClassInstance(
    //   workInProgress: Fiber,
    //   priorityLevel: PriorityLevel,
    // ): boolean {
    //   const instance = workInProgress.stateNode;
    //   resetInputPointers(workInProgress, instance);
  
    //   let newState = workInProgress.memoizedState;
    //   let newProps = workInProgress.pendingProps;
    //   if (!newProps) {
    //     // If there isn't any new props, then we'll reuse the memoized props.
    //     // This could be from already completed work.
    //     newProps = workInProgress.memoizedProps;
    //     invariant(
    //       newProps != null,
    //       'There should always be pending or memoized props. This error is ' +
    //         'likely caused by a bug in React. Please file an issue.',
    //     );
    //   }
    //   const newUnmaskedContext = getUnmaskedContext(workInProgress);
    //   const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
  
    //   const oldContext = instance.context;
    //   const oldProps = workInProgress.memoizedProps;
  
    //   if (
    //     typeof instance.componentWillReceiveProps === 'function' &&
    //     (oldProps !== newProps || oldContext !== newContext)
    //   ) {
    //     callComponentWillReceiveProps(
    //       workInProgress,
    //       instance,
    //       newProps,
    //       newContext,
    //     );
    //   }
  
    //   // Process the update queue before calling shouldComponentUpdate
    //   const updateQueue = workInProgress.updateQueue;
    //   if (updateQueue !== null) {
    //     newState = processUpdateQueue(
    //       workInProgress,
    //       updateQueue,
    //       instance,
    //       newState,
    //       newProps,
    //       priorityLevel,
    //     );
    //   }
  
    //   // TODO: Should we deal with a setState that happened after the last
    //   // componentWillMount and before this componentWillMount? Probably
    //   // unsupported anyway.
  
    //   if (
    //     !checkShouldComponentUpdate(
    //       workInProgress,
    //       workInProgress.memoizedProps,
    //       newProps,
    //       workInProgress.memoizedState,
    //       newState,
    //       newContext,
    //     )
    //   ) {
    //     // Update the existing instance's state, props, and context pointers even
    //     // though we're bailing out.
    //     instance.props = newProps;
    //     instance.state = newState;
    //     instance.context = newContext;
    //     return false;
    //   }
  
    //   // Update the input pointers now so that they are correct when we call
    //   // componentWillMount
    //   instance.props = newProps;
    //   instance.state = newState;
    //   instance.context = newContext;
  
    //   if (typeof instance.componentWillMount === 'function') {
    //     callComponentWillMount(workInProgress, instance);
    //     // componentWillMount may have called setState. Process the update queue.
    //     const newUpdateQueue = workInProgress.updateQueue;
    //     if (newUpdateQueue !== null) {
    //       newState = processUpdateQueue(
    //         workInProgress,
    //         newUpdateQueue,
    //         instance,
    //         newState,
    //         newProps,
    //         priorityLevel,
    //       );
    //     }
    //   }
  
    //   if (typeof instance.componentDidMount === 'function') {
    //     workInProgress.effectTag |= Update;
    //   }
  
    //   instance.state = newState;
  
    //   return true;
    // }
  
    // Invokes the update life-cycles and returns false if it shouldn't rerender.
    function updateClassInstance(current, workInProgress, renderExpirationTime) {
      var instance = workInProgress.stateNode;
      resetInputPointers(workInProgress, instance);
  
      var oldProps = workInProgress.memoizedProps;
      var newProps = workInProgress.pendingProps;
      if (!newProps) {
        // If there aren't any new props, then we'll reuse the memoized props.
        // This could be from already completed work.
        newProps = oldProps;
        !(newProps != null) ? invariant(false, 'There should always be pending or memoized props. This error is likely caused by a bug in React. Please file an issue.') : void 0;
      }
      var oldContext = instance.context;
      var newUnmaskedContext = getUnmaskedContext(workInProgress);
      var newContext = getMaskedContext(workInProgress, newUnmaskedContext);
  
      // Note: During these life-cycles, instance.props/instance.state are what
      // ever the previously attempted to render - not the "current". However,
      // during componentDidUpdate we pass the "current" props.
  
      if (typeof instance.componentWillReceiveProps === 'function' && (oldProps !== newProps || oldContext !== newContext)) {
        callComponentWillReceiveProps(workInProgress, instance, newProps, newContext);
      }
  
      // Compute the next state using the memoized state and the update queue.
      var oldState = workInProgress.memoizedState;
      // TODO: Previous state can be null.
      var newState = void 0;
      if (workInProgress.updateQueue !== null) {
        newState = processUpdateQueue(current, workInProgress, workInProgress.updateQueue, instance, newProps, renderExpirationTime);
      } else {
        newState = oldState;
      }
  
      if (oldProps === newProps && oldState === newState && !hasContextChanged() && !(workInProgress.updateQueue !== null && workInProgress.updateQueue.hasForceUpdate)) {
        // If an update was already in progress, we should schedule an Update
        // effect even though we're bailing out, so that cWU/cDU are called.
        if (typeof instance.componentDidUpdate === 'function') {
          if (oldProps !== current.memoizedProps || oldState !== current.memoizedState) {
            workInProgress.effectTag |= Update;
          }
        }
        return false;
      }
  
      var shouldUpdate = checkShouldComponentUpdate(workInProgress, oldProps, newProps, oldState, newState, newContext);
  
      if (shouldUpdate) {
        if (typeof instance.componentWillUpdate === 'function') {
          startPhaseTimer(workInProgress, 'componentWillUpdate');
          instance.componentWillUpdate(newProps, newState, newContext);
          stopPhaseTimer();
  
          // Simulate an async bailout/interruption by invoking lifecycle twice.
          if (debugRenderPhaseSideEffects) {
            instance.componentWillUpdate(newProps, newState, newContext);
          }
        }
        if (typeof instance.componentDidUpdate === 'function') {
          workInProgress.effectTag |= Update;
        }
      } else {
        // If an update was already in progress, we should schedule an Update
        // effect even though we're bailing out, so that cWU/cDU are called.
        if (typeof instance.componentDidUpdate === 'function') {
          if (oldProps !== current.memoizedProps || oldState !== current.memoizedState) {
            workInProgress.effectTag |= Update;
          }
        }
  
        // If shouldComponentUpdate returned false, we should still update the
        // memoized props/state to indicate that this work can be reused.
        memoizeProps(workInProgress, newProps);
        memoizeState(workInProgress, newState);
      }
  
      // Update the existing instance's state, props, and context pointers even
      // if shouldComponentUpdate returns false.
      instance.props = newProps;
      instance.state = newState;
      instance.context = newContext;
  
      return shouldUpdate;
    }
  
    return {
      adoptClassInstance: adoptClassInstance,
      constructClassInstance: constructClassInstance,
      mountClassInstance: mountClassInstance,
      // resumeMountClassInstance,
      updateClassInstance: updateClassInstance
    };
  };
  
  // The Symbol used to tag the ReactElement-like types. If there is no native Symbol
  // nor polyfill, then a plain number is used for performance.
  var hasSymbol = typeof Symbol === 'function' && Symbol['for'];
  
  var REACT_ELEMENT_TYPE = hasSymbol ? Symbol['for']('react.element') : 0xeac7;
  var REACT_CALL_TYPE = hasSymbol ? Symbol['for']('react.call') : 0xeac8;
  var REACT_RETURN_TYPE = hasSymbol ? Symbol['for']('react.return') : 0xeac9;
  var REACT_PORTAL_TYPE = hasSymbol ? Symbol['for']('react.portal') : 0xeaca;
  var REACT_FRAGMENT_TYPE = hasSymbol ? Symbol['for']('react.fragment') : 0xeacb;
  
  var MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
  var FAUX_ITERATOR_SYMBOL = '@@iterator';
  
  function getIteratorFn(maybeIterable) {
    if (maybeIterable === null || typeof maybeIterable === 'undefined') {
      return null;
    }
    var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
    if (typeof maybeIterator === 'function') {
      return maybeIterator;
    }
    return null;
  }
  
  var getCurrentFiberStackAddendum$1 = ReactDebugCurrentFiber.getCurrentFiberStackAddendum;
  
  
  {
    var didWarnAboutMaps = false;
    /**
     * Warn if there's no key explicitly set on dynamic arrays of children or
     * object keys are not valid. This allows us to keep track of children between
     * updates.
     */
    var ownerHasKeyUseWarning = {};
    var ownerHasFunctionTypeWarning = {};
  
    var warnForMissingKey = function (child) {
      if (child === null || typeof child !== 'object') {
        return;
      }
      if (!child._store || child._store.validated || child.key != null) {
        return;
      }
      !(typeof child._store === 'object') ? invariant(false, 'React Component in warnForMissingKey should have a _store. This error is likely caused by a bug in React. Please file an issue.') : void 0;
      child._store.validated = true;
  
      var currentComponentErrorInfo = 'Each child in an array or iterator should have a unique ' + '"key" prop. See https://fb.me/react-warning-keys for ' + 'more information.' + (getCurrentFiberStackAddendum$1() || '');
      if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
        return;
      }
      ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
  
      warning(false, 'Each child in an array or iterator should have a unique ' + '"key" prop. See https://fb.me/react-warning-keys for ' + 'more information.%s', getCurrentFiberStackAddendum$1());
    };
  }
  
  var isArray$1 = Array.isArray;
  
  function coerceRef(current, element) {
    var mixedRef = element.ref;
    if (mixedRef !== null && typeof mixedRef !== 'function') {
      if (element._owner) {
        var owner = element._owner;
        var inst = void 0;
        if (owner) {
          var ownerFiber = owner;
          !(ownerFiber.tag === ClassComponent) ? invariant(false, 'Stateless function components cannot have refs.') : void 0;
          inst = ownerFiber.stateNode;
        }
        !inst ? invariant(false, 'Missing owner for string ref %s. This error is likely caused by a bug in React. Please file an issue.', mixedRef) : void 0;
        var stringRef = '' + mixedRef;
        // Check if previous string ref matches new string ref
        if (current !== null && current.ref !== null && current.ref._stringRef === stringRef) {
          return current.ref;
        }
        var ref = function (value) {
          var refs = inst.refs === emptyObject ? inst.refs = {} : inst.refs;
          if (value === null) {
            delete refs[stringRef];
          } else {
            refs[stringRef] = value;
          }
        };
        ref._stringRef = stringRef;
        return ref;
      } else {
        !(typeof mixedRef === 'string') ? invariant(false, 'Expected ref to be a function or a string.') : void 0;
        !element._owner ? invariant(false, 'Element ref was specified as a string (%s) but no owner was set. You may have multiple copies of React loaded. (details: https://fb.me/react-refs-must-have-owner).', mixedRef) : void 0;
      }
    }
    return mixedRef;
  }
  
  function throwOnInvalidObjectType(returnFiber, newChild) {
    if (returnFiber.type !== 'textarea') {
      var addendum = '';
      {
        addendum = ' If you meant to render a collection of children, use an array ' + 'instead.' + (getCurrentFiberStackAddendum$1() || '');
      }
      invariant(false, 'Objects are not valid as a React child (found: %s).%s', Object.prototype.toString.call(newChild) === '[object Object]' ? 'object with keys {' + Object.keys(newChild).join(', ') + '}' : newChild, addendum);
    }
  }
  
  function warnOnFunctionType() {
    var currentComponentErrorInfo = 'Functions are not valid as a React child. This may happen if ' + 'you return a Component instead of <Component /> from render. ' + 'Or maybe you meant to call this function rather than return it.' + (getCurrentFiberStackAddendum$1() || '');
  
    if (ownerHasFunctionTypeWarning[currentComponentErrorInfo]) {
      return;
    }
    ownerHasFunctionTypeWarning[currentComponentErrorInfo] = true;
  
    warning(false, 'Functions are not valid as a React child. This may happen if ' + 'you return a Component instead of <Component /> from render. ' + 'Or maybe you meant to call this function rather than return it.%s', getCurrentFiberStackAddendum$1() || '');
  }
  
  // This wrapper function exists because I expect to clone the code in each path
  // to be able to optimize each path individually by branching early. This needs
  // a compiler or we can do it manually. Helpers that don't need this branching
  // live outside of this function.
  function ChildReconciler(shouldTrackSideEffects) {
    function deleteChild(returnFiber, childToDelete) {
      if (!shouldTrackSideEffects) {
        // Noop.
        return;
      }
      // Deletions are added in reversed order so we add it to the front.
      // At this point, the return fiber's effect list is empty except for
      // deletions, so we can just append the deletion to the list. The remaining
      // effects aren't added until the complete phase. Once we implement
      // resuming, this may not be true.
      var last = returnFiber.lastEffect;
      if (last !== null) {
        last.nextEffect = childToDelete;
        returnFiber.lastEffect = childToDelete;
      } else {
        returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
      }
      childToDelete.nextEffect = null;
      childToDelete.effectTag = Deletion;
    }
  
    function deleteRemainingChildren(returnFiber, currentFirstChild) {
      if (!shouldTrackSideEffects) {
        // Noop.
        return null;
      }
  
      // TODO: For the shouldClone case, this could be micro-optimized a bit by
      // assuming that after the first child we've already added everything.
      var childToDelete = currentFirstChild;
      while (childToDelete !== null) {
        deleteChild(returnFiber, childToDelete);
        childToDelete = childToDelete.sibling;
      }
      return null;
    }
  
    function mapRemainingChildren(returnFiber, currentFirstChild) {
      // Add the remaining children to a temporary map so that we can find them by
      // keys quickly. Implicit (null) keys get added to this set with their index
      var existingChildren = new Map();
  
      var existingChild = currentFirstChild;
      while (existingChild !== null) {
        if (existingChild.key !== null) {
          existingChildren.set(existingChild.key, existingChild);
        } else {
          existingChildren.set(existingChild.index, existingChild);
        }
        existingChild = existingChild.sibling;
      }
      return existingChildren;
    }
  
    function useFiber(fiber, pendingProps, expirationTime) {
      // We currently set sibling to null and index to 0 here because it is easy
      // to forget to do before returning it. E.g. for the single child case.
      var clone = createWorkInProgress(fiber, pendingProps, expirationTime);
      clone.index = 0;
      clone.sibling = null;
      return clone;
    }
  
    function placeChild(newFiber, lastPlacedIndex, newIndex) {
      newFiber.index = newIndex;
      if (!shouldTrackSideEffects) {
        // Noop.
        return lastPlacedIndex;
      }
      var current = newFiber.alternate;
      if (current !== null) {
        var oldIndex = current.index;
        if (oldIndex < lastPlacedIndex) {
          // This is a move.
          newFiber.effectTag = Placement;
          return lastPlacedIndex;
        } else {
          // This item can stay in place.
          return oldIndex;
        }
      } else {
        // This is an insertion.
        newFiber.effectTag = Placement;
        return lastPlacedIndex;
      }
    }
  
    function placeSingleChild(newFiber) {
      // This is simpler for the single child case. We only need to do a
      // placement for inserting new children.
      if (shouldTrackSideEffects && newFiber.alternate === null) {
        newFiber.effectTag = Placement;
      }
      return newFiber;
    }
  
    function updateTextNode(returnFiber, current, textContent, expirationTime) {
      if (current === null || current.tag !== HostText) {
        // Insert
        var created = createFiberFromText(textContent, returnFiber.internalContextTag, expirationTime);
        created['return'] = returnFiber;
        return created;
      } else {
        // Update
        var existing = useFiber(current, textContent, expirationTime);
        existing['return'] = returnFiber;
        return existing;
      }
    }
  
    function updateElement(returnFiber, current, element, expirationTime) {
      if (current !== null && current.type === element.type) {
        // Move based on index
        var existing = useFiber(current, element.props, expirationTime);
        existing.ref = coerceRef(current, element);
        existing['return'] = returnFiber;
        {
          existing._debugSource = element._source;
          existing._debugOwner = element._owner;
        }
        return existing;
      } else {
        // Insert
        var created = createFiberFromElement(element, returnFiber.internalContextTag, expirationTime);
        created.ref = coerceRef(current, element);
        created['return'] = returnFiber;
        return created;
      }
    }
  
    function updateCall(returnFiber, current, call, expirationTime) {
      // TODO: Should this also compare handler to determine whether to reuse?
      if (current === null || current.tag !== CallComponent) {
        // Insert
        var created = createFiberFromCall(call, returnFiber.internalContextTag, expirationTime);
        created['return'] = returnFiber;
        return created;
      } else {
        // Move based on index
        var existing = useFiber(current, call, expirationTime);
        existing['return'] = returnFiber;
        return existing;
      }
    }
  
    function updateReturn(returnFiber, current, returnNode, expirationTime) {
      if (current === null || current.tag !== ReturnComponent) {
        // Insert
        var created = createFiberFromReturn(returnNode, returnFiber.internalContextTag, expirationTime);
        created.type = returnNode.value;
        created['return'] = returnFiber;
        return created;
      } else {
        // Move based on index
        var existing = useFiber(current, null, expirationTime);
        existing.type = returnNode.value;
        existing['return'] = returnFiber;
        return existing;
      }
    }
  
    function updatePortal(returnFiber, current, portal, expirationTime) {
      if (current === null || current.tag !== HostPortal || current.stateNode.containerInfo !== portal.containerInfo || current.stateNode.implementation !== portal.implementation) {
        // Insert
        var created = createFiberFromPortal(portal, returnFiber.internalContextTag, expirationTime);
        created['return'] = returnFiber;
        return created;
      } else {
        // Update
        var existing = useFiber(current, portal.children || [], expirationTime);
        existing['return'] = returnFiber;
        return existing;
      }
    }
  
    function updateFragment(returnFiber, current, fragment, expirationTime, key) {
      if (current === null || current.tag !== Fragment) {
        // Insert
        var created = createFiberFromFragment(fragment, returnFiber.internalContextTag, expirationTime, key);
        created['return'] = returnFiber;
        return created;
      } else {
        // Update
        var existing = useFiber(current, fragment, expirationTime);
        existing['return'] = returnFiber;
        return existing;
      }
    }
  
    function createChild(returnFiber, newChild, expirationTime) {
      if (typeof newChild === 'string' || typeof newChild === 'number') {
        // Text nodes don't have keys. If the previous node is implicitly keyed
        // we can continue to replace it without aborting even if it is not a text
        // node.
        var created = createFiberFromText('' + newChild, returnFiber.internalContextTag, expirationTime);
        created['return'] = returnFiber;
        return created;
      }
  
      if (typeof newChild === 'object' && newChild !== null) {
        switch (newChild.$$typeof) {
          case REACT_ELEMENT_TYPE:
            {
              if (newChild.type === REACT_FRAGMENT_TYPE) {
                var _created = createFiberFromFragment(newChild.props.children, returnFiber.internalContextTag, expirationTime, newChild.key);
                _created['return'] = returnFiber;
                return _created;
              } else {
                var _created2 = createFiberFromElement(newChild, returnFiber.internalContextTag, expirationTime);
                _created2.ref = coerceRef(null, newChild);
                _created2['return'] = returnFiber;
                return _created2;
              }
            }
  
          case REACT_CALL_TYPE:
            {
              var _created3 = createFiberFromCall(newChild, returnFiber.internalContextTag, expirationTime);
              _created3['return'] = returnFiber;
              return _created3;
            }
  
          case REACT_RETURN_TYPE:
            {
              var _created4 = createFiberFromReturn(newChild, returnFiber.internalContextTag, expirationTime);
              _created4.type = newChild.value;
              _created4['return'] = returnFiber;
              return _created4;
            }
  
          case REACT_PORTAL_TYPE:
            {
              var _created5 = createFiberFromPortal(newChild, returnFiber.internalContextTag, expirationTime);
              _created5['return'] = returnFiber;
              return _created5;
            }
        }
  
        if (isArray$1(newChild) || getIteratorFn(newChild)) {
          var _created6 = createFiberFromFragment(newChild, returnFiber.internalContextTag, expirationTime, null);
          _created6['return'] = returnFiber;
          return _created6;
        }
  
        throwOnInvalidObjectType(returnFiber, newChild);
      }
  
      {
        if (typeof newChild === 'function') {
          warnOnFunctionType();
        }
      }
  
      return null;
    }
  
    function updateSlot(returnFiber, oldFiber, newChild, expirationTime) {
      // Update the fiber if the keys match, otherwise return null.
  
      var key = oldFiber !== null ? oldFiber.key : null;
  
      if (typeof newChild === 'string' || typeof newChild === 'number') {
        // Text nodes don't have keys. If the previous node is implicitly keyed
        // we can continue to replace it without aborting even if it is not a text
        // node.
        if (key !== null) {
          return null;
        }
        return updateTextNode(returnFiber, oldFiber, '' + newChild, expirationTime);
      }
  
      if (typeof newChild === 'object' && newChild !== null) {
        switch (newChild.$$typeof) {
          case REACT_ELEMENT_TYPE:
            {
              if (newChild.key === key) {
                if (newChild.type === REACT_FRAGMENT_TYPE) {
                  return updateFragment(returnFiber, oldFiber, newChild.props.children, expirationTime, key);
                }
                return updateElement(returnFiber, oldFiber, newChild, expirationTime);
              } else {
                return null;
              }
            }
  
          case REACT_CALL_TYPE:
            {
              if (newChild.key === key) {
                return updateCall(returnFiber, oldFiber, newChild, expirationTime);
              } else {
                return null;
              }
            }
  
          case REACT_RETURN_TYPE:
            {
              // Returns don't have keys. If the previous node is implicitly keyed
              // we can continue to replace it without aborting even if it is not a
              // yield.
              if (key === null) {
                return updateReturn(returnFiber, oldFiber, newChild, expirationTime);
              } else {
                return null;
              }
            }
  
          case REACT_PORTAL_TYPE:
            {
              if (newChild.key === key) {
                return updatePortal(returnFiber, oldFiber, newChild, expirationTime);
              } else {
                return null;
              }
            }
        }
  
        if (isArray$1(newChild) || getIteratorFn(newChild)) {
          if (key !== null) {
            return null;
          }
  
          return updateFragment(returnFiber, oldFiber, newChild, expirationTime, null);
        }
  
        throwOnInvalidObjectType(returnFiber, newChild);
      }
  
      {
        if (typeof newChild === 'function') {
          warnOnFunctionType();
        }
      }
  
      return null;
    }
  
    function updateFromMap(existingChildren, returnFiber, newIdx, newChild, expirationTime) {
      if (typeof newChild === 'string' || typeof newChild === 'number') {
        // Text nodes don't have keys, so we neither have to check the old nor
        // new node for the key. If both are text nodes, they match.
        var matchedFiber = existingChildren.get(newIdx) || null;
        return updateTextNode(returnFiber, matchedFiber, '' + newChild, expirationTime);
      }
  
      if (typeof newChild === 'object' && newChild !== null) {
        switch (newChild.$$typeof) {
          case REACT_ELEMENT_TYPE:
            {
              var _matchedFiber = existingChildren.get(newChild.key === null ? newIdx : newChild.key) || null;
              if (newChild.type === REACT_FRAGMENT_TYPE) {
                return updateFragment(returnFiber, _matchedFiber, newChild.props.children, expirationTime, newChild.key);
              }
              return updateElement(returnFiber, _matchedFiber, newChild, expirationTime);
            }
  
          case REACT_CALL_TYPE:
            {
              var _matchedFiber2 = existingChildren.get(newChild.key === null ? newIdx : newChild.key) || null;
              return updateCall(returnFiber, _matchedFiber2, newChild, expirationTime);
            }
  
          case REACT_RETURN_TYPE:
            {
              // Returns don't have keys, so we neither have to check the old nor
              // new node for the key. If both are returns, they match.
              var _matchedFiber3 = existingChildren.get(newIdx) || null;
              return updateReturn(returnFiber, _matchedFiber3, newChild, expirationTime);
            }
  
          case REACT_PORTAL_TYPE:
            {
              var _matchedFiber4 = existingChildren.get(newChild.key === null ? newIdx : newChild.key) || null;
              return updatePortal(returnFiber, _matchedFiber4, newChild, expirationTime);
            }
        }
  
        if (isArray$1(newChild) || getIteratorFn(newChild)) {
          var _matchedFiber5 = existingChildren.get(newIdx) || null;
          return updateFragment(returnFiber, _matchedFiber5, newChild, expirationTime, null);
        }
  
        throwOnInvalidObjectType(returnFiber, newChild);
      }
  
      {
        if (typeof newChild === 'function') {
          warnOnFunctionType();
        }
      }
  
      return null;
    }
  
    /**
     * Warns if there is a duplicate or missing key
     */
    function warnOnInvalidKey(child, knownKeys) {
      {
        if (typeof child !== 'object' || child === null) {
          return knownKeys;
        }
        switch (child.$$typeof) {
          case REACT_ELEMENT_TYPE:
          case REACT_CALL_TYPE:
          case REACT_PORTAL_TYPE:
            warnForMissingKey(child);
            var key = child.key;
            if (typeof key !== 'string') {
              break;
            }
            if (knownKeys === null) {
              knownKeys = new Set();
              knownKeys.add(key);
              break;
            }
            if (!knownKeys.has(key)) {
              knownKeys.add(key);
              break;
            }
            warning(false, 'Encountered two children with the same key, `%s`. ' + 'Keys should be unique so that components maintain their identity ' + 'across updates. Non-unique keys may cause children to be ' + 'duplicated and/or omitted — the behavior is unsupported and ' + 'could change in a future version.%s', key, getCurrentFiberStackAddendum$1());
            break;
          default:
            break;
        }
      }
      return knownKeys;
    }
  
    function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren, expirationTime) {
      // This algorithm can't optimize by searching from boths ends since we
      // don't have backpointers on fibers. I'm trying to see how far we can get
      // with that model. If it ends up not being worth the tradeoffs, we can
      // add it later.
  
      // Even with a two ended optimization, we'd want to optimize for the case
      // where there are few changes and brute force the comparison instead of
      // going for the Map. It'd like to explore hitting that path first in
      // forward-only mode and only go for the Map once we notice that we need
      // lots of look ahead. This doesn't handle reversal as well as two ended
      // search but that's unusual. Besides, for the two ended optimization to
      // work on Iterables, we'd need to copy the whole set.
  
      // In this first iteration, we'll just live with hitting the bad case
      // (adding everything to a Map) in for every insert/move.
  
      // If you change this code, also update reconcileChildrenIterator() which
      // uses the same algorithm.
  
      {
        // First, validate keys.
        var knownKeys = null;
        for (var i = 0; i < newChildren.length; i++) {
          var child = newChildren[i];
          knownKeys = warnOnInvalidKey(child, knownKeys);
        }
      }
  
      var resultingFirstChild = null;
      var previousNewFiber = null;
  
      var oldFiber = currentFirstChild;
      var lastPlacedIndex = 0;
      var newIdx = 0;
      var nextOldFiber = null;
      for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
        if (oldFiber.index > newIdx) {
          nextOldFiber = oldFiber;
          oldFiber = null;
        } else {
          nextOldFiber = oldFiber.sibling;
        }
        var newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx], expirationTime);
        if (newFiber === null) {
          // TODO: This breaks on empty slots like null children. That's
          // unfortunate because it triggers the slow path all the time. We need
          // a better way to communicate whether this was a miss or null,
          // boolean, undefined, etc.
          if (oldFiber === null) {
            oldFiber = nextOldFiber;
          }
          break;
        }
        if (shouldTrackSideEffects) {
          if (oldFiber && newFiber.alternate === null) {
            // We matched the slot, but we didn't reuse the existing fiber, so we
            // need to delete the existing child.
            deleteChild(returnFiber, oldFiber);
          }
        }
        lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
        if (previousNewFiber === null) {
          // TODO: Move out of the loop. This only happens for the first run.
          resultingFirstChild = newFiber;
        } else {
          // TODO: Defer siblings if we're not at the right index for this slot.
          // I.e. if we had null values before, then we want to defer this
          // for each null value. However, we also don't want to call updateSlot
          // with the previous one.
          previousNewFiber.sibling = newFiber;
        }
        previousNewFiber = newFiber;
        oldFiber = nextOldFiber;
      }
  
      if (newIdx === newChildren.length) {
        // We've reached the end of the new children. We can delete the rest.
        deleteRemainingChildren(returnFiber, oldFiber);
        return resultingFirstChild;
      }
  
      if (oldFiber === null) {
        // If we don't have any more existing children we can choose a fast path
        // since the rest will all be insertions.
        for (; newIdx < newChildren.length; newIdx++) {
          var _newFiber = createChild(returnFiber, newChildren[newIdx], expirationTime);
          if (!_newFiber) {
            continue;
          }
          lastPlacedIndex = placeChild(_newFiber, lastPlacedIndex, newIdx);
          if (previousNewFiber === null) {
            // TODO: Move out of the loop. This only happens for the first run.
            resultingFirstChild = _newFiber;
          } else {
            previousNewFiber.sibling = _newFiber;
          }
          previousNewFiber = _newFiber;
        }
        return resultingFirstChild;
      }
  
      // Add all children to a key map for quick lookups.
      var existingChildren = mapRemainingChildren(returnFiber, oldFiber);
  
      // Keep scanning and use the map to restore deleted items as moves.
      for (; newIdx < newChildren.length; newIdx++) {
        var _newFiber2 = updateFromMap(existingChildren, returnFiber, newIdx, newChildren[newIdx], expirationTime);
        if (_newFiber2) {
          if (shouldTrackSideEffects) {
            if (_newFiber2.alternate !== null) {
              // The new fiber is a work in progress, but if there exists a
              // current, that means that we reused the fiber. We need to delete
              // it from the child list so that we don't add it to the deletion
              // list.
              existingChildren['delete'](_newFiber2.key === null ? newIdx : _newFiber2.key);
            }
          }
          lastPlacedIndex = placeChild(_newFiber2, lastPlacedIndex, newIdx);
          if (previousNewFiber === null) {
            resultingFirstChild = _newFiber2;
          } else {
            previousNewFiber.sibling = _newFiber2;
          }
          previousNewFiber = _newFiber2;
        }
      }
  
      if (shouldTrackSideEffects) {
        // Any existing children that weren't consumed above were deleted. We need
        // to add them to the deletion list.
        existingChildren.forEach(function (child) {
          return deleteChild(returnFiber, child);
        });
      }
  
      return resultingFirstChild;
    }
  
    function reconcileChildrenIterator(returnFiber, currentFirstChild, newChildrenIterable, expirationTime) {
      // This is the same implementation as reconcileChildrenArray(),
      // but using the iterator instead.
  
      var iteratorFn = getIteratorFn(newChildrenIterable);
      !(typeof iteratorFn === 'function') ? invariant(false, 'An object is not an iterable. This error is likely caused by a bug in React. Please file an issue.') : void 0;
  
      {
        // Warn about using Maps as children
        if (typeof newChildrenIterable.entries === 'function') {
          var possibleMap = newChildrenIterable;
          if (possibleMap.entries === iteratorFn) {
            warning(didWarnAboutMaps, 'Using Maps as children is unsupported and will likely yield ' + 'unexpected results. Convert it to a sequence/iterable of keyed ' + 'ReactElements instead.%s', getCurrentFiberStackAddendum$1());
            didWarnAboutMaps = true;
          }
        }
  
        // First, validate keys.
        // We'll get a different iterator later for the main pass.
        var _newChildren = iteratorFn.call(newChildrenIterable);
        if (_newChildren) {
          var knownKeys = null;
          var _step = _newChildren.next();
          for (; !_step.done; _step = _newChildren.next()) {
            var child = _step.value;
            knownKeys = warnOnInvalidKey(child, knownKeys);
          }
        }
      }
  
      var newChildren = iteratorFn.call(newChildrenIterable);
      !(newChildren != null) ? invariant(false, 'An iterable object provided no iterator.') : void 0;
  
      var resultingFirstChild = null;
      var previousNewFiber = null;
  
      var oldFiber = currentFirstChild;
      var lastPlacedIndex = 0;
      var newIdx = 0;
      var nextOldFiber = null;
  
      var step = newChildren.next();
      for (; oldFiber !== null && !step.done; newIdx++, step = newChildren.next()) {
        if (oldFiber.index > newIdx) {
          nextOldFiber = oldFiber;
          oldFiber = null;
        } else {
          nextOldFiber = oldFiber.sibling;
        }
        var newFiber = updateSlot(returnFiber, oldFiber, step.value, expirationTime);
        if (newFiber === null) {
          // TODO: This breaks on empty slots like null children. That's
          // unfortunate because it triggers the slow path all the time. We need
          // a better way to communicate whether this was a miss or null,
          // boolean, undefined, etc.
          if (!oldFiber) {
            oldFiber = nextOldFiber;
          }
          break;
        }
        if (shouldTrackSideEffects) {
          if (oldFiber && newFiber.alternate === null) {
            // We matched the slot, but we didn't reuse the existing fiber, so we
            // need to delete the existing child.
            deleteChild(returnFiber, oldFiber);
          }
        }
        lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
        if (previousNewFiber === null) {
          // TODO: Move out of the loop. This only happens for the first run.
          resultingFirstChild = newFiber;
        } else {
          // TODO: Defer siblings if we're not at the right index for this slot.
          // I.e. if we had null values before, then we want to defer this
          // for each null value. However, we also don't want to call updateSlot
          // with the previous one.
          previousNewFiber.sibling = newFiber;
        }
        previousNewFiber = newFiber;
        oldFiber = nextOldFiber;
      }
  
      if (step.done) {
        // We've reached the end of the new children. We can delete the rest.
        deleteRemainingChildren(returnFiber, oldFiber);
        return resultingFirstChild;
      }
  
      if (oldFiber === null) {
        // If we don't have any more existing children we can choose a fast path
        // since the rest will all be insertions.
        for (; !step.done; newIdx++, step = newChildren.next()) {
          var _newFiber3 = createChild(returnFiber, step.value, expirationTime);
          if (_newFiber3 === null) {
            continue;
          }
          lastPlacedIndex = placeChild(_newFiber3, lastPlacedIndex, newIdx);
          if (previousNewFiber === null) {
            // TODO: Move out of the loop. This only happens for the first run.
            resultingFirstChild = _newFiber3;
          } else {
            previousNewFiber.sibling = _newFiber3;
          }
          previousNewFiber = _newFiber3;
        }
        return resultingFirstChild;
      }
  
      // Add all children to a key map for quick lookups.
      var existingChildren = mapRemainingChildren(returnFiber, oldFiber);
  
      // Keep scanning and use the map to restore deleted items as moves.
      for (; !step.done; newIdx++, step = newChildren.next()) {
        var _newFiber4 = updateFromMap(existingChildren, returnFiber, newIdx, step.value, expirationTime);
        if (_newFiber4 !== null) {
          if (shouldTrackSideEffects) {
            if (_newFiber4.alternate !== null) {
              // The new fiber is a work in progress, but if there exists a
              // current, that means that we reused the fiber. We need to delete
              // it from the child list so that we don't add it to the deletion
              // list.
              existingChildren['delete'](_newFiber4.key === null ? newIdx : _newFiber4.key);
            }
          }
          lastPlacedIndex = placeChild(_newFiber4, lastPlacedIndex, newIdx);
          if (previousNewFiber === null) {
            resultingFirstChild = _newFiber4;
          } else {
            previousNewFiber.sibling = _newFiber4;
          }
          previousNewFiber = _newFiber4;
        }
      }
  
      if (shouldTrackSideEffects) {
        // Any existing children that weren't consumed above were deleted. We need
        // to add them to the deletion list.
        existingChildren.forEach(function (child) {
          return deleteChild(returnFiber, child);
        });
      }
  
      return resultingFirstChild;
    }
  
    function reconcileSingleTextNode(returnFiber, currentFirstChild, textContent, expirationTime) {
      // There's no need to check for keys on text nodes since we don't have a
      // way to define them.
      if (currentFirstChild !== null && currentFirstChild.tag === HostText) {
        // We already have an existing node so let's just update it and delete
        // the rest.
        deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
        var existing = useFiber(currentFirstChild, textContent, expirationTime);
        existing['return'] = returnFiber;
        return existing;
      }
      // The existing first child is not a text node so we need to create one
      // and delete the existing ones.
      deleteRemainingChildren(returnFiber, currentFirstChild);
      var created = createFiberFromText(textContent, returnFiber.internalContextTag, expirationTime);
      created['return'] = returnFiber;
      return created;
    }
  
    function reconcileSingleElement(returnFiber, currentFirstChild, element, expirationTime) {
      var key = element.key;
      var child = currentFirstChild;
      while (child !== null) {
        // TODO: If key === null and child.key === null, then this only applies to
        // the first item in the list.
        if (child.key === key) {
          if (child.tag === Fragment ? element.type === REACT_FRAGMENT_TYPE : child.type === element.type) {
            deleteRemainingChildren(returnFiber, child.sibling);
            var existing = useFiber(child, element.type === REACT_FRAGMENT_TYPE ? element.props.children : element.props, expirationTime);
            existing.ref = coerceRef(child, element);
            existing['return'] = returnFiber;
            {
              existing._debugSource = element._source;
              existing._debugOwner = element._owner;
            }
            return existing;
          } else {
            deleteRemainingChildren(returnFiber, child);
            break;
          }
        } else {
          deleteChild(returnFiber, child);
        }
        child = child.sibling;
      }
  
      if (element.type === REACT_FRAGMENT_TYPE) {
        var created = createFiberFromFragment(element.props.children, returnFiber.internalContextTag, expirationTime, element.key);
        created['return'] = returnFiber;
        return created;
      } else {
        var _created7 = createFiberFromElement(element, returnFiber.internalContextTag, expirationTime);
        _created7.ref = coerceRef(currentFirstChild, element);
        _created7['return'] = returnFiber;
        return _created7;
      }
    }
  
    function reconcileSingleCall(returnFiber, currentFirstChild, call, expirationTime) {
      var key = call.key;
      var child = currentFirstChild;
      while (child !== null) {
        // TODO: If key === null and child.key === null, then this only applies to
        // the first item in the list.
        if (child.key === key) {
          if (child.tag === CallComponent) {
            deleteRemainingChildren(returnFiber, child.sibling);
            var existing = useFiber(child, call, expirationTime);
            existing['return'] = returnFiber;
            return existing;
          } else {
            deleteRemainingChildren(returnFiber, child);
            break;
          }
        } else {
          deleteChild(returnFiber, child);
        }
        child = child.sibling;
      }
  
      var created = createFiberFromCall(call, returnFiber.internalContextTag, expirationTime);
      created['return'] = returnFiber;
      return created;
    }
  
    function reconcileSingleReturn(returnFiber, currentFirstChild, returnNode, expirationTime) {
      // There's no need to check for keys on yields since they're stateless.
      var child = currentFirstChild;
      if (child !== null) {
        if (child.tag === ReturnComponent) {
          deleteRemainingChildren(returnFiber, child.sibling);
          var existing = useFiber(child, null, expirationTime);
          existing.type = returnNode.value;
          existing['return'] = returnFiber;
          return existing;
        } else {
          deleteRemainingChildren(returnFiber, child);
        }
      }
  
      var created = createFiberFromReturn(returnNode, returnFiber.internalContextTag, expirationTime);
      created.type = returnNode.value;
      created['return'] = returnFiber;
      return created;
    }
  
    function reconcileSinglePortal(returnFiber, currentFirstChild, portal, expirationTime) {
      var key = portal.key;
      var child = currentFirstChild;
      while (child !== null) {
        // TODO: If key === null and child.key === null, then this only applies to
        // the first item in the list.
        if (child.key === key) {
          if (child.tag === HostPortal && child.stateNode.containerInfo === portal.containerInfo && child.stateNode.implementation === portal.implementation) {
            deleteRemainingChildren(returnFiber, child.sibling);
            var existing = useFiber(child, portal.children || [], expirationTime);
            existing['return'] = returnFiber;
            return existing;
          } else {
            deleteRemainingChildren(returnFiber, child);
            break;
          }
        } else {
          deleteChild(returnFiber, child);
        }
        child = child.sibling;
      }
  
      var created = createFiberFromPortal(portal, returnFiber.internalContextTag, expirationTime);
      created['return'] = returnFiber;
      return created;
    }
  
    // This API will tag the children with the side-effect of the reconciliation
    // itself. They will be added to the side-effect list as we pass through the
    // children and the parent.
    function reconcileChildFibers(returnFiber, currentFirstChild, newChild, expirationTime) {
      // This function is not recursive.
      // If the top level item is an array, we treat it as a set of children,
      // not as a fragment. Nested arrays on the other hand will be treated as
      // fragment nodes. Recursion happens at the normal flow.
  
      // Handle top level unkeyed fragments as if they were arrays.
      // This leads to an ambiguity between <>{[...]}</> and <>...</>.
      // We treat the ambiguous cases above the same.
      if (typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null) {
        newChild = newChild.props.children;
      }
  
      // Handle object types
      var isObject = typeof newChild === 'object' && newChild !== null;
  
      if (isObject) {
        switch (newChild.$$typeof) {
          case REACT_ELEMENT_TYPE:
            return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, expirationTime));
  
          case REACT_CALL_TYPE:
            return placeSingleChild(reconcileSingleCall(returnFiber, currentFirstChild, newChild, expirationTime));
          case REACT_RETURN_TYPE:
            return placeSingleChild(reconcileSingleReturn(returnFiber, currentFirstChild, newChild, expirationTime));
          case REACT_PORTAL_TYPE:
            return placeSingleChild(reconcileSinglePortal(returnFiber, currentFirstChild, newChild, expirationTime));
        }
      }
  
      if (typeof newChild === 'string' || typeof newChild === 'number') {
        return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, '' + newChild, expirationTime));
      }
  
      if (isArray$1(newChild)) {
        return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, expirationTime);
      }
  
      if (getIteratorFn(newChild)) {
        return reconcileChildrenIterator(returnFiber, currentFirstChild, newChild, expirationTime);
      }
  
      if (isObject) {
        throwOnInvalidObjectType(returnFiber, newChild);
      }
  
      {
        if (typeof newChild === 'function') {
          warnOnFunctionType();
        }
      }
      if (typeof newChild === 'undefined') {
        // If the new child is undefined, and the return fiber is a composite
        // component, throw an error. If Fiber return types are disabled,
        // we already threw above.
        switch (returnFiber.tag) {
          case ClassComponent:
            {
              {
                var instance = returnFiber.stateNode;
                if (instance.render._isMockFunction) {
                  // We allow auto-mocks to proceed as if they're returning null.
                  break;
                }
              }
            }
          // Intentionally fall through to the next case, which handles both
          // functions and classes
          // eslint-disable-next-lined no-fallthrough
          case FunctionalComponent:
            {
              var Component = returnFiber.type;
              invariant(false, '%s(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.', Component.displayName || Component.name || 'Component');
            }
        }
      }
  
      // Remaining cases are all treated as empty.
      return deleteRemainingChildren(returnFiber, currentFirstChild);
    }
  
    return reconcileChildFibers;
  }
  
  var reconcileChildFibers = ChildReconciler(true);
  var mountChildFibers = ChildReconciler(false);
  
  function cloneChildFibers(current, workInProgress) {
    !(current === null || workInProgress.child === current.child) ? invariant(false, 'Resuming work not yet implemented.') : void 0;
  
    if (workInProgress.child === null) {
      return;
    }
  
    var currentChild = workInProgress.child;
    var newChild = createWorkInProgress(currentChild, currentChild.pendingProps, currentChild.expirationTime);
    workInProgress.child = newChild;
  
    newChild['return'] = workInProgress;
    while (currentChild.sibling !== null) {
      currentChild = currentChild.sibling;
      newChild = newChild.sibling = createWorkInProgress(currentChild, currentChild.pendingProps, currentChild.expirationTime);
      newChild['return'] = workInProgress;
    }
    newChild.sibling = null;
  }
  
  {
    var warnedAboutStatelessRefs = {};
  }
  
  var ReactFiberBeginWork = function (config, hostContext, hydrationContext, scheduleWork, computeExpirationForFiber) {
    var shouldSetTextContent = config.shouldSetTextContent,
        useSyncScheduling = config.useSyncScheduling,
        shouldDeprioritizeSubtree = config.shouldDeprioritizeSubtree;
    var pushHostContext = hostContext.pushHostContext,
        pushHostContainer = hostContext.pushHostContainer;
    var enterHydrationState = hydrationContext.enterHydrationState,
        resetHydrationState = hydrationContext.resetHydrationState,
        tryToClaimNextHydratableInstance = hydrationContext.tryToClaimNextHydratableInstance;
  
    var _ReactFiberClassCompo = ReactFiberClassComponent(scheduleWork, computeExpirationForFiber, memoizeProps, memoizeState),
        adoptClassInstance = _ReactFiberClassCompo.adoptClassInstance,
        constructClassInstance = _ReactFiberClassCompo.constructClassInstance,
        mountClassInstance = _ReactFiberClassCompo.mountClassInstance,
        updateClassInstance = _ReactFiberClassCompo.updateClassInstance;
  
    // TODO: Remove this and use reconcileChildrenAtExpirationTime directly.
  
  
    function reconcileChildren(current, workInProgress, nextChildren) {
      reconcileChildrenAtExpirationTime(current, workInProgress, nextChildren, workInProgress.expirationTime);
    }
  
    function reconcileChildrenAtExpirationTime(current, workInProgress, nextChildren, renderExpirationTime) {
      if (current === null) {
        // If this is a fresh new component that hasn't been rendered yet, we
        // won't update its child set by applying minimal side-effects. Instead,
        // we will add them all to the child before it gets rendered. That means
        // we can optimize this reconciliation pass by not tracking side-effects.
        workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderExpirationTime);
      } else {
        // If the current child is the same as the work in progress, it means that
        // we haven't yet started any work on these children. Therefore, we use
        // the clone algorithm to create a copy of all the current children.
  
        // If we had any progressed work already, that is invalid at this point so
        // let's throw it out.
        workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderExpirationTime);
      }
    }
  
    function updateFragment(current, workInProgress) {
      var nextChildren = workInProgress.pendingProps;
      if (hasContextChanged()) {
        // Normally we can bail out on props equality but if context has changed
        // we don't do the bailout and we have to reuse existing props instead.
        if (nextChildren === null) {
          nextChildren = workInProgress.memoizedProps;
        }
      } else if (nextChildren === null || workInProgress.memoizedProps === nextChildren) {
        return bailoutOnAlreadyFinishedWork(current, workInProgress);
      }
      reconcileChildren(current, workInProgress, nextChildren);
      memoizeProps(workInProgress, nextChildren);
      return workInProgress.child;
    }
  
    function markRef(current, workInProgress) {
      var ref = workInProgress.ref;
      if (ref !== null && (!current || current.ref !== ref)) {
        // Schedule a Ref effect
        workInProgress.effectTag |= Ref;
      }
    }
  
    function updateFunctionalComponent(current, workInProgress) {
      var fn = workInProgress.type;
      var nextProps = workInProgress.pendingProps;
  
      var memoizedProps = workInProgress.memoizedProps;
      if (hasContextChanged()) {
        // Normally we can bail out on props equality but if context has changed
        // we don't do the bailout and we have to reuse existing props instead.
        if (nextProps === null) {
          nextProps = memoizedProps;
        }
      } else {
        if (nextProps === null || memoizedProps === nextProps) {
          return bailoutOnAlreadyFinishedWork(current, workInProgress);
        }
        // TODO: consider bringing fn.shouldComponentUpdate() back.
        // It used to be here.
      }
  
      var unmaskedContext = getUnmaskedContext(workInProgress);
      var context = getMaskedContext(workInProgress, unmaskedContext);
  
      var nextChildren;
  
      {
        ReactCurrentOwner.current = workInProgress;
        ReactDebugCurrentFiber.setCurrentPhase('render');
        nextChildren = fn(nextProps, context);
        ReactDebugCurrentFiber.setCurrentPhase(null);
      }
      // React DevTools reads this flag.
      workInProgress.effectTag |= PerformedWork;
      reconcileChildren(current, workInProgress, nextChildren);
      memoizeProps(workInProgress, nextProps);
      return workInProgress.child;
    }
  
    function updateClassComponent(current, workInProgress, renderExpirationTime) {
      // Push context providers early to prevent context stack mismatches.
      // During mounting we don't know the child context yet as the instance doesn't exist.
      // We will invalidate the child context in finishClassComponent() right after rendering.
      var hasContext = pushContextProvider(workInProgress);
  
      var shouldUpdate = void 0;
      if (current === null) {
        if (!workInProgress.stateNode) {
          // In the initial pass we might need to construct the instance.
          constructClassInstance(workInProgress, workInProgress.pendingProps);
          mountClassInstance(workInProgress, renderExpirationTime);
          shouldUpdate = true;
        } else {
          invariant(false, 'Resuming work not yet implemented.');
          // In a resume, we'll already have an instance we can reuse.
          // shouldUpdate = resumeMountClassInstance(workInProgress, renderExpirationTime);
        }
      } else {
        shouldUpdate = updateClassInstance(current, workInProgress, renderExpirationTime);
      }
      return finishClassComponent(current, workInProgress, shouldUpdate, hasContext);
    }
  
    function finishClassComponent(current, workInProgress, shouldUpdate, hasContext) {
      // Refs should update even if shouldComponentUpdate returns false
      markRef(current, workInProgress);
  
      if (!shouldUpdate) {
        // Context providers should defer to sCU for rendering
        if (hasContext) {
          invalidateContextProvider(workInProgress, false);
        }
  
        return bailoutOnAlreadyFinishedWork(current, workInProgress);
      }
  
      var instance = workInProgress.stateNode;
  
      // Rerender
      ReactCurrentOwner.current = workInProgress;
      var nextChildren = void 0;
      {
        ReactDebugCurrentFiber.setCurrentPhase('render');
        nextChildren = instance.render();
        if (debugRenderPhaseSideEffects) {
          instance.render();
        }
        ReactDebugCurrentFiber.setCurrentPhase(null);
      }
      // React DevTools reads this flag.
      workInProgress.effectTag |= PerformedWork;
      reconcileChildren(current, workInProgress, nextChildren);
      // Memoize props and state using the values we just used to render.
      // TODO: Restructure so we never read values from the instance.
      memoizeState(workInProgress, instance.state);
      memoizeProps(workInProgress, instance.props);
  
      // The context might have changed so we need to recalculate it.
      if (hasContext) {
        invalidateContextProvider(workInProgress, true);
      }
  
      return workInProgress.child;
    }
  
    function pushHostRootContext(workInProgress) {
      var root = workInProgress.stateNode;
      if (root.pendingContext) {
        pushTopLevelContextObject(workInProgress, root.pendingContext, root.pendingContext !== root.context);
      } else if (root.context) {
        // Should always be set
        pushTopLevelContextObject(workInProgress, root.context, false);
      }
      pushHostContainer(workInProgress, root.containerInfo);
    }
  
    function updateHostRoot(current, workInProgress, renderExpirationTime) {
      pushHostRootContext(workInProgress);
      var updateQueue = workInProgress.updateQueue;
      if (updateQueue !== null) {
        var prevState = workInProgress.memoizedState;
        var state = processUpdateQueue(current, workInProgress, updateQueue, null, null, renderExpirationTime);
        if (prevState === state) {
          // If the state is the same as before, that's a bailout because we had
          // no work that expires at this time.
          resetHydrationState();
          return bailoutOnAlreadyFinishedWork(current, workInProgress);
        }
        var element = state.element;
        var root = workInProgress.stateNode;
        if ((current === null || current.child === null) && root.hydrate && enterHydrationState(workInProgress)) {
          // If we don't have any current children this might be the first pass.
          // We always try to hydrate. If this isn't a hydration pass there won't
          // be any children to hydrate which is effectively the same thing as
          // not hydrating.
  
          // This is a bit of a hack. We track the host root as a placement to
          // know that we're currently in a mounting state. That way isMounted
          // works as expected. We must reset this before committing.
          // TODO: Delete this when we delete isMounted and findDOMNode.
          workInProgress.effectTag |= Placement;
  
          // Ensure that children mount into this root without tracking
          // side-effects. This ensures that we don't store Placement effects on
          // nodes that will be hydrated.
          workInProgress.child = mountChildFibers(workInProgress, null, element, renderExpirationTime);
        } else {
          // Otherwise reset hydration state in case we aborted and resumed another
          // root.
          resetHydrationState();
          reconcileChildren(current, workInProgress, element);
        }
        memoizeState(workInProgress, state);
        return workInProgress.child;
      }
      resetHydrationState();
      // If there is no update queue, that's a bailout because the root has no props.
      return bailoutOnAlreadyFinishedWork(current, workInProgress);
    }
  
    function updateHostComponent(current, workInProgress, renderExpirationTime) {
      pushHostContext(workInProgress);
  
      if (current === null) {
        tryToClaimNextHydratableInstance(workInProgress);
      }
  
      var type = workInProgress.type;
      var memoizedProps = workInProgress.memoizedProps;
      var nextProps = workInProgress.pendingProps;
      if (nextProps === null) {
        nextProps = memoizedProps;
        !(nextProps !== null) ? invariant(false, 'We should always have pending or current props. This error is likely caused by a bug in React. Please file an issue.') : void 0;
      }
      var prevProps = current !== null ? current.memoizedProps : null;
  
      if (hasContextChanged()) {
        // Normally we can bail out on props equality but if context has changed
        // we don't do the bailout and we have to reuse existing props instead.
      } else if (nextProps === null || memoizedProps === nextProps) {
        return bailoutOnAlreadyFinishedWork(current, workInProgress);
      }
  
      var nextChildren = nextProps.children;
      var isDirectTextChild = shouldSetTextContent(type, nextProps);
  
      if (isDirectTextChild) {
        // We special case a direct text child of a host node. This is a common
        // case. We won't handle it as a reified child. We will instead handle
        // this in the host environment that also have access to this prop. That
        // avoids allocating another HostText fiber and traversing it.
        nextChildren = null;
      } else if (prevProps && shouldSetTextContent(type, prevProps)) {
        // If we're switching from a direct text child to a normal child, or to
        // empty, we need to schedule the text content to be reset.
        workInProgress.effectTag |= ContentReset;
      }
  
      markRef(current, workInProgress);
  
      // Check the host config to see if the children are offscreen/hidden.
      if (renderExpirationTime !== Never && !useSyncScheduling && shouldDeprioritizeSubtree(type, nextProps)) {
        // Down-prioritize the children.
        workInProgress.expirationTime = Never;
        // Bailout and come back to this fiber later.
        return null;
      }
  
      reconcileChildren(current, workInProgress, nextChildren);
      memoizeProps(workInProgress, nextProps);
      return workInProgress.child;
    }
  
    function updateHostText(current, workInProgress) {
      if (current === null) {
        tryToClaimNextHydratableInstance(workInProgress);
      }
      var nextProps = workInProgress.pendingProps;
      if (nextProps === null) {
        nextProps = workInProgress.memoizedProps;
      }
      memoizeProps(workInProgress, nextProps);
      // Nothing to do here. This is terminal. We'll do the completion step
      // immediately after.
      return null;
    }
  
    function mountIndeterminateComponent(current, workInProgress, renderExpirationTime) {
      !(current === null) ? invariant(false, 'An indeterminate component should never have mounted. This error is likely caused by a bug in React. Please file an issue.') : void 0;
      var fn = workInProgress.type;
      var props = workInProgress.pendingProps;
      var unmaskedContext = getUnmaskedContext(workInProgress);
      var context = getMaskedContext(workInProgress, unmaskedContext);
  
      var value;
  
      {
        if (fn.prototype && typeof fn.prototype.render === 'function') {
          var componentName = getComponentName(workInProgress);
          warning(false, "The <%s /> component appears to have a render method, but doesn't extend React.Component. " + 'This is likely to cause errors. Change %s to extend React.Component instead.', componentName, componentName);
        }
        ReactCurrentOwner.current = workInProgress;
        value = fn(props, context);
      }
      // React DevTools reads this flag.
      workInProgress.effectTag |= PerformedWork;
  
      if (typeof value === 'object' && value !== null && typeof value.render === 'function') {
        // Proceed under the assumption that this is a class instance
        workInProgress.tag = ClassComponent;
  
        // Push context providers early to prevent context stack mismatches.
        // During mounting we don't know the child context yet as the instance doesn't exist.
        // We will invalidate the child context in finishClassComponent() right after rendering.
        var hasContext = pushContextProvider(workInProgress);
        adoptClassInstance(workInProgress, value);
        mountClassInstance(workInProgress, renderExpirationTime);
        return finishClassComponent(current, workInProgress, true, hasContext);
      } else {
        // Proceed under the assumption that this is a functional component
        workInProgress.tag = FunctionalComponent;
        {
          var Component = workInProgress.type;
  
          if (Component) {
            warning(!Component.childContextTypes, '%s(...): childContextTypes cannot be defined on a functional component.', Component.displayName || Component.name || 'Component');
          }
          if (workInProgress.ref !== null) {
            var info = '';
            var ownerName = ReactDebugCurrentFiber.getCurrentFiberOwnerName();
            if (ownerName) {
              info += '\n\nCheck the render method of `' + ownerName + '`.';
            }
  
            var warningKey = ownerName || workInProgress._debugID || '';
            var debugSource = workInProgress._debugSource;
            if (debugSource) {
              warningKey = debugSource.fileName + ':' + debugSource.lineNumber;
            }
            if (!warnedAboutStatelessRefs[warningKey]) {
              warnedAboutStatelessRefs[warningKey] = true;
              warning(false, 'Stateless function components cannot be given refs. ' + 'Attempts to access this ref will fail.%s%s', info, ReactDebugCurrentFiber.getCurrentFiberStackAddendum());
            }
          }
        }
        reconcileChildren(current, workInProgress, value);
        memoizeProps(workInProgress, props);
        return workInProgress.child;
      }
    }
  
    function updateCallComponent(current, workInProgress, renderExpirationTime) {
      var nextCall = workInProgress.pendingProps;
      if (hasContextChanged()) {
        // Normally we can bail out on props equality but if context has changed
        // we don't do the bailout and we have to reuse existing props instead.
        if (nextCall === null) {
          nextCall = current && current.memoizedProps;
          !(nextCall !== null) ? invariant(false, 'We should always have pending or current props. This error is likely caused by a bug in React. Please file an issue.') : void 0;
        }
      } else if (nextCall === null || workInProgress.memoizedProps === nextCall) {
        nextCall = workInProgress.memoizedProps;
        // TODO: When bailing out, we might need to return the stateNode instead
        // of the child. To check it for work.
        // return bailoutOnAlreadyFinishedWork(current, workInProgress);
      }
  
      var nextChildren = nextCall.children;
  
      // The following is a fork of reconcileChildrenAtExpirationTime but using
      // stateNode to store the child.
      if (current === null) {
        workInProgress.stateNode = mountChildFibers(workInProgress, workInProgress.stateNode, nextChildren, renderExpirationTime);
      } else {
        workInProgress.stateNode = reconcileChildFibers(workInProgress, workInProgress.stateNode, nextChildren, renderExpirationTime);
      }
  
      memoizeProps(workInProgress, nextCall);
      // This doesn't take arbitrary time so we could synchronously just begin
      // eagerly do the work of workInProgress.child as an optimization.
      return workInProgress.stateNode;
    }
  
    function updatePortalComponent(current, workInProgress, renderExpirationTime) {
      pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
      var nextChildren = workInProgress.pendingProps;
      if (hasContextChanged()) {
        // Normally we can bail out on props equality but if context has changed
        // we don't do the bailout and we have to reuse existing props instead.
        if (nextChildren === null) {
          nextChildren = current && current.memoizedProps;
          !(nextChildren != null) ? invariant(false, 'We should always have pending or current props. This error is likely caused by a bug in React. Please file an issue.') : void 0;
        }
      } else if (nextChildren === null || workInProgress.memoizedProps === nextChildren) {
        return bailoutOnAlreadyFinishedWork(current, workInProgress);
      }
  
      if (current === null) {
        // Portals are special because we don't append the children during mount
        // but at commit. Therefore we need to track insertions which the normal
        // flow doesn't do during mount. This doesn't happen at the root because
        // the root always starts with a "current" with a null child.
        // TODO: Consider unifying this with how the root works.
        workInProgress.child = reconcileChildFibers(workInProgress, null, nextChildren, renderExpirationTime);
        memoizeProps(workInProgress, nextChildren);
      } else {
        reconcileChildren(current, workInProgress, nextChildren);
        memoizeProps(workInProgress, nextChildren);
      }
      return workInProgress.child;
    }
  
    /*
    function reuseChildrenEffects(returnFiber : Fiber, firstChild : Fiber) {
      let child = firstChild;
      do {
        // Ensure that the first and last effect of the parent corresponds
        // to the children's first and last effect.
        if (!returnFiber.firstEffect) {
          returnFiber.firstEffect = child.firstEffect;
        }
        if (child.lastEffect) {
          if (returnFiber.lastEffect) {
            returnFiber.lastEffect.nextEffect = child.firstEffect;
          }
          returnFiber.lastEffect = child.lastEffect;
        }
      } while (child = child.sibling);
    }
    */
  
    function bailoutOnAlreadyFinishedWork(current, workInProgress) {
      cancelWorkTimer(workInProgress);
  
      // TODO: We should ideally be able to bail out early if the children have no
      // more work to do. However, since we don't have a separation of this
      // Fiber's priority and its children yet - we don't know without doing lots
      // of the same work we do anyway. Once we have that separation we can just
      // bail out here if the children has no more work at this priority level.
      // if (workInProgress.priorityOfChildren <= priorityLevel) {
      //   // If there are side-effects in these children that have not yet been
      //   // committed we need to ensure that they get properly transferred up.
      //   if (current && current.child !== workInProgress.child) {
      //     reuseChildrenEffects(workInProgress, child);
      //   }
      //   return null;
      // }
  
      cloneChildFibers(current, workInProgress);
      return workInProgress.child;
    }
  
    function bailoutOnLowPriority(current, workInProgress) {
      cancelWorkTimer(workInProgress);
  
      // TODO: Handle HostComponent tags here as well and call pushHostContext()?
      // See PR 8590 discussion for context
      switch (workInProgress.tag) {
        case HostRoot:
          pushHostRootContext(workInProgress);
          break;
        case ClassComponent:
          pushContextProvider(workInProgress);
          break;
        case HostPortal:
          pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
          break;
      }
      // TODO: What if this is currently in progress?
      // How can that happen? How is this not being cloned?
      return null;
    }
  
    // TODO: Delete memoizeProps/State and move to reconcile/bailout instead
    function memoizeProps(workInProgress, nextProps) {
      workInProgress.memoizedProps = nextProps;
    }
  
    function memoizeState(workInProgress, nextState) {
      workInProgress.memoizedState = nextState;
      // Don't reset the updateQueue, in case there are pending updates. Resetting
      // is handled by processUpdateQueue.
    }
  
    function beginWork(current, workInProgress, renderExpirationTime) {
      if (workInProgress.expirationTime === NoWork || workInProgress.expirationTime > renderExpirationTime) {
        return bailoutOnLowPriority(current, workInProgress);
      }
  
      switch (workInProgress.tag) {
        case IndeterminateComponent:
          return mountIndeterminateComponent(current, workInProgress, renderExpirationTime);
        case FunctionalComponent:
          return updateFunctionalComponent(current, workInProgress);
        case ClassComponent:
          return updateClassComponent(current, workInProgress, renderExpirationTime);
        case HostRoot:
          return updateHostRoot(current, workInProgress, renderExpirationTime);
        case HostComponent:
          return updateHostComponent(current, workInProgress, renderExpirationTime);
        case HostText:
          return updateHostText(current, workInProgress);
        case CallHandlerPhase:
          // This is a restart. Reset the tag to the initial phase.
          workInProgress.tag = CallComponent;
        // Intentionally fall through since this is now the same.
        case CallComponent:
          return updateCallComponent(current, workInProgress, renderExpirationTime);
        case ReturnComponent:
          // A return component is just a placeholder, we can just run through the
          // next one immediately.
          return null;
        case HostPortal:
          return updatePortalComponent(current, workInProgress, renderExpirationTime);
        case Fragment:
          return updateFragment(current, workInProgress);
        default:
          invariant(false, 'Unknown unit of work tag. This error is likely caused by a bug in React. Please file an issue.');
      }
    }
  
    function beginFailedWork(current, workInProgress, renderExpirationTime) {
      // Push context providers here to avoid a push/pop context mismatch.
      switch (workInProgress.tag) {
        case ClassComponent:
          pushContextProvider(workInProgress);
          break;
        case HostRoot:
          pushHostRootContext(workInProgress);
          break;
        default:
          invariant(false, 'Invalid type of work. This error is likely caused by a bug in React. Please file an issue.');
      }
  
      // Add an error effect so we can handle the error during the commit phase
      workInProgress.effectTag |= Err;
  
      // This is a weird case where we do "resume" work — work that failed on
      // our first attempt. Because we no longer have a notion of "progressed
      // deletions," reset the child to the current child to make sure we delete
      // it again. TODO: Find a better way to handle this, perhaps during a more
      // general overhaul of error handling.
      if (current === null) {
        workInProgress.child = null;
      } else if (workInProgress.child !== current.child) {
        workInProgress.child = current.child;
      }
  
      if (workInProgress.expirationTime === NoWork || workInProgress.expirationTime > renderExpirationTime) {
        return bailoutOnLowPriority(current, workInProgress);
      }
  
      // If we don't bail out, we're going be recomputing our children so we need
      // to drop our effect list.
      workInProgress.firstEffect = null;
      workInProgress.lastEffect = null;
  
      // Unmount the current children as if the component rendered null
      var nextChildren = null;
      reconcileChildrenAtExpirationTime(current, workInProgress, nextChildren, renderExpirationTime);
  
      if (workInProgress.tag === ClassComponent) {
        var instance = workInProgress.stateNode;
        workInProgress.memoizedProps = instance.props;
        workInProgress.memoizedState = instance.state;
      }
  
      return workInProgress.child;
    }
  
    return {
      beginWork: beginWork,
      beginFailedWork: beginFailedWork
    };
  };
  
  var ReactFiberCompleteWork = function (config, hostContext, hydrationContext) {
    var createInstance = config.createInstance,
        createTextInstance = config.createTextInstance,
        appendInitialChild = config.appendInitialChild,
        finalizeInitialChildren = config.finalizeInitialChildren,
        prepareUpdate = config.prepareUpdate,
        mutation = config.mutation,
        persistence = config.persistence;
    var getRootHostContainer = hostContext.getRootHostContainer,
        popHostContext = hostContext.popHostContext,
        getHostContext = hostContext.getHostContext,
        popHostContainer = hostContext.popHostContainer;
    var prepareToHydrateHostInstance = hydrationContext.prepareToHydrateHostInstance,
        prepareToHydrateHostTextInstance = hydrationContext.prepareToHydrateHostTextInstance,
        popHydrationState = hydrationContext.popHydrationState;
  
  
    function markUpdate(workInProgress) {
      // Tag the fiber with an update effect. This turns a Placement into
      // an UpdateAndPlacement.
      workInProgress.effectTag |= Update;
    }
  
    function markRef(workInProgress) {
      workInProgress.effectTag |= Ref;
    }
  
    function appendAllReturns(returns, workInProgress) {
      var node = workInProgress.stateNode;
      if (node) {
        node['return'] = workInProgress;
      }
      while (node !== null) {
        if (node.tag === HostComponent || node.tag === HostText || node.tag === HostPortal) {
          invariant(false, 'A call cannot have host component children.');
        } else if (node.tag === ReturnComponent) {
          returns.push(node.type);
        } else if (node.child !== null) {
          node.child['return'] = node;
          node = node.child;
          continue;
        }
        while (node.sibling === null) {
          if (node['return'] === null || node['return'] === workInProgress) {
            return;
          }
          node = node['return'];
        }
        node.sibling['return'] = node['return'];
        node = node.sibling;
      }
    }
  
    function moveCallToHandlerPhase(current, workInProgress, renderExpirationTime) {
      var call = workInProgress.memoizedProps;
      !call ? invariant(false, 'Should be resolved by now. This error is likely caused by a bug in React. Please file an issue.') : void 0;
  
      // First step of the call has completed. Now we need to do the second.
      // TODO: It would be nice to have a multi stage call represented by a
      // single component, or at least tail call optimize nested ones. Currently
      // that requires additional fields that we don't want to add to the fiber.
      // So this requires nested handlers.
      // Note: This doesn't mutate the alternate node. I don't think it needs to
      // since this stage is reset for every pass.
      workInProgress.tag = CallHandlerPhase;
  
      // Build up the returns.
      // TODO: Compare this to a generator or opaque helpers like Children.
      var returns = [];
      appendAllReturns(returns, workInProgress);
      var fn = call.handler;
      var props = call.props;
      var nextChildren = fn(props, returns);
  
      var currentFirstChild = current !== null ? current.child : null;
      workInProgress.child = reconcileChildFibers(workInProgress, currentFirstChild, nextChildren, renderExpirationTime);
      return workInProgress.child;
    }
  
    function appendAllChildren(parent, workInProgress) {
      // We only have the top Fiber that was created but we need recurse down its
      // children to find all the terminal nodes.
      var node = workInProgress.child;
      while (node !== null) {
        if (node.tag === HostComponent || node.tag === HostText) {
          appendInitialChild(parent, node.stateNode);
        } else if (node.tag === HostPortal) {
          // If we have a portal child, then we don't want to traverse
          // down its children. Instead, we'll get insertions from each child in
          // the portal directly.
        } else if (node.child !== null) {
          node.child['return'] = node;
          node = node.child;
          continue;
        }
        if (node === workInProgress) {
          return;
        }
        while (node.sibling === null) {
          if (node['return'] === null || node['return'] === workInProgress) {
            return;
          }
          node = node['return'];
        }
        node.sibling['return'] = node['return'];
        node = node.sibling;
      }
    }
  
    var updateHostContainer = void 0;
    var updateHostComponent = void 0;
    var updateHostText = void 0;
    if (mutation) {
      if (enableMutatingReconciler) {
        // Mutation mode
        updateHostContainer = function (workInProgress) {
          // Noop
        };
        updateHostComponent = function (current, workInProgress, updatePayload, type, oldProps, newProps, rootContainerInstance) {
          // TODO: Type this specific to this type of component.
          workInProgress.updateQueue = updatePayload;
          // If the update payload indicates that there is a change or if there
          // is a new ref we mark this as an update. All the work is done in commitWork.
          if (updatePayload) {
            markUpdate(workInProgress);
          }
        };
        updateHostText = function (current, workInProgress, oldText, newText) {
          // If the text differs, mark it as an update. All the work in done in commitWork.
          if (oldText !== newText) {
            markUpdate(workInProgress);
          }
        };
      } else {
        invariant(false, 'Mutating reconciler is disabled.');
      }
    } else if (persistence) {
      if (enablePersistentReconciler) {
        // Persistent host tree mode
        var cloneInstance = persistence.cloneInstance,
            createContainerChildSet = persistence.createContainerChildSet,
            appendChildToContainerChildSet = persistence.appendChildToContainerChildSet,
            finalizeContainerChildren = persistence.finalizeContainerChildren;
  
        // An unfortunate fork of appendAllChildren because we have two different parent types.
  
        var appendAllChildrenToContainer = function (containerChildSet, workInProgress) {
          // We only have the top Fiber that was created but we need recurse down its
          // children to find all the terminal nodes.
          var node = workInProgress.child;
          while (node !== null) {
            if (node.tag === HostComponent || node.tag === HostText) {
              appendChildToContainerChildSet(containerChildSet, node.stateNode);
            } else if (node.tag === HostPortal) {
              // If we have a portal child, then we don't want to traverse
              // down its children. Instead, we'll get insertions from each child in
              // the portal directly.
            } else if (node.child !== null) {
              node.child['return'] = node;
              node = node.child;
              continue;
            }
            if (node === workInProgress) {
              return;
            }
            while (node.sibling === null) {
              if (node['return'] === null || node['return'] === workInProgress) {
                return;
              }
              node = node['return'];
            }
            node.sibling['return'] = node['return'];
            node = node.sibling;
          }
        };
        updateHostContainer = function (workInProgress) {
          var portalOrRoot = workInProgress.stateNode;
          var childrenUnchanged = workInProgress.firstEffect === null;
          if (childrenUnchanged) {
            // No changes, just reuse the existing instance.
          } else {
            var container = portalOrRoot.containerInfo;
            var newChildSet = createContainerChildSet(container);
            if (finalizeContainerChildren(container, newChildSet)) {
              markUpdate(workInProgress);
            }
            portalOrRoot.pendingChildren = newChildSet;
            // If children might have changed, we have to add them all to the set.
            appendAllChildrenToContainer(newChildSet, workInProgress);
            // Schedule an update on the container to swap out the container.
            markUpdate(workInProgress);
          }
        };
        updateHostComponent = function (current, workInProgress, updatePayload, type, oldProps, newProps, rootContainerInstance) {
          // If there are no effects associated with this node, then none of our children had any updates.
          // This guarantees that we can reuse all of them.
          var childrenUnchanged = workInProgress.firstEffect === null;
          var currentInstance = current.stateNode;
          if (childrenUnchanged && updatePayload === null) {
            // No changes, just reuse the existing instance.
            // Note that this might release a previous clone.
            workInProgress.stateNode = currentInstance;
          } else {
            var recyclableInstance = workInProgress.stateNode;
            var newInstance = cloneInstance(currentInstance, updatePayload, type, oldProps, newProps, workInProgress, childrenUnchanged, recyclableInstance);
            if (finalizeInitialChildren(newInstance, type, newProps, rootContainerInstance)) {
              markUpdate(workInProgress);
            }
            workInProgress.stateNode = newInstance;
            if (childrenUnchanged) {
              // If there are no other effects in this tree, we need to flag this node as having one.
              // Even though we're not going to use it for anything.
              // Otherwise parents won't know that there are new children to propagate upwards.
              markUpdate(workInProgress);
            } else {
              // If children might have changed, we have to add them all to the set.
              appendAllChildren(newInstance, workInProgress);
            }
          }
        };
        updateHostText = function (current, workInProgress, oldText, newText) {
          if (oldText !== newText) {
            // If the text content differs, we'll create a new text instance for it.
            var rootContainerInstance = getRootHostContainer();
            var currentHostContext = getHostContext();
            workInProgress.stateNode = createTextInstance(newText, rootContainerInstance, currentHostContext, workInProgress);
            // We'll have to mark it as having an effect, even though we won't use the effect for anything.
            // This lets the parents know that at least one of their children has changed.
            markUpdate(workInProgress);
          }
        };
      } else {
        invariant(false, 'Persistent reconciler is disabled.');
      }
    } else {
      if (enableNoopReconciler) {
        // No host operations
        updateHostContainer = function (workInProgress) {
          // Noop
        };
        updateHostComponent = function (current, workInProgress, updatePayload, type, oldProps, newProps, rootContainerInstance) {
          // Noop
        };
        updateHostText = function (current, workInProgress, oldText, newText) {
          // Noop
        };
      } else {
        invariant(false, 'Noop reconciler is disabled.');
      }
    }
  
    function completeWork(current, workInProgress, renderExpirationTime) {
      // Get the latest props.
      var newProps = workInProgress.pendingProps;
      if (newProps === null) {
        newProps = workInProgress.memoizedProps;
      } else if (workInProgress.expirationTime !== Never || renderExpirationTime === Never) {
        // Reset the pending props, unless this was a down-prioritization.
        workInProgress.pendingProps = null;
      }
  
      switch (workInProgress.tag) {
        case FunctionalComponent:
          return null;
        case ClassComponent:
          {
            // We are leaving this subtree, so pop context if any.
            popContextProvider(workInProgress);
            return null;
          }
        case HostRoot:
          {
            popHostContainer(workInProgress);
            popTopLevelContextObject(workInProgress);
            var fiberRoot = workInProgress.stateNode;
            if (fiberRoot.pendingContext) {
              fiberRoot.context = fiberRoot.pendingContext;
              fiberRoot.pendingContext = null;
            }
  
            if (current === null || current.child === null) {
              // If we hydrated, pop so that we can delete any remaining children
              // that weren't hydrated.
              popHydrationState(workInProgress);
              // This resets the hacky state to fix isMounted before committing.
              // TODO: Delete this when we delete isMounted and findDOMNode.
              workInProgress.effectTag &= ~Placement;
            }
            updateHostContainer(workInProgress);
            return null;
          }
        case HostComponent:
          {
            popHostContext(workInProgress);
            var rootContainerInstance = getRootHostContainer();
            var type = workInProgress.type;
            if (current !== null && workInProgress.stateNode != null) {
              // If we have an alternate, that means this is an update and we need to
              // schedule a side-effect to do the updates.
              var oldProps = current.memoizedProps;
              // If we get updated because one of our children updated, we don't
              // have newProps so we'll have to reuse them.
              // TODO: Split the update API as separate for the props vs. children.
              // Even better would be if children weren't special cased at all tho.
              var instance = workInProgress.stateNode;
              var currentHostContext = getHostContext();
              var updatePayload = prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, currentHostContext);
  
              updateHostComponent(current, workInProgress, updatePayload, type, oldProps, newProps, rootContainerInstance);
  
              if (current.ref !== workInProgress.ref) {
                markRef(workInProgress);
              }
            } else {
              if (!newProps) {
                !(workInProgress.stateNode !== null) ? invariant(false, 'We must have new props for new mounts. This error is likely caused by a bug in React. Please file an issue.') : void 0;
                // This can happen when we abort work.
                return null;
              }
  
              var _currentHostContext = getHostContext();
              // TODO: Move createInstance to beginWork and keep it on a context
              // "stack" as the parent. Then append children as we go in beginWork
              // or completeWork depending on we want to add then top->down or
              // bottom->up. Top->down is faster in IE11.
              var wasHydrated = popHydrationState(workInProgress);
              if (wasHydrated) {
                // TODO: Move this and createInstance step into the beginPhase
                // to consolidate.
                if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, _currentHostContext)) {
                  // If changes to the hydrated node needs to be applied at the
                  // commit-phase we mark this as such.
                  markUpdate(workInProgress);
                }
              } else {
                var _instance = createInstance(type, newProps, rootContainerInstance, _currentHostContext, workInProgress);
  
                appendAllChildren(_instance, workInProgress);
  
                // Certain renderers require commit-time effects for initial mount.
                // (eg DOM renderer supports auto-focus for certain elements).
                // Make sure such renderers get scheduled for later work.
                if (finalizeInitialChildren(_instance, type, newProps, rootContainerInstance)) {
                  markUpdate(workInProgress);
                }
                workInProgress.stateNode = _instance;
              }
  
              if (workInProgress.ref !== null) {
                // If there is a ref on a host node we need to schedule a callback
                markRef(workInProgress);
              }
            }
            return null;
          }
        case HostText:
          {
            var newText = newProps;
            if (current && workInProgress.stateNode != null) {
              var oldText = current.memoizedProps;
              // If we have an alternate, that means this is an update and we need
              // to schedule a side-effect to do the updates.
              updateHostText(current, workInProgress, oldText, newText);
            } else {
              if (typeof newText !== 'string') {
                !(workInProgress.stateNode !== null) ? invariant(false, 'We must have new props for new mounts. This error is likely caused by a bug in React. Please file an issue.') : void 0;
                // This can happen when we abort work.
                return null;
              }
              var _rootContainerInstance = getRootHostContainer();
              var _currentHostContext2 = getHostContext();
              var _wasHydrated = popHydrationState(workInProgress);
              if (_wasHydrated) {
                if (prepareToHydrateHostTextInstance(workInProgress)) {
                  markUpdate(workInProgress);
                }
              } else {
                workInProgress.stateNode = createTextInstance(newText, _rootContainerInstance, _currentHostContext2, workInProgress);
              }
            }
            return null;
          }
        case CallComponent:
          return moveCallToHandlerPhase(current, workInProgress, renderExpirationTime);
        case CallHandlerPhase:
          // Reset the tag to now be a first phase call.
          workInProgress.tag = CallComponent;
          return null;
        case ReturnComponent:
          // Does nothing.
          return null;
        case Fragment:
          return null;
        case HostPortal:
          popHostContainer(workInProgress);
          updateHostContainer(workInProgress);
          return null;
        // Error cases
        case IndeterminateComponent:
          invariant(false, 'An indeterminate component should have become determinate before completing. This error is likely caused by a bug in React. Please file an issue.');
        // eslint-disable-next-line no-fallthrough
        default:
          invariant(false, 'Unknown unit of work tag. This error is likely caused by a bug in React. Please file an issue.');
      }
    }
  
    return {
      completeWork: completeWork
    };
  };
  
  var invokeGuardedCallback$2 = ReactErrorUtils.invokeGuardedCallback;
  var hasCaughtError$1 = ReactErrorUtils.hasCaughtError;
  var clearCaughtError$1 = ReactErrorUtils.clearCaughtError;
  
  
  var ReactFiberCommitWork = function (config, captureError) {
    var getPublicInstance = config.getPublicInstance,
        mutation = config.mutation,
        persistence = config.persistence;
  
  
    var callComponentWillUnmountWithTimer = function (current, instance) {
      startPhaseTimer(current, 'componentWillUnmount');
      instance.props = current.memoizedProps;
      instance.state = current.memoizedState;
      instance.componentWillUnmount();
      stopPhaseTimer();
    };
  
    // Capture errors so they don't interrupt unmounting.
    function safelyCallComponentWillUnmount(current, instance) {
      {
        invokeGuardedCallback$2(null, callComponentWillUnmountWithTimer, null, current, instance);
        if (hasCaughtError$1()) {
          var unmountError = clearCaughtError$1();
          captureError(current, unmountError);
        }
      }
    }
  
    function safelyDetachRef(current) {
      var ref = current.ref;
      if (ref !== null) {
        {
          invokeGuardedCallback$2(null, ref, null, null);
          if (hasCaughtError$1()) {
            var refError = clearCaughtError$1();
            captureError(current, refError);
          }
        }
      }
    }
  
    function commitLifeCycles(current, finishedWork) {
      switch (finishedWork.tag) {
        case ClassComponent:
          {
            var instance = finishedWork.stateNode;
            if (finishedWork.effectTag & Update) {
              if (current === null) {
                startPhaseTimer(finishedWork, 'componentDidMount');
                instance.props = finishedWork.memoizedProps;
                instance.state = finishedWork.memoizedState;
                instance.componentDidMount();
                stopPhaseTimer();
              } else {
                var prevProps = current.memoizedProps;
                var prevState = current.memoizedState;
                startPhaseTimer(finishedWork, 'componentDidUpdate');
                instance.props = finishedWork.memoizedProps;
                instance.state = finishedWork.memoizedState;
                instance.componentDidUpdate(prevProps, prevState);
                stopPhaseTimer();
              }
            }
            var updateQueue = finishedWork.updateQueue;
            if (updateQueue !== null) {
              commitCallbacks(updateQueue, instance);
            }
            return;
          }
        case HostRoot:
          {
            var _updateQueue = finishedWork.updateQueue;
            if (_updateQueue !== null) {
              var _instance = finishedWork.child !== null ? finishedWork.child.stateNode : null;
              commitCallbacks(_updateQueue, _instance);
            }
            return;
          }
        case HostComponent:
          {
            var _instance2 = finishedWork.stateNode;
  
            // Renderers may schedule work to be done after host components are mounted
            // (eg DOM renderer may schedule auto-focus for inputs and form controls).
            // These effects should only be committed when components are first mounted,
            // aka when there is no current/alternate.
            if (current === null && finishedWork.effectTag & Update) {
              var type = finishedWork.type;
              var props = finishedWork.memoizedProps;
              commitMount(_instance2, type, props, finishedWork);
            }
  
            return;
          }
        case HostText:
          {
            // We have no life-cycles associated with text.
            return;
          }
        case HostPortal:
          {
            // We have no life-cycles associated with portals.
            return;
          }
        default:
          {
            invariant(false, 'This unit of work tag should not have side-effects. This error is likely caused by a bug in React. Please file an issue.');
          }
      }
    }
  
    function commitAttachRef(finishedWork) {
      var ref = finishedWork.ref;
      if (ref !== null) {
        var instance = finishedWork.stateNode;
        switch (finishedWork.tag) {
          case HostComponent:
            ref(getPublicInstance(instance));
            break;
          default:
            ref(instance);
        }
      }
    }
  
    function commitDetachRef(current) {
      var currentRef = current.ref;
      if (currentRef !== null) {
        currentRef(null);
      }
    }
  
    // User-originating errors (lifecycles and refs) should not interrupt
    // deletion, so don't let them throw. Host-originating errors should
    // interrupt deletion, so it's okay
    function commitUnmount(current) {
      if (typeof onCommitUnmount === 'function') {
        onCommitUnmount(current);
      }
  
      switch (current.tag) {
        case ClassComponent:
          {
            safelyDetachRef(current);
            var instance = current.stateNode;
            if (typeof instance.componentWillUnmount === 'function') {
              safelyCallComponentWillUnmount(current, instance);
            }
            return;
          }
        case HostComponent:
          {
            safelyDetachRef(current);
            return;
          }
        case CallComponent:
          {
            commitNestedUnmounts(current.stateNode);
            return;
          }
        case HostPortal:
          {
            // TODO: this is recursive.
            // We are also not using this parent because
            // the portal will get pushed immediately.
            if (enableMutatingReconciler && mutation) {
              unmountHostComponents(current);
            } else if (enablePersistentReconciler && persistence) {
              emptyPortalContainer(current);
            }
            return;
          }
      }
    }
  
    function commitNestedUnmounts(root) {
      // While we're inside a removed host node we don't want to call
      // removeChild on the inner nodes because they're removed by the top
      // call anyway. We also want to call componentWillUnmount on all
      // composites before this host node is removed from the tree. Therefore
      var node = root;
      while (true) {
        commitUnmount(node);
        // Visit children because they may contain more composite or host nodes.
        // Skip portals because commitUnmount() currently visits them recursively.
        if (node.child !== null && (
        // If we use mutation we drill down into portals using commitUnmount above.
        // If we don't use mutation we drill down into portals here instead.
        !mutation || node.tag !== HostPortal)) {
          node.child['return'] = node;
          node = node.child;
          continue;
        }
        if (node === root) {
          return;
        }
        while (node.sibling === null) {
          if (node['return'] === null || node['return'] === root) {
            return;
          }
          node = node['return'];
        }
        node.sibling['return'] = node['return'];
        node = node.sibling;
      }
    }
  
    function detachFiber(current) {
      // Cut off the return pointers to disconnect it from the tree. Ideally, we
      // should clear the child pointer of the parent alternate to let this
      // get GC:ed but we don't know which for sure which parent is the current
      // one so we'll settle for GC:ing the subtree of this child. This child
      // itself will be GC:ed when the parent updates the next time.
      current['return'] = null;
      current.child = null;
      if (current.alternate) {
        current.alternate.child = null;
        current.alternate['return'] = null;
      }
    }
  
    if (!mutation) {
      var commitContainer = void 0;
      if (persistence) {
        var replaceContainerChildren = persistence.replaceContainerChildren,
            createContainerChildSet = persistence.createContainerChildSet;
  
        var emptyPortalContainer = function (current) {
          var portal = current.stateNode;
          var containerInfo = portal.containerInfo;
  
          var emptyChildSet = createContainerChildSet(containerInfo);
          replaceContainerChildren(containerInfo, emptyChildSet);
        };
        commitContainer = function (finishedWork) {
          switch (finishedWork.tag) {
            case ClassComponent:
              {
                return;
              }
            case HostComponent:
              {
                return;
              }
            case HostText:
              {
                return;
              }
            case HostRoot:
            case HostPortal:
              {
                var portalOrRoot = finishedWork.stateNode;
                var containerInfo = portalOrRoot.containerInfo,
                    _pendingChildren = portalOrRoot.pendingChildren;
  
                replaceContainerChildren(containerInfo, _pendingChildren);
                return;
              }
            default:
              {
                invariant(false, 'This unit of work tag should not have side-effects. This error is likely caused by a bug in React. Please file an issue.');
              }
          }
        };
      } else {
        commitContainer = function (finishedWork) {
          // Noop
        };
      }
      if (enablePersistentReconciler || enableNoopReconciler) {
        return {
          commitResetTextContent: function (finishedWork) {},
          commitPlacement: function (finishedWork) {},
          commitDeletion: function (current) {
            // Detach refs and call componentWillUnmount() on the whole subtree.
            commitNestedUnmounts(current);
            detachFiber(current);
          },
          commitWork: function (current, finishedWork) {
            commitContainer(finishedWork);
          },
  
          commitLifeCycles: commitLifeCycles,
          commitAttachRef: commitAttachRef,
          commitDetachRef: commitDetachRef
        };
      } else if (persistence) {
        invariant(false, 'Persistent reconciler is disabled.');
      } else {
        invariant(false, 'Noop reconciler is disabled.');
      }
    }
    var commitMount = mutation.commitMount,
        commitUpdate = mutation.commitUpdate,
        resetTextContent = mutation.resetTextContent,
        commitTextUpdate = mutation.commitTextUpdate,
        appendChild = mutation.appendChild,
        appendChildToContainer = mutation.appendChildToContainer,
        insertBefore = mutation.insertBefore,
        insertInContainerBefore = mutation.insertInContainerBefore,
        removeChild = mutation.removeChild,
        removeChildFromContainer = mutation.removeChildFromContainer;
  
  
    function getHostParentFiber(fiber) {
      var parent = fiber['return'];
      while (parent !== null) {
        if (isHostParent(parent)) {
          return parent;
        }
        parent = parent['return'];
      }
      invariant(false, 'Expected to find a host parent. This error is likely caused by a bug in React. Please file an issue.');
    }
  
    function isHostParent(fiber) {
      return fiber.tag === HostComponent || fiber.tag === HostRoot || fiber.tag === HostPortal;
    }
  
    function getHostSibling(fiber) {
      // We're going to search forward into the tree until we find a sibling host
      // node. Unfortunately, if multiple insertions are done in a row we have to
      // search past them. This leads to exponential search for the next sibling.
      var node = fiber;
      siblings: while (true) {
        // If we didn't find anything, let's try the next sibling.
        while (node.sibling === null) {
          if (node['return'] === null || isHostParent(node['return'])) {
            // If we pop out of the root or hit the parent the fiber we are the
            // last sibling.
            return null;
          }
          node = node['return'];
        }
        node.sibling['return'] = node['return'];
        node = node.sibling;
        while (node.tag !== HostComponent && node.tag !== HostText) {
          // If it is not host node and, we might have a host node inside it.
          // Try to search down until we find one.
          if (node.effectTag & Placement) {
            // If we don't have a child, try the siblings instead.
            continue siblings;
          }
          // If we don't have a child, try the siblings instead.
          // We also skip portals because they are not part of this host tree.
          if (node.child === null || node.tag === HostPortal) {
            continue siblings;
          } else {
            node.child['return'] = node;
            node = node.child;
          }
        }
        // Check if this host node is stable or about to be placed.
        if (!(node.effectTag & Placement)) {
          // Found it!
          return node.stateNode;
        }
      }
    }
  
    function commitPlacement(finishedWork) {
      // Recursively insert all host nodes into the parent.
      var parentFiber = getHostParentFiber(finishedWork);
      var parent = void 0;
      var isContainer = void 0;
      switch (parentFiber.tag) {
        case HostComponent:
          parent = parentFiber.stateNode;
          isContainer = false;
          break;
        case HostRoot:
          parent = parentFiber.stateNode.containerInfo;
          isContainer = true;
          break;
        case HostPortal:
          parent = parentFiber.stateNode.containerInfo;
          isContainer = true;
          break;
        default:
          invariant(false, 'Invalid host parent fiber. This error is likely caused by a bug in React. Please file an issue.');
      }
      if (parentFiber.effectTag & ContentReset) {
        // Reset the text content of the parent before doing any insertions
        resetTextContent(parent);
        // Clear ContentReset from the effect tag
        parentFiber.effectTag &= ~ContentReset;
      }
  
      var before = getHostSibling(finishedWork);
      // We only have the top Fiber that was inserted but we need recurse down its
      // children to find all the terminal nodes.
      var node = finishedWork;
      while (true) {
        if (node.tag === HostComponent || node.tag === HostText) {
          if (before) {
            if (isContainer) {
              insertInContainerBefore(parent, node.stateNode, before);
            } else {
              insertBefore(parent, node.stateNode, before);
            }
          } else {
            if (isContainer) {
              appendChildToContainer(parent, node.stateNode);
            } else {
              appendChild(parent, node.stateNode);
            }
          }
        } else if (node.tag === HostPortal) {
          // If the insertion itself is a portal, then we don't want to traverse
          // down its children. Instead, we'll get insertions from each child in
          // the portal directly.
        } else if (node.child !== null) {
          node.child['return'] = node;
          node = node.child;
          continue;
        }
        if (node === finishedWork) {
          return;
        }
        while (node.sibling === null) {
          if (node['return'] === null || node['return'] === finishedWork) {
            return;
          }
          node = node['return'];
        }
        node.sibling['return'] = node['return'];
        node = node.sibling;
      }
    }
  
    function unmountHostComponents(current) {
      // We only have the top Fiber that was inserted but we need recurse down its
      var node = current;
  
      // Each iteration, currentParent is populated with node's host parent if not
      // currentParentIsValid.
      var currentParentIsValid = false;
      var currentParent = void 0;
      var currentParentIsContainer = void 0;
  
      while (true) {
        if (!currentParentIsValid) {
          var parent = node['return'];
          findParent: while (true) {
            !(parent !== null) ? invariant(false, 'Expected to find a host parent. This error is likely caused by a bug in React. Please file an issue.') : void 0;
            switch (parent.tag) {
              case HostComponent:
                currentParent = parent.stateNode;
                currentParentIsContainer = false;
                break findParent;
              case HostRoot:
                currentParent = parent.stateNode.containerInfo;
                currentParentIsContainer = true;
                break findParent;
              case HostPortal:
                currentParent = parent.stateNode.containerInfo;
                currentParentIsContainer = true;
                break findParent;
            }
            parent = parent['return'];
          }
          currentParentIsValid = true;
        }
  
        if (node.tag === HostComponent || node.tag === HostText) {
          commitNestedUnmounts(node);
          // After all the children have unmounted, it is now safe to remove the
          // node from the tree.
          if (currentParentIsContainer) {
            removeChildFromContainer(currentParent, node.stateNode);
          } else {
            removeChild(currentParent, node.stateNode);
          }
          // Don't visit children because we already visited them.
        } else if (node.tag === HostPortal) {
          // When we go into a portal, it becomes the parent to remove from.
          // We will reassign it back when we pop the portal on the way up.
          currentParent = node.stateNode.containerInfo;
          // Visit children because portals might contain host components.
          if (node.child !== null) {
            node.child['return'] = node;
            node = node.child;
            continue;
          }
        } else {
          commitUnmount(node);
          // Visit children because we may find more host components below.
          if (node.child !== null) {
            node.child['return'] = node;
            node = node.child;
            continue;
          }
        }
        if (node === current) {
          return;
        }
        while (node.sibling === null) {
          if (node['return'] === null || node['return'] === current) {
            return;
          }
          node = node['return'];
          if (node.tag === HostPortal) {
            // When we go out of the portal, we need to restore the parent.
            // Since we don't keep a stack of them, we will search for it.
            currentParentIsValid = false;
          }
        }
        node.sibling['return'] = node['return'];
        node = node.sibling;
      }
    }
  
    function commitDeletion(current) {
      // Recursively delete all host nodes from the parent.
      // Detach refs and call componentWillUnmount() on the whole subtree.
      unmountHostComponents(current);
      detachFiber(current);
    }
  
    function commitWork(current, finishedWork) {
      switch (finishedWork.tag) {
        case ClassComponent:
          {
            return;
          }
        case HostComponent:
          {
            var instance = finishedWork.stateNode;
            if (instance != null) {
              // Commit the work prepared earlier.
              var newProps = finishedWork.memoizedProps;
              // For hydration we reuse the update path but we treat the oldProps
              // as the newProps. The updatePayload will contain the real change in
              // this case.
              var oldProps = current !== null ? current.memoizedProps : newProps;
              var type = finishedWork.type;
              // TODO: Type the updateQueue to be specific to host components.
              var updatePayload = finishedWork.updateQueue;
              finishedWork.updateQueue = null;
              if (updatePayload !== null) {
                commitUpdate(instance, updatePayload, type, oldProps, newProps, finishedWork);
              }
            }
            return;
          }
        case HostText:
          {
            !(finishedWork.stateNode !== null) ? invariant(false, 'This should have a text node initialized. This error is likely caused by a bug in React. Please file an issue.') : void 0;
            var textInstance = finishedWork.stateNode;
            var newText = finishedWork.memoizedProps;
            // For hydration we reuse the update path but we treat the oldProps
            // as the newProps. The updatePayload will contain the real change in
            // this case.
            var oldText = current !== null ? current.memoizedProps : newText;
            commitTextUpdate(textInstance, oldText, newText);
            return;
          }
        case HostRoot:
          {
            return;
          }
        default:
          {
            invariant(false, 'This unit of work tag should not have side-effects. This error is likely caused by a bug in React. Please file an issue.');
          }
      }
    }
  
    function commitResetTextContent(current) {
      resetTextContent(current.stateNode);
    }
  
    if (enableMutatingReconciler) {
      return {
        commitResetTextContent: commitResetTextContent,
        commitPlacement: commitPlacement,
        commitDeletion: commitDeletion,
        commitWork: commitWork,
        commitLifeCycles: commitLifeCycles,
        commitAttachRef: commitAttachRef,
        commitDetachRef: commitDetachRef
      };
    } else {
      invariant(false, 'Mutating reconciler is disabled.');
    }
  };
  
  var NO_CONTEXT = {};
  
  var ReactFiberHostContext = function (config) {
    var getChildHostContext = config.getChildHostContext,
        getRootHostContext = config.getRootHostContext;
  
  
    var contextStackCursor = createCursor(NO_CONTEXT);
    var contextFiberStackCursor = createCursor(NO_CONTEXT);
    var rootInstanceStackCursor = createCursor(NO_CONTEXT);
  
    function requiredContext(c) {
      !(c !== NO_CONTEXT) ? invariant(false, 'Expected host context to exist. This error is likely caused by a bug in React. Please file an issue.') : void 0;
      return c;
    }
  
    function getRootHostContainer() {
      var rootInstance = requiredContext(rootInstanceStackCursor.current);
      return rootInstance;
    }
  
    function pushHostContainer(fiber, nextRootInstance) {
      // Push current root instance onto the stack;
      // This allows us to reset root when portals are popped.
      push(rootInstanceStackCursor, nextRootInstance, fiber);
  
      var nextRootContext = getRootHostContext(nextRootInstance);
  
      // Track the context and the Fiber that provided it.
      // This enables us to pop only Fibers that provide unique contexts.
      push(contextFiberStackCursor, fiber, fiber);
      push(contextStackCursor, nextRootContext, fiber);
    }
  
    function popHostContainer(fiber) {
      pop(contextStackCursor, fiber);
      pop(contextFiberStackCursor, fiber);
      pop(rootInstanceStackCursor, fiber);
    }
  
    function getHostContext() {
      var context = requiredContext(contextStackCursor.current);
      return context;
    }
  
    function pushHostContext(fiber) {
      var rootInstance = requiredContext(rootInstanceStackCursor.current);
      var context = requiredContext(contextStackCursor.current);
      var nextContext = getChildHostContext(context, fiber.type, rootInstance);
  
      // Don't push this Fiber's context unless it's unique.
      if (context === nextContext) {
        return;
      }
  
      // Track the context and the Fiber that provided it.
      // This enables us to pop only Fibers that provide unique contexts.
      push(contextFiberStackCursor, fiber, fiber);
      push(contextStackCursor, nextContext, fiber);
    }
  
    function popHostContext(fiber) {
      // Do not pop unless this Fiber provided the current context.
      // pushHostContext() only pushes Fibers that provide unique contexts.
      if (contextFiberStackCursor.current !== fiber) {
        return;
      }
  
      pop(contextStackCursor, fiber);
      pop(contextFiberStackCursor, fiber);
    }
  
    function resetHostContainer() {
      contextStackCursor.current = NO_CONTEXT;
      rootInstanceStackCursor.current = NO_CONTEXT;
    }
  
    return {
      getHostContext: getHostContext,
      getRootHostContainer: getRootHostContainer,
      popHostContainer: popHostContainer,
      popHostContext: popHostContext,
      pushHostContainer: pushHostContainer,
      pushHostContext: pushHostContext,
      resetHostContainer: resetHostContainer
    };
  };
  
  var ReactFiberHydrationContext = function (config) {
    var shouldSetTextContent = config.shouldSetTextContent,
        hydration = config.hydration;
  
    // If this doesn't have hydration mode.
  
    if (!hydration) {
      return {
        enterHydrationState: function () {
          return false;
        },
        resetHydrationState: function () {},
        tryToClaimNextHydratableInstance: function () {},
        prepareToHydrateHostInstance: function () {
          invariant(false, 'Expected prepareToHydrateHostInstance() to never be called. This error is likely caused by a bug in React. Please file an issue.');
        },
        prepareToHydrateHostTextInstance: function () {
          invariant(false, 'Expected prepareToHydrateHostTextInstance() to never be called. This error is likely caused by a bug in React. Please file an issue.');
        },
        popHydrationState: function (fiber) {
          return false;
        }
      };
    }
  
    var canHydrateInstance = hydration.canHydrateInstance,
        canHydrateTextInstance = hydration.canHydrateTextInstance,
        getNextHydratableSibling = hydration.getNextHydratableSibling,
        getFirstHydratableChild = hydration.getFirstHydratableChild,
        hydrateInstance = hydration.hydrateInstance,
        hydrateTextInstance = hydration.hydrateTextInstance,
        didNotMatchHydratedContainerTextInstance = hydration.didNotMatchHydratedContainerTextInstance,
        didNotMatchHydratedTextInstance = hydration.didNotMatchHydratedTextInstance,
        didNotHydrateContainerInstance = hydration.didNotHydrateContainerInstance,
        didNotHydrateInstance = hydration.didNotHydrateInstance,
        didNotFindHydratableContainerInstance = hydration.didNotFindHydratableContainerInstance,
        didNotFindHydratableContainerTextInstance = hydration.didNotFindHydratableContainerTextInstance,
        didNotFindHydratableInstance = hydration.didNotFindHydratableInstance,
        didNotFindHydratableTextInstance = hydration.didNotFindHydratableTextInstance;
  
    // The deepest Fiber on the stack involved in a hydration context.
    // This may have been an insertion or a hydration.
  
    var hydrationParentFiber = null;
    var nextHydratableInstance = null;
    var isHydrating = false;
  
    function enterHydrationState(fiber) {
      var parentInstance = fiber.stateNode.containerInfo;
      nextHydratableInstance = getFirstHydratableChild(parentInstance);
      hydrationParentFiber = fiber;
      isHydrating = true;
      return true;
    }
  
    function deleteHydratableInstance(returnFiber, instance) {
      {
        switch (returnFiber.tag) {
          case HostRoot:
            didNotHydrateContainerInstance(returnFiber.stateNode.containerInfo, instance);
            break;
          case HostComponent:
            didNotHydrateInstance(returnFiber.type, returnFiber.memoizedProps, returnFiber.stateNode, instance);
            break;
        }
      }
  
      var childToDelete = createFiberFromHostInstanceForDeletion();
      childToDelete.stateNode = instance;
      childToDelete['return'] = returnFiber;
      childToDelete.effectTag = Deletion;
  
      // This might seem like it belongs on progressedFirstDeletion. However,
      // these children are not part of the reconciliation list of children.
      // Even if we abort and rereconcile the children, that will try to hydrate
      // again and the nodes are still in the host tree so these will be
      // recreated.
      if (returnFiber.lastEffect !== null) {
        returnFiber.lastEffect.nextEffect = childToDelete;
        returnFiber.lastEffect = childToDelete;
      } else {
        returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
      }
    }
  
    function insertNonHydratedInstance(returnFiber, fiber) {
      fiber.effectTag |= Placement;
      {
        switch (returnFiber.tag) {
          case HostRoot:
            {
              var parentContainer = returnFiber.stateNode.containerInfo;
              switch (fiber.tag) {
                case HostComponent:
                  var type = fiber.type;
                  var props = fiber.pendingProps;
                  didNotFindHydratableContainerInstance(parentContainer, type, props);
                  break;
                case HostText:
                  var text = fiber.pendingProps;
                  didNotFindHydratableContainerTextInstance(parentContainer, text);
                  break;
              }
              break;
            }
          case HostComponent:
            {
              var parentType = returnFiber.type;
              var parentProps = returnFiber.memoizedProps;
              var parentInstance = returnFiber.stateNode;
              switch (fiber.tag) {
                case HostComponent:
                  var _type = fiber.type;
                  var _props = fiber.pendingProps;
                  didNotFindHydratableInstance(parentType, parentProps, parentInstance, _type, _props);
                  break;
                case HostText:
                  var _text = fiber.pendingProps;
                  didNotFindHydratableTextInstance(parentType, parentProps, parentInstance, _text);
                  break;
              }
              break;
            }
          default:
            return;
        }
      }
    }
  
    function tryHydrate(fiber, nextInstance) {
      switch (fiber.tag) {
        case HostComponent:
          {
            var type = fiber.type;
            var props = fiber.pendingProps;
            var instance = canHydrateInstance(nextInstance, type, props);
            if (instance !== null) {
              fiber.stateNode = instance;
              return true;
            }
            return false;
          }
        case HostText:
          {
            var text = fiber.pendingProps;
            var textInstance = canHydrateTextInstance(nextInstance, text);
            if (textInstance !== null) {
              fiber.stateNode = textInstance;
              return true;
            }
            return false;
          }
        default:
          return false;
      }
    }
  
    function tryToClaimNextHydratableInstance(fiber) {
      if (!isHydrating) {
        return;
      }
      var nextInstance = nextHydratableInstance;
      if (!nextInstance) {
        // Nothing to hydrate. Make it an insertion.
        insertNonHydratedInstance(hydrationParentFiber, fiber);
        isHydrating = false;
        hydrationParentFiber = fiber;
        return;
      }
      if (!tryHydrate(fiber, nextInstance)) {
        // If we can't hydrate this instance let's try the next one.
        // We use this as a heuristic. It's based on intuition and not data so it
        // might be flawed or unnecessary.
        nextInstance = getNextHydratableSibling(nextInstance);
        if (!nextInstance || !tryHydrate(fiber, nextInstance)) {
          // Nothing to hydrate. Make it an insertion.
          insertNonHydratedInstance(hydrationParentFiber, fiber);
          isHydrating = false;
          hydrationParentFiber = fiber;
          return;
        }
        // We matched the next one, we'll now assume that the first one was
        // superfluous and we'll delete it. Since we can't eagerly delete it
        // we'll have to schedule a deletion. To do that, this node needs a dummy
        // fiber associated with it.
        deleteHydratableInstance(hydrationParentFiber, nextHydratableInstance);
      }
      hydrationParentFiber = fiber;
      nextHydratableInstance = getFirstHydratableChild(nextInstance);
    }
  
    function prepareToHydrateHostInstance(fiber, rootContainerInstance, hostContext) {
      var instance = fiber.stateNode;
      var updatePayload = hydrateInstance(instance, fiber.type, fiber.memoizedProps, rootContainerInstance, hostContext, fiber);
      // TODO: Type this specific to this type of component.
      fiber.updateQueue = updatePayload;
      // If the update payload indicates that there is a change or if there
      // is a new ref we mark this as an update.
      if (updatePayload !== null) {
        return true;
      }
      return false;
    }
  
    function prepareToHydrateHostTextInstance(fiber) {
      var textInstance = fiber.stateNode;
      var textContent = fiber.memoizedProps;
      var shouldUpdate = hydrateTextInstance(textInstance, textContent, fiber);
      {
        if (shouldUpdate) {
          // We assume that prepareToHydrateHostTextInstance is called in a context where the
          // hydration parent is the parent host component of this host text.
          var returnFiber = hydrationParentFiber;
          if (returnFiber !== null) {
            switch (returnFiber.tag) {
              case HostRoot:
                {
                  var parentContainer = returnFiber.stateNode.containerInfo;
                  didNotMatchHydratedContainerTextInstance(parentContainer, textInstance, textContent);
                  break;
                }
              case HostComponent:
                {
                  var parentType = returnFiber.type;
                  var parentProps = returnFiber.memoizedProps;
                  var parentInstance = returnFiber.stateNode;
                  didNotMatchHydratedTextInstance(parentType, parentProps, parentInstance, textInstance, textContent);
                  break;
                }
            }
          }
        }
      }
      return shouldUpdate;
    }
  
    function popToNextHostParent(fiber) {
      var parent = fiber['return'];
      while (parent !== null && parent.tag !== HostComponent && parent.tag !== HostRoot) {
        parent = parent['return'];
      }
      hydrationParentFiber = parent;
    }
  
    function popHydrationState(fiber) {
      if (fiber !== hydrationParentFiber) {
        // We're deeper than the current hydration context, inside an inserted
        // tree.
        return false;
      }
      if (!isHydrating) {
        // If we're not currently hydrating but we're in a hydration context, then
        // we were an insertion and now need to pop up reenter hydration of our
        // siblings.
        popToNextHostParent(fiber);
        isHydrating = true;
        return false;
      }
  
      var type = fiber.type;
  
      // If we have any remaining hydratable nodes, we need to delete them now.
      // We only do this deeper than head and body since they tend to have random
      // other nodes in them. We also ignore components with pure text content in
      // side of them.
      // TODO: Better heuristic.
      if (fiber.tag !== HostComponent || type !== 'head' && type !== 'body' && !shouldSetTextContent(type, fiber.memoizedProps)) {
        var nextInstance = nextHydratableInstance;
        while (nextInstance) {
          deleteHydratableInstance(fiber, nextInstance);
          nextInstance = getNextHydratableSibling(nextInstance);
        }
      }
  
      popToNextHostParent(fiber);
      nextHydratableInstance = hydrationParentFiber ? getNextHydratableSibling(fiber.stateNode) : null;
      return true;
    }
  
    function resetHydrationState() {
      hydrationParentFiber = null;
      nextHydratableInstance = null;
      isHydrating = false;
    }
  
    return {
      enterHydrationState: enterHydrationState,
      resetHydrationState: resetHydrationState,
      tryToClaimNextHydratableInstance: tryToClaimNextHydratableInstance,
      prepareToHydrateHostInstance: prepareToHydrateHostInstance,
      prepareToHydrateHostTextInstance: prepareToHydrateHostTextInstance,
      popHydrationState: popHydrationState
    };
  };
  
  // This lets us hook into Fiber to debug what it's doing.
  // See https://github.com/facebook/react/pull/8033.
  // This is not part of the public API, not even for React DevTools.
  // You may only inject a debugTool if you work on React Fiber itself.
  var ReactFiberInstrumentation = {
    debugTool: null
  };
  
  var ReactFiberInstrumentation_1 = ReactFiberInstrumentation;
  
  var defaultShowDialog = function (capturedError) {
    return true;
  };
  
  var showDialog = defaultShowDialog;
  
  function logCapturedError(capturedError) {
    var logError = showDialog(capturedError);
  
    // Allow injected showDialog() to prevent default console.error logging.
    // This enables renderers like ReactNative to better manage redbox behavior.
    if (logError === false) {
      return;
    }
  
    var error = capturedError.error;
    var suppressLogging = error && error.suppressReactErrorLogging;
    if (suppressLogging) {
      return;
    }
  
    {
      var componentName = capturedError.componentName,
          componentStack = capturedError.componentStack,
          errorBoundaryName = capturedError.errorBoundaryName,
          errorBoundaryFound = capturedError.errorBoundaryFound,
          willRetry = capturedError.willRetry;
  
  
      var componentNameMessage = componentName ? 'The above error occurred in the <' + componentName + '> component:' : 'The above error occurred in one of your React components:';
  
      var errorBoundaryMessage = void 0;
      // errorBoundaryFound check is sufficient; errorBoundaryName check is to satisfy Flow.
      if (errorBoundaryFound && errorBoundaryName) {
        if (willRetry) {
          errorBoundaryMessage = 'React will try to recreate this component tree from scratch ' + ('using the error boundary you provided, ' + errorBoundaryName + '.');
        } else {
          errorBoundaryMessage = 'This error was initially handled by the error boundary ' + errorBoundaryName + '.\n' + 'Recreating the tree from scratch failed so React will unmount the tree.';
        }
      } else {
        errorBoundaryMessage = 'Consider adding an error boundary to your tree to customize error handling behavior.\n' + 'Visit https://fb.me/react-error-boundaries to learn more about error boundaries.';
      }
      var combinedMessage = '' + componentNameMessage + componentStack + '\n\n' + ('' + errorBoundaryMessage);
  
      // In development, we provide our own message with just the component stack.
      // We don't include the original error message and JS stack because the browser
      // has already printed it. Even if the application swallows the error, it is still
      // displayed by the browser thanks to the DEV-only fake event trick in ReactErrorUtils.
      console.error(combinedMessage);
    }
  }
  
  var invokeGuardedCallback$1 = ReactErrorUtils.invokeGuardedCallback;
  var hasCaughtError = ReactErrorUtils.hasCaughtError;
  var clearCaughtError = ReactErrorUtils.clearCaughtError;
  
  
  {
    var didWarnAboutStateTransition = false;
    var didWarnSetStateChildContext = false;
    var didWarnStateUpdateForUnmountedComponent = {};
  
    var warnAboutUpdateOnUnmounted = function (fiber) {
      var componentName = getComponentName(fiber) || 'ReactClass';
      if (didWarnStateUpdateForUnmountedComponent[componentName]) {
        return;
      }
      warning(false, 'Can only update a mounted or mounting ' + 'component. This usually means you called setState, replaceState, ' + 'or forceUpdate on an unmounted component. This is a no-op.\n\nPlease ' + 'check the code for the %s component.', componentName);
      didWarnStateUpdateForUnmountedComponent[componentName] = true;
    };
  
    var warnAboutInvalidUpdates = function (instance) {
      switch (ReactDebugCurrentFiber.phase) {
        case 'getChildContext':
          if (didWarnSetStateChildContext) {
            return;
          }
          warning(false, 'setState(...): Cannot call setState() inside getChildContext()');
          didWarnSetStateChildContext = true;
          break;
        case 'render':
          if (didWarnAboutStateTransition) {
            return;
          }
          warning(false, 'Cannot update during an existing state transition (such as within ' + "`render` or another component's constructor). Render methods should " + 'be a pure function of props and state; constructor side-effects are ' + 'an anti-pattern, but can be moved to `componentWillMount`.');
          didWarnAboutStateTransition = true;
          break;
      }
    };
  }
  
  var ReactFiberScheduler = function (config) {
    var hostContext = ReactFiberHostContext(config);
    var hydrationContext = ReactFiberHydrationContext(config);
    var popHostContainer = hostContext.popHostContainer,
        popHostContext = hostContext.popHostContext,
        resetHostContainer = hostContext.resetHostContainer;
  
    var _ReactFiberBeginWork = ReactFiberBeginWork(config, hostContext, hydrationContext, scheduleWork, computeExpirationForFiber),
        beginWork = _ReactFiberBeginWork.beginWork,
        beginFailedWork = _ReactFiberBeginWork.beginFailedWork;
  
    var _ReactFiberCompleteWo = ReactFiberCompleteWork(config, hostContext, hydrationContext),
        completeWork = _ReactFiberCompleteWo.completeWork;
  
    var _ReactFiberCommitWork = ReactFiberCommitWork(config, captureError),
        commitResetTextContent = _ReactFiberCommitWork.commitResetTextContent,
        commitPlacement = _ReactFiberCommitWork.commitPlacement,
        commitDeletion = _ReactFiberCommitWork.commitDeletion,
        commitWork = _ReactFiberCommitWork.commitWork,
        commitLifeCycles = _ReactFiberCommitWork.commitLifeCycles,
        commitAttachRef = _ReactFiberCommitWork.commitAttachRef,
        commitDetachRef = _ReactFiberCommitWork.commitDetachRef;
  
    var now = config.now,
        scheduleDeferredCallback = config.scheduleDeferredCallback,
        cancelDeferredCallback = config.cancelDeferredCallback,
        useSyncScheduling = config.useSyncScheduling,
        prepareForCommit = config.prepareForCommit,
        resetAfterCommit = config.resetAfterCommit;
  
    // Represents the current time in ms.
  
    var startTime = now();
    var mostRecentCurrentTime = msToExpirationTime(0);
  
    // Represents the expiration time that incoming updates should use. (If this
    // is NoWork, use the default strategy: async updates in async mode, sync
    // updates in sync mode.)
    var expirationContext = NoWork;
  
    var isWorking = false;
  
    // The next work in progress fiber that we're currently working on.
    var nextUnitOfWork = null;
    var nextRoot = null;
    // The time at which we're currently rendering work.
    var nextRenderExpirationTime = NoWork;
  
    // The next fiber with an effect that we're currently committing.
    var nextEffect = null;
  
    // Keep track of which fibers have captured an error that need to be handled.
    // Work is removed from this collection after componentDidCatch is called.
    var capturedErrors = null;
    // Keep track of which fibers have failed during the current batch of work.
    // This is a different set than capturedErrors, because it is not reset until
    // the end of the batch. This is needed to propagate errors correctly if a
    // subtree fails more than once.
    var failedBoundaries = null;
    // Error boundaries that captured an error during the current commit.
    var commitPhaseBoundaries = null;
    var firstUncaughtError = null;
    var didFatal = false;
  
    var isCommitting = false;
    var isUnmounting = false;
  
    // Used for performance tracking.
    var interruptedBy = null;
  
    function resetContextStack() {
      // Reset the stack
      reset$1();
      // Reset the cursors
      resetContext();
      resetHostContainer();
    }
  
    function commitAllHostEffects() {
      while (nextEffect !== null) {
        {
          ReactDebugCurrentFiber.setCurrentFiber(nextEffect);
        }
        recordEffect();
  
        var effectTag = nextEffect.effectTag;
        if (effectTag & ContentReset) {
          commitResetTextContent(nextEffect);
        }
  
        if (effectTag & Ref) {
          var current = nextEffect.alternate;
          if (current !== null) {
            commitDetachRef(current);
          }
        }
  
        // The following switch statement is only concerned about placement,
        // updates, and deletions. To avoid needing to add a case for every
        // possible bitmap value, we remove the secondary effects from the
        // effect tag and switch on that value.
        var primaryEffectTag = effectTag & ~(Callback | Err | ContentReset | Ref | PerformedWork);
        switch (primaryEffectTag) {
          case Placement:
            {
              commitPlacement(nextEffect);
              // Clear the "placement" from effect tag so that we know that this is inserted, before
              // any life-cycles like componentDidMount gets called.
              // TODO: findDOMNode doesn't rely on this any more but isMounted
              // does and isMounted is deprecated anyway so we should be able
              // to kill this.
              nextEffect.effectTag &= ~Placement;
              break;
            }
          case PlacementAndUpdate:
            {
              // Placement
              commitPlacement(nextEffect);
              // Clear the "placement" from effect tag so that we know that this is inserted, before
              // any life-cycles like componentDidMount gets called.
              nextEffect.effectTag &= ~Placement;
  
              // Update
              var _current = nextEffect.alternate;
              commitWork(_current, nextEffect);
              break;
            }
          case Update:
            {
              var _current2 = nextEffect.alternate;
              commitWork(_current2, nextEffect);
              break;
            }
          case Deletion:
            {
              isUnmounting = true;
              commitDeletion(nextEffect);
              isUnmounting = false;
              break;
            }
        }
        nextEffect = nextEffect.nextEffect;
      }
  
      {
        ReactDebugCurrentFiber.resetCurrentFiber();
      }
    }
  
    function commitAllLifeCycles() {
      while (nextEffect !== null) {
        var effectTag = nextEffect.effectTag;
  
        if (effectTag & (Update | Callback)) {
          recordEffect();
          var current = nextEffect.alternate;
          commitLifeCycles(current, nextEffect);
        }
  
        if (effectTag & Ref) {
          recordEffect();
          commitAttachRef(nextEffect);
        }
  
        if (effectTag & Err) {
          recordEffect();
          commitErrorHandling(nextEffect);
        }
  
        var next = nextEffect.nextEffect;
        // Ensure that we clean these up so that we don't accidentally keep them.
        // I'm not actually sure this matters because we can't reset firstEffect
        // and lastEffect since they're on every node, not just the effectful
        // ones. So we have to clean everything as we reuse nodes anyway.
        nextEffect.nextEffect = null;
        // Ensure that we reset the effectTag here so that we can rely on effect
        // tags to reason about the current life-cycle.
        nextEffect = next;
      }
    }
  
    function commitRoot(finishedWork) {
      // We keep track of this so that captureError can collect any boundaries
      // that capture an error during the commit phase. The reason these aren't
      // local to this function is because errors that occur during cWU are
      // captured elsewhere, to prevent the unmount from being interrupted.
      isWorking = true;
      isCommitting = true;
      startCommitTimer();
  
      var root = finishedWork.stateNode;
      !(root.current !== finishedWork) ? invariant(false, 'Cannot commit the same tree as before. This is probably a bug related to the return field. This error is likely caused by a bug in React. Please file an issue.') : void 0;
      root.isReadyForCommit = false;
  
      // Reset this to null before calling lifecycles
      ReactCurrentOwner.current = null;
  
      var firstEffect = void 0;
      if (finishedWork.effectTag > PerformedWork) {
        // A fiber's effect list consists only of its children, not itself. So if
        // the root has an effect, we need to add it to the end of the list. The
        // resulting list is the set that would belong to the root's parent, if
        // it had one; that is, all the effects in the tree including the root.
        if (finishedWork.lastEffect !== null) {
          finishedWork.lastEffect.nextEffect = finishedWork;
          firstEffect = finishedWork.firstEffect;
        } else {
          firstEffect = finishedWork;
        }
      } else {
        // There is no effect on the root.
        firstEffect = finishedWork.firstEffect;
      }
  
      prepareForCommit();
  
      // Commit all the side-effects within a tree. We'll do this in two passes.
      // The first pass performs all the host insertions, updates, deletions and
      // ref unmounts.
      nextEffect = firstEffect;
      startCommitHostEffectsTimer();
      while (nextEffect !== null) {
        var didError = false;
        var _error = void 0;
        {
          invokeGuardedCallback$1(null, commitAllHostEffects, null);
          if (hasCaughtError()) {
            didError = true;
            _error = clearCaughtError();
          }
        }
        if (didError) {
          !(nextEffect !== null) ? invariant(false, 'Should have next effect. This error is likely caused by a bug in React. Please file an issue.') : void 0;
          captureError(nextEffect, _error);
          // Clean-up
          if (nextEffect !== null) {
            nextEffect = nextEffect.nextEffect;
          }
        }
      }
      stopCommitHostEffectsTimer();
  
      resetAfterCommit();
  
      // The work-in-progress tree is now the current tree. This must come after
      // the first pass of the commit phase, so that the previous tree is still
      // current during componentWillUnmount, but before the second pass, so that
      // the finished work is current during componentDidMount/Update.
      root.current = finishedWork;
  
      // In the second pass we'll perform all life-cycles and ref callbacks.
      // Life-cycles happen as a separate pass so that all placements, updates,
      // and deletions in the entire tree have already been invoked.
      // This pass also triggers any renderer-specific initial effects.
      nextEffect = firstEffect;
      startCommitLifeCyclesTimer();
      while (nextEffect !== null) {
        var _didError = false;
        var _error2 = void 0;
        {
          invokeGuardedCallback$1(null, commitAllLifeCycles, null);
          if (hasCaughtError()) {
            _didError = true;
            _error2 = clearCaughtError();
          }
        }
        if (_didError) {
          !(nextEffect !== null) ? invariant(false, 'Should have next effect. This error is likely caused by a bug in React. Please file an issue.') : void 0;
          captureError(nextEffect, _error2);
          if (nextEffect !== null) {
            nextEffect = nextEffect.nextEffect;
          }
        }
      }
  
      isCommitting = false;
      isWorking = false;
      stopCommitLifeCyclesTimer();
      stopCommitTimer();
      if (typeof onCommitRoot === 'function') {
        onCommitRoot(finishedWork.stateNode);
      }
      if (true && ReactFiberInstrumentation_1.debugTool) {
        ReactFiberInstrumentation_1.debugTool.onCommitWork(finishedWork);
      }
  
      // If we caught any errors during this commit, schedule their boundaries
      // to update.
      if (commitPhaseBoundaries) {
        commitPhaseBoundaries.forEach(scheduleErrorRecovery);
        commitPhaseBoundaries = null;
      }
  
      if (firstUncaughtError !== null) {
        var _error3 = firstUncaughtError;
        firstUncaughtError = null;
        onUncaughtError(_error3);
      }
  
      var remainingTime = root.current.expirationTime;
  
      if (remainingTime === NoWork) {
        capturedErrors = null;
        failedBoundaries = null;
      }
  
      return remainingTime;
    }
  
    function resetExpirationTime(workInProgress, renderTime) {
      if (renderTime !== Never && workInProgress.expirationTime === Never) {
        // The children of this component are hidden. Don't bubble their
        // expiration times.
        return;
      }
  
      // Check for pending updates.
      var newExpirationTime = getUpdateExpirationTime(workInProgress);
  
      // TODO: Calls need to visit stateNode
  
      // Bubble up the earliest expiration time.
      var child = workInProgress.child;
      while (child !== null) {
        if (child.expirationTime !== NoWork && (newExpirationTime === NoWork || newExpirationTime > child.expirationTime)) {
          newExpirationTime = child.expirationTime;
        }
        child = child.sibling;
      }
      workInProgress.expirationTime = newExpirationTime;
    }
  
    function completeUnitOfWork(workInProgress) {
      while (true) {
        // The current, flushed, state of this fiber is the alternate.
        // Ideally nothing should rely on this, but relying on it here
        // means that we don't need an additional field on the work in
        // progress.
        var current = workInProgress.alternate;
        {
          ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
        }
        var next = completeWork(current, workInProgress, nextRenderExpirationTime);
        {
          ReactDebugCurrentFiber.resetCurrentFiber();
        }
  
        var returnFiber = workInProgress['return'];
        var siblingFiber = workInProgress.sibling;
  
        resetExpirationTime(workInProgress, nextRenderExpirationTime);
  
        if (next !== null) {
          stopWorkTimer(workInProgress);
          if (true && ReactFiberInstrumentation_1.debugTool) {
            ReactFiberInstrumentation_1.debugTool.onCompleteWork(workInProgress);
          }
          // If completing this work spawned new work, do that next. We'll come
          // back here again.
          return next;
        }
  
        if (returnFiber !== null) {
          // Append all the effects of the subtree and this fiber onto the effect
          // list of the parent. The completion order of the children affects the
          // side-effect order.
          if (returnFiber.firstEffect === null) {
            returnFiber.firstEffect = workInProgress.firstEffect;
          }
          if (workInProgress.lastEffect !== null) {
            if (returnFiber.lastEffect !== null) {
              returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
            }
            returnFiber.lastEffect = workInProgress.lastEffect;
          }
  
          // If this fiber had side-effects, we append it AFTER the children's
          // side-effects. We can perform certain side-effects earlier if
          // needed, by doing multiple passes over the effect list. We don't want
          // to schedule our own side-effect on our own list because if end up
          // reusing children we'll schedule this effect onto itself since we're
          // at the end.
          var effectTag = workInProgress.effectTag;
          // Skip both NoWork and PerformedWork tags when creating the effect list.
          // PerformedWork effect is read by React DevTools but shouldn't be committed.
          if (effectTag > PerformedWork) {
            if (returnFiber.lastEffect !== null) {
              returnFiber.lastEffect.nextEffect = workInProgress;
            } else {
              returnFiber.firstEffect = workInProgress;
            }
            returnFiber.lastEffect = workInProgress;
          }
        }
  
        stopWorkTimer(workInProgress);
        if (true && ReactFiberInstrumentation_1.debugTool) {
          ReactFiberInstrumentation_1.debugTool.onCompleteWork(workInProgress);
        }
  
        if (siblingFiber !== null) {
          // If there is more work to do in this returnFiber, do that next.
          return siblingFiber;
        } else if (returnFiber !== null) {
          // If there's no more work in this returnFiber. Complete the returnFiber.
          workInProgress = returnFiber;
          continue;
        } else {
          // We've reached the root.
          var root = workInProgress.stateNode;
          root.isReadyForCommit = true;
          return null;
        }
      }
  
      // Without this explicit null return Flow complains of invalid return type
      // TODO Remove the above while(true) loop
      // eslint-disable-next-line no-unreachable
      return null;
    }
  
    function performUnitOfWork(workInProgress) {
      // The current, flushed, state of this fiber is the alternate.
      // Ideally nothing should rely on this, but relying on it here
      // means that we don't need an additional field on the work in
      // progress.
      var current = workInProgress.alternate;
  
      // See if beginning this work spawns more work.
      startWorkTimer(workInProgress);
      {
        ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
      }
  
      var next = beginWork(current, workInProgress, nextRenderExpirationTime);
      {
        ReactDebugCurrentFiber.resetCurrentFiber();
      }
      if (true && ReactFiberInstrumentation_1.debugTool) {
        ReactFiberInstrumentation_1.debugTool.onBeginWork(workInProgress);
      }
  
      if (next === null) {
        // If this doesn't spawn new work, complete the current work.
        next = completeUnitOfWork(workInProgress);
      }
  
      ReactCurrentOwner.current = null;
  
      return next;
    }
  
    function performFailedUnitOfWork(workInProgress) {
      // The current, flushed, state of this fiber is the alternate.
      // Ideally nothing should rely on this, but relying on it here
      // means that we don't need an additional field on the work in
      // progress.
      var current = workInProgress.alternate;
  
      // See if beginning this work spawns more work.
      startWorkTimer(workInProgress);
      {
        ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
      }
      var next = beginFailedWork(current, workInProgress, nextRenderExpirationTime);
      {
        ReactDebugCurrentFiber.resetCurrentFiber();
      }
      if (true && ReactFiberInstrumentation_1.debugTool) {
        ReactFiberInstrumentation_1.debugTool.onBeginWork(workInProgress);
      }
  
      if (next === null) {
        // If this doesn't spawn new work, complete the current work.
        next = completeUnitOfWork(workInProgress);
      }
  
      ReactCurrentOwner.current = null;
  
      return next;
    }
  
    function workLoop(expirationTime) {
      if (capturedErrors !== null) {
        // If there are unhandled errors, switch to the slow work loop.
        // TODO: How to avoid this check in the fast path? Maybe the renderer
        // could keep track of which roots have unhandled errors and call a
        // forked version of renderRoot.
        slowWorkLoopThatChecksForFailedWork(expirationTime);
        return;
      }
      if (nextRenderExpirationTime === NoWork || nextRenderExpirationTime > expirationTime) {
        return;
      }
  
      if (nextRenderExpirationTime <= mostRecentCurrentTime) {
        // Flush all expired work.
        while (nextUnitOfWork !== null) {
          nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
        }
      } else {
        // Flush asynchronous work until the deadline runs out of time.
        while (nextUnitOfWork !== null && !shouldYield()) {
          nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
        }
      }
    }
  
    function slowWorkLoopThatChecksForFailedWork(expirationTime) {
      if (nextRenderExpirationTime === NoWork || nextRenderExpirationTime > expirationTime) {
        return;
      }
  
      if (nextRenderExpirationTime <= mostRecentCurrentTime) {
        // Flush all expired work.
        while (nextUnitOfWork !== null) {
          if (hasCapturedError(nextUnitOfWork)) {
            // Use a forked version of performUnitOfWork
            nextUnitOfWork = performFailedUnitOfWork(nextUnitOfWork);
          } else {
            nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
          }
        }
      } else {
        // Flush asynchronous work until the deadline runs out of time.
        while (nextUnitOfWork !== null && !shouldYield()) {
          if (hasCapturedError(nextUnitOfWork)) {
            // Use a forked version of performUnitOfWork
            nextUnitOfWork = performFailedUnitOfWork(nextUnitOfWork);
          } else {
            nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
          }
        }
      }
    }
  
    function renderRootCatchBlock(root, failedWork, boundary, expirationTime) {
      // We're going to restart the error boundary that captured the error.
      // Conceptually, we're unwinding the stack. We need to unwind the
      // context stack, too.
      unwindContexts(failedWork, boundary);
  
      // Restart the error boundary using a forked version of
      // performUnitOfWork that deletes the boundary's children. The entire
      // failed subree will be unmounted. During the commit phase, a special
      // lifecycle method is called on the error boundary, which triggers
      // a re-render.
      nextUnitOfWork = performFailedUnitOfWork(boundary);
  
      // Continue working.
      workLoop(expirationTime);
    }
  
    function renderRoot(root, expirationTime) {
      !!isWorking ? invariant(false, 'renderRoot was called recursively. This error is likely caused by a bug in React. Please file an issue.') : void 0;
      isWorking = true;
  
      // We're about to mutate the work-in-progress tree. If the root was pending
      // commit, it no longer is: we'll need to complete it again.
      root.isReadyForCommit = false;
  
      // Check if we're starting from a fresh stack, or if we're resuming from
      // previously yielded work.
      if (root !== nextRoot || expirationTime !== nextRenderExpirationTime || nextUnitOfWork === null) {
        // Reset the stack and start working from the root.
        resetContextStack();
        nextRoot = root;
        nextRenderExpirationTime = expirationTime;
        nextUnitOfWork = createWorkInProgress(nextRoot.current, null, expirationTime);
      }
  
      startWorkLoopTimer(nextUnitOfWork);
  
      var didError = false;
      var error = null;
      {
        invokeGuardedCallback$1(null, workLoop, null, expirationTime);
        if (hasCaughtError()) {
          didError = true;
          error = clearCaughtError();
        }
      }
  
      // An error was thrown during the render phase.
      while (didError) {
        if (didFatal) {
          // This was a fatal error. Don't attempt to recover from it.
          firstUncaughtError = error;
          break;
        }
  
        var failedWork = nextUnitOfWork;
        if (failedWork === null) {
          // An error was thrown but there's no current unit of work. This can
          // happen during the commit phase if there's a bug in the renderer.
          didFatal = true;
          continue;
        }
  
        // "Capture" the error by finding the nearest boundary. If there is no
        // error boundary, we use the root.
        var boundary = captureError(failedWork, error);
        !(boundary !== null) ? invariant(false, 'Should have found an error boundary. This error is likely caused by a bug in React. Please file an issue.') : void 0;
  
        if (didFatal) {
          // The error we just captured was a fatal error. This happens
          // when the error propagates to the root more than once.
          continue;
        }
  
        didError = false;
        error = null;
        {
          invokeGuardedCallback$1(null, renderRootCatchBlock, null, root, failedWork, boundary, expirationTime);
          if (hasCaughtError()) {
            didError = true;
            error = clearCaughtError();
            continue;
          }
        }
        // We're finished working. Exit the error loop.
        break;
      }
  
      var uncaughtError = firstUncaughtError;
  
      // We're done performing work. Time to clean up.
      stopWorkLoopTimer(interruptedBy);
      interruptedBy = null;
      isWorking = false;
      didFatal = false;
      firstUncaughtError = null;
  
      if (uncaughtError !== null) {
        onUncaughtError(uncaughtError);
      }
  
      return root.isReadyForCommit ? root.current.alternate : null;
    }
  
    // Returns the boundary that captured the error, or null if the error is ignored
    function captureError(failedWork, error) {
      // It is no longer valid because we exited the user code.
      ReactCurrentOwner.current = null;
      {
        ReactDebugCurrentFiber.resetCurrentFiber();
      }
  
      // Search for the nearest error boundary.
      var boundary = null;
  
      // Passed to logCapturedError()
      var errorBoundaryFound = false;
      var willRetry = false;
      var errorBoundaryName = null;
  
      // Host containers are a special case. If the failed work itself is a host
      // container, then it acts as its own boundary. In all other cases, we
      // ignore the work itself and only search through the parents.
      if (failedWork.tag === HostRoot) {
        boundary = failedWork;
  
        if (isFailedBoundary(failedWork)) {
          // If this root already failed, there must have been an error when
          // attempting to unmount it. This is a worst-case scenario and
          // should only be possible if there's a bug in the renderer.
          didFatal = true;
        }
      } else {
        var node = failedWork['return'];
        while (node !== null && boundary === null) {
          if (node.tag === ClassComponent) {
            var instance = node.stateNode;
            if (typeof instance.componentDidCatch === 'function') {
              errorBoundaryFound = true;
              errorBoundaryName = getComponentName(node);
  
              // Found an error boundary!
              boundary = node;
              willRetry = true;
            }
          } else if (node.tag === HostRoot) {
            // Treat the root like a no-op error boundary
            boundary = node;
          }
  
          if (isFailedBoundary(node)) {
            // This boundary is already in a failed state.
  
            // If we're currently unmounting, that means this error was
            // thrown while unmounting a failed subtree. We should ignore
            // the error.
            if (isUnmounting) {
              return null;
            }
  
            // If we're in the commit phase, we should check to see if
            // this boundary already captured an error during this commit.
            // This case exists because multiple errors can be thrown during
            // a single commit without interruption.
            if (commitPhaseBoundaries !== null && (commitPhaseBoundaries.has(node) || node.alternate !== null && commitPhaseBoundaries.has(node.alternate))) {
              // If so, we should ignore this error.
              return null;
            }
  
            // The error should propagate to the next boundary -— we keep looking.
            boundary = null;
            willRetry = false;
          }
  
          node = node['return'];
        }
      }
  
      if (boundary !== null) {
        // Add to the collection of failed boundaries. This lets us know that
        // subsequent errors in this subtree should propagate to the next boundary.
        if (failedBoundaries === null) {
          failedBoundaries = new Set();
        }
        failedBoundaries.add(boundary);
  
        // This method is unsafe outside of the begin and complete phases.
        // We might be in the commit phase when an error is captured.
        // The risk is that the return path from this Fiber may not be accurate.
        // That risk is acceptable given the benefit of providing users more context.
        var _componentStack = getStackAddendumByWorkInProgressFiber(failedWork);
        var _componentName = getComponentName(failedWork);
  
        // Add to the collection of captured errors. This is stored as a global
        // map of errors and their component stack location keyed by the boundaries
        // that capture them. We mostly use this Map as a Set; it's a Map only to
        // avoid adding a field to Fiber to store the error.
        if (capturedErrors === null) {
          capturedErrors = new Map();
        }
  
        var capturedError = {
          componentName: _componentName,
          componentStack: _componentStack,
          error: error,
          errorBoundary: errorBoundaryFound ? boundary.stateNode : null,
          errorBoundaryFound: errorBoundaryFound,
          errorBoundaryName: errorBoundaryName,
          willRetry: willRetry
        };
  
        capturedErrors.set(boundary, capturedError);
  
        try {
          logCapturedError(capturedError);
        } catch (e) {
          // Prevent cycle if logCapturedError() throws.
          // A cycle may still occur if logCapturedError renders a component that throws.
          var suppressLogging = e && e.suppressReactErrorLogging;
          if (!suppressLogging) {
            console.error(e);
          }
        }
  
        // If we're in the commit phase, defer scheduling an update on the
        // boundary until after the commit is complete
        if (isCommitting) {
          if (commitPhaseBoundaries === null) {
            commitPhaseBoundaries = new Set();
          }
          commitPhaseBoundaries.add(boundary);
        } else {
          // Otherwise, schedule an update now.
          // TODO: Is this actually necessary during the render phase? Is it
          // possible to unwind and continue rendering at the same priority,
          // without corrupting internal state?
          scheduleErrorRecovery(boundary);
        }
        return boundary;
      } else if (firstUncaughtError === null) {
        // If no boundary is found, we'll need to throw the error
        firstUncaughtError = error;
      }
      return null;
    }
  
    function hasCapturedError(fiber) {
      // TODO: capturedErrors should store the boundary instance, to avoid needing
      // to check the alternate.
      return capturedErrors !== null && (capturedErrors.has(fiber) || fiber.alternate !== null && capturedErrors.has(fiber.alternate));
    }
  
    function isFailedBoundary(fiber) {
      // TODO: failedBoundaries should store the boundary instance, to avoid
      // needing to check the alternate.
      return failedBoundaries !== null && (failedBoundaries.has(fiber) || fiber.alternate !== null && failedBoundaries.has(fiber.alternate));
    }
  
    function commitErrorHandling(effectfulFiber) {
      var capturedError = void 0;
      if (capturedErrors !== null) {
        capturedError = capturedErrors.get(effectfulFiber);
        capturedErrors['delete'](effectfulFiber);
        if (capturedError == null) {
          if (effectfulFiber.alternate !== null) {
            effectfulFiber = effectfulFiber.alternate;
            capturedError = capturedErrors.get(effectfulFiber);
            capturedErrors['delete'](effectfulFiber);
          }
        }
      }
  
      !(capturedError != null) ? invariant(false, 'No error for given unit of work. This error is likely caused by a bug in React. Please file an issue.') : void 0;
  
      switch (effectfulFiber.tag) {
        case ClassComponent:
          var instance = effectfulFiber.stateNode;
  
          var info = {
            componentStack: capturedError.componentStack
          };
  
          // Allow the boundary to handle the error, usually by scheduling
          // an update to itself
          instance.componentDidCatch(capturedError.error, info);
          return;
        case HostRoot:
          if (firstUncaughtError === null) {
            firstUncaughtError = capturedError.error;
          }
          return;
        default:
          invariant(false, 'Invalid type of work. This error is likely caused by a bug in React. Please file an issue.');
      }
    }
  
    function unwindContexts(from, to) {
      var node = from;
      while (node !== null) {
        switch (node.tag) {
          case ClassComponent:
            popContextProvider(node);
            break;
          case HostComponent:
            popHostContext(node);
            break;
          case HostRoot:
            popHostContainer(node);
            break;
          case HostPortal:
            popHostContainer(node);
            break;
        }
        if (node === to || node.alternate === to) {
          stopFailedWorkTimer(node);
          break;
        } else {
          stopWorkTimer(node);
        }
        node = node['return'];
      }
    }
  
    function computeAsyncExpiration() {
      // Given the current clock time, returns an expiration time. We use rounding
      // to batch like updates together.
      // Should complete within ~1000ms. 1200ms max.
      var currentTime = recalculateCurrentTime();
      var expirationMs = 1000;
      var bucketSizeMs = 200;
      return computeExpirationBucket(currentTime, expirationMs, bucketSizeMs);
    }
  
    function computeExpirationForFiber(fiber) {
      var expirationTime = void 0;
      if (expirationContext !== NoWork) {
        // An explicit expiration context was set;
        expirationTime = expirationContext;
      } else if (isWorking) {
        if (isCommitting) {
          // Updates that occur during the commit phase should have sync priority
          // by default.
          expirationTime = Sync;
        } else {
          // Updates during the render phase should expire at the same time as
          // the work that is being rendered.
          expirationTime = nextRenderExpirationTime;
        }
      } else {
        // No explicit expiration context was set, and we're not currently
        // performing work. Calculate a new expiration time.
        if (useSyncScheduling && !(fiber.internalContextTag & AsyncUpdates)) {
          // This is a sync update
          expirationTime = Sync;
        } else {
          // This is an async update
          expirationTime = computeAsyncExpiration();
        }
      }
      return expirationTime;
    }
  
    function scheduleWork(fiber, expirationTime) {
      return scheduleWorkImpl(fiber, expirationTime, false);
    }
  
    function checkRootNeedsClearing(root, fiber, expirationTime) {
      if (!isWorking && root === nextRoot && expirationTime < nextRenderExpirationTime) {
        // Restart the root from the top.
        if (nextUnitOfWork !== null) {
          // This is an interruption. (Used for performance tracking.)
          interruptedBy = fiber;
        }
        nextRoot = null;
        nextUnitOfWork = null;
        nextRenderExpirationTime = NoWork;
      }
    }
  
    function scheduleWorkImpl(fiber, expirationTime, isErrorRecovery) {
      recordScheduleUpdate();
  
      {
        if (!isErrorRecovery && fiber.tag === ClassComponent) {
          var instance = fiber.stateNode;
          warnAboutInvalidUpdates(instance);
        }
      }
  
      var node = fiber;
      while (node !== null) {
        // Walk the parent path to the root and update each node's
        // expiration time.
        if (node.expirationTime === NoWork || node.expirationTime > expirationTime) {
          node.expirationTime = expirationTime;
        }
        if (node.alternate !== null) {
          if (node.alternate.expirationTime === NoWork || node.alternate.expirationTime > expirationTime) {
            node.alternate.expirationTime = expirationTime;
          }
        }
        if (node['return'] === null) {
          if (node.tag === HostRoot) {
            var root = node.stateNode;
  
            checkRootNeedsClearing(root, fiber, expirationTime);
            requestWork(root, expirationTime);
            checkRootNeedsClearing(root, fiber, expirationTime);
          } else {
            {
              if (!isErrorRecovery && fiber.tag === ClassComponent) {
                warnAboutUpdateOnUnmounted(fiber);
              }
            }
            return;
          }
        }
        node = node['return'];
      }
    }
  
    function scheduleErrorRecovery(fiber) {
      scheduleWorkImpl(fiber, Sync, true);
    }
  
    function recalculateCurrentTime() {
      // Subtract initial time so it fits inside 32bits
      var ms = now() - startTime;
      mostRecentCurrentTime = msToExpirationTime(ms);
      return mostRecentCurrentTime;
    }
  
    function deferredUpdates(fn) {
      var previousExpirationContext = expirationContext;
      expirationContext = computeAsyncExpiration();
      try {
        return fn();
      } finally {
        expirationContext = previousExpirationContext;
      }
    }
  
    function syncUpdates(fn) {
      var previousExpirationContext = expirationContext;
      expirationContext = Sync;
      try {
        return fn();
      } finally {
        expirationContext = previousExpirationContext;
      }
    }
  
    // TODO: Everything below this is written as if it has been lifted to the
    // renderers. I'll do this in a follow-up.
  
    // Linked-list of roots
    var firstScheduledRoot = null;
    var lastScheduledRoot = null;
  
    var callbackExpirationTime = NoWork;
    var callbackID = -1;
    var isRendering = false;
    var nextFlushedRoot = null;
    var nextFlushedExpirationTime = NoWork;
    var deadlineDidExpire = false;
    var hasUnhandledError = false;
    var unhandledError = null;
    var deadline = null;
  
    var isBatchingUpdates = false;
    var isUnbatchingUpdates = false;
  
    // Use these to prevent an infinite loop of nested updates
    var NESTED_UPDATE_LIMIT = 1000;
    var nestedUpdateCount = 0;
  
    var timeHeuristicForUnitOfWork = 1;
  
    function scheduleCallbackWithExpiration(expirationTime) {
      if (callbackExpirationTime !== NoWork) {
        // A callback is already scheduled. Check its expiration time (timeout).
        if (expirationTime > callbackExpirationTime) {
          // Existing callback has sufficient timeout. Exit.
          return;
        } else {
          // Existing callback has insufficient timeout. Cancel and schedule a
          // new one.
          cancelDeferredCallback(callbackID);
        }
        // The request callback timer is already running. Don't start a new one.
      } else {
        startRequestCallbackTimer();
      }
  
      // Compute a timeout for the given expiration time.
      var currentMs = now() - startTime;
      var expirationMs = expirationTimeToMs(expirationTime);
      var timeout = expirationMs - currentMs;
  
      callbackExpirationTime = expirationTime;
      callbackID = scheduleDeferredCallback(performAsyncWork, { timeout: timeout });
    }
  
    // requestWork is called by the scheduler whenever a root receives an update.
    // It's up to the renderer to call renderRoot at some point in the future.
    function requestWork(root, expirationTime) {
      if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
        invariant(false, 'Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.');
      }
  
      // Add the root to the schedule.
      // Check if this root is already part of the schedule.
      if (root.nextScheduledRoot === null) {
        // This root is not already scheduled. Add it.
        root.remainingExpirationTime = expirationTime;
        if (lastScheduledRoot === null) {
          firstScheduledRoot = lastScheduledRoot = root;
          root.nextScheduledRoot = root;
        } else {
          lastScheduledRoot.nextScheduledRoot = root;
          lastScheduledRoot = root;
          lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
        }
      } else {
        // This root is already scheduled, but its priority may have increased.
        var remainingExpirationTime = root.remainingExpirationTime;
        if (remainingExpirationTime === NoWork || expirationTime < remainingExpirationTime) {
          // Update the priority.
          root.remainingExpirationTime = expirationTime;
        }
      }
  
      if (isRendering) {
        // Prevent reentrancy. Remaining work will be scheduled at the end of
        // the currently rendering batch.
        return;
      }
  
      if (isBatchingUpdates) {
        // Flush work at the end of the batch.
        if (isUnbatchingUpdates) {
          // ...unless we're inside unbatchedUpdates, in which case we should
          // flush it now.
          nextFlushedRoot = root;
          nextFlushedExpirationTime = Sync;
          performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime);
        }
        return;
      }
  
      // TODO: Get rid of Sync and use current time?
      if (expirationTime === Sync) {
        performWork(Sync, null);
      } else {
        scheduleCallbackWithExpiration(expirationTime);
      }
    }
  
    function findHighestPriorityRoot() {
      var highestPriorityWork = NoWork;
      var highestPriorityRoot = null;
  
      if (lastScheduledRoot !== null) {
        var previousScheduledRoot = lastScheduledRoot;
        var root = firstScheduledRoot;
        while (root !== null) {
          var remainingExpirationTime = root.remainingExpirationTime;
          if (remainingExpirationTime === NoWork) {
            // This root no longer has work. Remove it from the scheduler.
  
            // TODO: This check is redudant, but Flow is confused by the branch
            // below where we set lastScheduledRoot to null, even though we break
            // from the loop right after.
            !(previousScheduledRoot !== null && lastScheduledRoot !== null) ? invariant(false, 'Should have a previous and last root. This error is likely caused by a bug in React. Please file an issue.') : void 0;
            if (root === root.nextScheduledRoot) {
              // This is the only root in the list.
              root.nextScheduledRoot = null;
              firstScheduledRoot = lastScheduledRoot = null;
              break;
            } else if (root === firstScheduledRoot) {
              // This is the first root in the list.
              var next = root.nextScheduledRoot;
              firstScheduledRoot = next;
              lastScheduledRoot.nextScheduledRoot = next;
              root.nextScheduledRoot = null;
            } else if (root === lastScheduledRoot) {
              // This is the last root in the list.
              lastScheduledRoot = previousScheduledRoot;
              lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
              root.nextScheduledRoot = null;
              break;
            } else {
              previousScheduledRoot.nextScheduledRoot = root.nextScheduledRoot;
              root.nextScheduledRoot = null;
            }
            root = previousScheduledRoot.nextScheduledRoot;
          } else {
            if (highestPriorityWork === NoWork || remainingExpirationTime < highestPriorityWork) {
              // Update the priority, if it's higher
              highestPriorityWork = remainingExpirationTime;
              highestPriorityRoot = root;
            }
            if (root === lastScheduledRoot) {
              break;
            }
            previousScheduledRoot = root;
            root = root.nextScheduledRoot;
          }
        }
      }
  
      // If the next root is the same as the previous root, this is a nested
      // update. To prevent an infinite loop, increment the nested update count.
      var previousFlushedRoot = nextFlushedRoot;
      if (previousFlushedRoot !== null && previousFlushedRoot === highestPriorityRoot) {
        nestedUpdateCount++;
      } else {
        // Reset whenever we switch roots.
        nestedUpdateCount = 0;
      }
      nextFlushedRoot = highestPriorityRoot;
      nextFlushedExpirationTime = highestPriorityWork;
    }
  
    function performAsyncWork(dl) {
      performWork(NoWork, dl);
    }
  
    function performWork(minExpirationTime, dl) {
      deadline = dl;
  
      // Keep working on roots until there's no more work, or until the we reach
      // the deadline.
      findHighestPriorityRoot();
  
      if (enableUserTimingAPI && deadline !== null) {
        var didExpire = nextFlushedExpirationTime < recalculateCurrentTime();
        stopRequestCallbackTimer(didExpire);
      }
  
      while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || nextFlushedExpirationTime <= minExpirationTime) && !deadlineDidExpire) {
        performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime);
        // Find the next highest priority work.
        findHighestPriorityRoot();
      }
  
      // We're done flushing work. Either we ran out of time in this callback,
      // or there's no more work left with sufficient priority.
  
      // If we're inside a callback, set this to false since we just completed it.
      if (deadline !== null) {
        callbackExpirationTime = NoWork;
        callbackID = -1;
      }
      // If there's work left over, schedule a new callback.
      if (nextFlushedExpirationTime !== NoWork) {
        scheduleCallbackWithExpiration(nextFlushedExpirationTime);
      }
  
      // Clean-up.
      deadline = null;
      deadlineDidExpire = false;
      nestedUpdateCount = 0;
  
      if (hasUnhandledError) {
        var _error4 = unhandledError;
        unhandledError = null;
        hasUnhandledError = false;
        throw _error4;
      }
    }
  
    function performWorkOnRoot(root, expirationTime) {
      !!isRendering ? invariant(false, 'performWorkOnRoot was called recursively. This error is likely caused by a bug in React. Please file an issue.') : void 0;
  
      isRendering = true;
  
      // Check if this is async work or sync/expired work.
      // TODO: Pass current time as argument to renderRoot, commitRoot
      if (expirationTime <= recalculateCurrentTime()) {
        // Flush sync work.
        var finishedWork = root.finishedWork;
        if (finishedWork !== null) {
          // This root is already complete. We can commit it.
          root.finishedWork = null;
          root.remainingExpirationTime = commitRoot(finishedWork);
        } else {
          root.finishedWork = null;
          finishedWork = renderRoot(root, expirationTime);
          if (finishedWork !== null) {
            // We've completed the root. Commit it.
            root.remainingExpirationTime = commitRoot(finishedWork);
          }
        }
      } else {
        // Flush async work.
        var _finishedWork = root.finishedWork;
        if (_finishedWork !== null) {
          // This root is already complete. We can commit it.
          root.finishedWork = null;
          root.remainingExpirationTime = commitRoot(_finishedWork);
        } else {
          root.finishedWork = null;
          _finishedWork = renderRoot(root, expirationTime);
          if (_finishedWork !== null) {
            // We've completed the root. Check the deadline one more time
            // before committing.
            if (!shouldYield()) {
              // Still time left. Commit the root.
              root.remainingExpirationTime = commitRoot(_finishedWork);
            } else {
              // There's no time left. Mark this root as complete. We'll come
              // back and commit it later.
              root.finishedWork = _finishedWork;
            }
          }
        }
      }
  
      isRendering = false;
    }
  
    // When working on async work, the reconciler asks the renderer if it should
    // yield execution. For DOM, we implement this with requestIdleCallback.
    function shouldYield() {
      if (deadline === null) {
        return false;
      }
      if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
        // Disregard deadline.didTimeout. Only expired work should be flushed
        // during a timeout. This path is only hit for non-expired work.
        return false;
      }
      deadlineDidExpire = true;
      return true;
    }
  
    // TODO: Not happy about this hook. Conceptually, renderRoot should return a
    // tuple of (isReadyForCommit, didError, error)
    function onUncaughtError(error) {
      !(nextFlushedRoot !== null) ? invariant(false, 'Should be working on a root. This error is likely caused by a bug in React. Please file an issue.') : void 0;
      // Unschedule this root so we don't work on it again until there's
      // another update.
      nextFlushedRoot.remainingExpirationTime = NoWork;
      if (!hasUnhandledError) {
        hasUnhandledError = true;
        unhandledError = error;
      }
    }
  
    // TODO: Batching should be implemented at the renderer level, not inside
    // the reconciler.
    function batchedUpdates(fn, a) {
      var previousIsBatchingUpdates = isBatchingUpdates;
      isBatchingUpdates = true;
      try {
        return fn(a);
      } finally {
        isBatchingUpdates = previousIsBatchingUpdates;
        if (!isBatchingUpdates && !isRendering) {
          performWork(Sync, null);
        }
      }
    }
  
    // TODO: Batching should be implemented at the renderer level, not inside
    // the reconciler.
    function unbatchedUpdates(fn) {
      if (isBatchingUpdates && !isUnbatchingUpdates) {
        isUnbatchingUpdates = true;
        try {
          return fn();
        } finally {
          isUnbatchingUpdates = false;
        }
      }
      return fn();
    }
  
    // TODO: Batching should be implemented at the renderer level, not within
    // the reconciler.
    function flushSync(fn) {
      var previousIsBatchingUpdates = isBatchingUpdates;
      isBatchingUpdates = true;
      try {
        return syncUpdates(fn);
      } finally {
        isBatchingUpdates = previousIsBatchingUpdates;
        !!isRendering ? invariant(false, 'flushSync was called from inside a lifecycle method. It cannot be called when React is already rendering.') : void 0;
        performWork(Sync, null);
      }
    }
  
    return {
      computeAsyncExpiration: computeAsyncExpiration,
      computeExpirationForFiber: computeExpirationForFiber,
      scheduleWork: scheduleWork,
      batchedUpdates: batchedUpdates,
      unbatchedUpdates: unbatchedUpdates,
      flushSync: flushSync,
      deferredUpdates: deferredUpdates
    };
  };
  
  {
    var didWarnAboutNestedUpdates = false;
  }
  
  // 0 is PROD, 1 is DEV.
  // Might add PROFILE later.
  
  
  function getContextForSubtree(parentComponent) {
    if (!parentComponent) {
      return emptyObject;
    }
  
    var fiber = get(parentComponent);
    var parentContext = findCurrentUnmaskedContext(fiber);
    return isContextProvider(fiber) ? processChildContext(fiber, parentContext) : parentContext;
  }
  
  var ReactFiberReconciler$1 = function (config) {
    var getPublicInstance = config.getPublicInstance;
  
    var _ReactFiberScheduler = ReactFiberScheduler(config),
        computeAsyncExpiration = _ReactFiberScheduler.computeAsyncExpiration,
        computeExpirationForFiber = _ReactFiberScheduler.computeExpirationForFiber,
        scheduleWork = _ReactFiberScheduler.scheduleWork,
        batchedUpdates = _ReactFiberScheduler.batchedUpdates,
        unbatchedUpdates = _ReactFiberScheduler.unbatchedUpdates,
        flushSync = _ReactFiberScheduler.flushSync,
        deferredUpdates = _ReactFiberScheduler.deferredUpdates;
  
    function scheduleTopLevelUpdate(current, element, callback) {
      {
        if (ReactDebugCurrentFiber.phase === 'render' && ReactDebugCurrentFiber.current !== null && !didWarnAboutNestedUpdates) {
          didWarnAboutNestedUpdates = true;
          warning(false, 'Render methods should be a pure function of props and state; ' + 'triggering nested component updates from render is not allowed. ' + 'If necessary, trigger nested updates in componentDidUpdate.\n\n' + 'Check the render method of %s.', getComponentName(ReactDebugCurrentFiber.current) || 'Unknown');
        }
      }
  
      callback = callback === undefined ? null : callback;
      {
        warning(callback === null || typeof callback === 'function', 'render(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback);
      }
  
      var expirationTime = void 0;
      // Check if the top-level element is an async wrapper component. If so,
      // treat updates to the root as async. This is a bit weird but lets us
      // avoid a separate `renderAsync` API.
      if (enableAsyncSubtreeAPI && element != null && element.type != null && element.type.prototype != null && element.type.prototype.unstable_isAsyncReactComponent === true) {
        expirationTime = computeAsyncExpiration();
      } else {
        expirationTime = computeExpirationForFiber(current);
      }
  
      var update = {
        expirationTime: expirationTime,
        partialState: { element: element },
        callback: callback,
        isReplace: false,
        isForced: false,
        nextCallback: null,
        next: null
      };
      insertUpdateIntoFiber(current, update);
      scheduleWork(current, expirationTime);
    }
  
    function findHostInstance(fiber) {
      var hostFiber = findCurrentHostFiber(fiber);
      if (hostFiber === null) {
        return null;
      }
      return hostFiber.stateNode;
    }
  
    return {
      createContainer: function (containerInfo, hydrate) {
        return createFiberRoot(containerInfo, hydrate);
      },
      updateContainer: function (element, container, parentComponent, callback) {
        // TODO: If this is a nested container, this won't be the root.
        var current = container.current;
  
        {
          if (ReactFiberInstrumentation_1.debugTool) {
            if (current.alternate === null) {
              ReactFiberInstrumentation_1.debugTool.onMountContainer(container);
            } else if (element === null) {
              ReactFiberInstrumentation_1.debugTool.onUnmountContainer(container);
            } else {
              ReactFiberInstrumentation_1.debugTool.onUpdateContainer(container);
            }
          }
        }
  
        var context = getContextForSubtree(parentComponent);
        if (container.context === null) {
          container.context = context;
        } else {
          container.pendingContext = context;
        }
  
        scheduleTopLevelUpdate(current, element, callback);
      },
  
  
      batchedUpdates: batchedUpdates,
  
      unbatchedUpdates: unbatchedUpdates,
  
      deferredUpdates: deferredUpdates,
  
      flushSync: flushSync,
  
      getPublicRootInstance: function (container) {
        var containerFiber = container.current;
        if (!containerFiber.child) {
          return null;
        }
        switch (containerFiber.child.tag) {
          case HostComponent:
            return getPublicInstance(containerFiber.child.stateNode);
          default:
            return containerFiber.child.stateNode;
        }
      },
  
  
      findHostInstance: findHostInstance,
  
      findHostInstanceWithNoPortals: function (fiber) {
        var hostFiber = findCurrentHostFiberWithNoPortals(fiber);
        if (hostFiber === null) {
          return null;
        }
        return hostFiber.stateNode;
      },
      injectIntoDevTools: function (devToolsConfig) {
        var findFiberByHostInstance = devToolsConfig.findFiberByHostInstance;
  
        return injectInternals(_assign({}, devToolsConfig, {
          findHostInstanceByFiber: function (fiber) {
            return findHostInstance(fiber);
          },
          findFiberByHostInstance: function (instance) {
            if (!findFiberByHostInstance) {
              // Might not be implemented by the renderer.
              return null;
            }
            return findFiberByHostInstance(instance);
          }
        }));
      }
    };
  };
  
  var ReactFiberReconciler$2 = Object.freeze({
    default: ReactFiberReconciler$1
  });
  
  var ReactFiberReconciler$3 = ( ReactFiberReconciler$2 && ReactFiberReconciler$1 ) || ReactFiberReconciler$2;
  
  // TODO: bundle Flow types with the package.
  
  
  
  // TODO: decide on the top-level export form.
  // This is hacky but makes it work with both Rollup and Jest.
  var reactReconciler = ReactFiberReconciler$3['default'] ? ReactFiberReconciler$3['default'] : ReactFiberReconciler$3;
  
  function createPortal$1(children, containerInfo,
  // TODO: figure out the API for cross-renderer implementation.
  implementation) {
    var key = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
  
    return {
      // This tag allow us to uniquely identify this as a React Portal
      $$typeof: REACT_PORTAL_TYPE,
      key: key == null ? null : '' + key,
      children: children,
      containerInfo: containerInfo,
      implementation: implementation
    };
  }
  
  // TODO: this is special because it gets imported during build.
  
  var ReactVersion = '16.2.0';
  
  // a requestAnimationFrame, storing the time for the start of the frame, then
  // scheduling a postMessage which gets scheduled after paint. Within the
  // postMessage handler do as much work as possible until time + frame rate.
  // By separating the idle call into a separate event tick we ensure that
  // layout, paint and other browser work is counted against the available time.
  // The frame rate is dynamically adjusted.
  
  {
    if (ExecutionEnvironment.canUseDOM && typeof requestAnimationFrame !== 'function') {
      warning(false, 'React depends on requestAnimationFrame. Make sure that you load a ' + 'polyfill in older browsers. http://fb.me/react-polyfills');
    }
  }
  
  var hasNativePerformanceNow = typeof performance === 'object' && typeof performance.now === 'function';
  
  var now = void 0;
  if (hasNativePerformanceNow) {
    now = function () {
      return performance.now();
    };
  } else {
    now = function () {
      return Date.now();
    };
  }
  
  // TODO: There's no way to cancel, because Fiber doesn't atm.
  var rIC = void 0;
  var cIC = void 0;
  
  if (!ExecutionEnvironment.canUseDOM) {
    rIC = function (frameCallback) {
      return setTimeout(function () {
        frameCallback({
          timeRemaining: function () {
            return Infinity;
          }
        });
      });
    };
    cIC = function (timeoutID) {
      clearTimeout(timeoutID);
    };
  } else if (typeof requestIdleCallback !== 'function' || typeof cancelIdleCallback !== 'function') {
    // Polyfill requestIdleCallback and cancelIdleCallback
  
    var scheduledRICCallback = null;
    var isIdleScheduled = false;
    var timeoutTime = -1;
  
    var isAnimationFrameScheduled = false;
  
    var frameDeadline = 0;
    // We start out assuming that we run at 30fps but then the heuristic tracking
    // will adjust this value to a faster fps if we get more frequent animation
    // frames.
    var previousFrameTime = 33;
    var activeFrameTime = 33;
  
    var frameDeadlineObject;
    if (hasNativePerformanceNow) {
      frameDeadlineObject = {
        didTimeout: false,
        timeRemaining: function () {
          // We assume that if we have a performance timer that the rAF callback
          // gets a performance timer value. Not sure if this is always true.
          var remaining = frameDeadline - performance.now();
          return remaining > 0 ? remaining : 0;
        }
      };
    } else {
      frameDeadlineObject = {
        didTimeout: false,
        timeRemaining: function () {
          // Fallback to Date.now()
          var remaining = frameDeadline - Date.now();
          return remaining > 0 ? remaining : 0;
        }
      };
    }
  
    // We use the postMessage trick to defer idle work until after the repaint.
    var messageKey = '__reactIdleCallback$' + Math.random().toString(36).slice(2);
    var idleTick = function (event) {
      if (event.source !== window || event.data !== messageKey) {
        return;
      }
  
      isIdleScheduled = false;
  
      var currentTime = now();
      if (frameDeadline - currentTime <= 0) {
        // There's no time left in this idle period. Check if the callback has
        // a timeout and whether it's been exceeded.
        if (timeoutTime !== -1 && timeoutTime <= currentTime) {
          // Exceeded the timeout. Invoke the callback even though there's no
          // time left.
          frameDeadlineObject.didTimeout = true;
        } else {
          // No timeout.
          if (!isAnimationFrameScheduled) {
            // Schedule another animation callback so we retry later.
            isAnimationFrameScheduled = true;
            requestAnimationFrame(animationTick);
          }
          // Exit without invoking the callback.
          return;
        }
      } else {
        // There's still time left in this idle period.
        frameDeadlineObject.didTimeout = false;
      }
  
      timeoutTime = -1;
      var callback = scheduledRICCallback;
      scheduledRICCallback = null;
      if (callback !== null) {
        callback(frameDeadlineObject);
      }
    };
    // Assumes that we have addEventListener in this environment. Might need
    // something better for old IE.
    window.addEventListener('message', idleTick, false);
  
    var animationTick = function (rafTime) {
      isAnimationFrameScheduled = false;
      var nextFrameTime = rafTime - frameDeadline + activeFrameTime;
      if (nextFrameTime < activeFrameTime && previousFrameTime < activeFrameTime) {
        if (nextFrameTime < 8) {
          // Defensive coding. We don't support higher frame rates than 120hz.
          // If we get lower than that, it is probably a bug.
          nextFrameTime = 8;
        }
        // If one frame goes long, then the next one can be short to catch up.
        // If two frames are short in a row, then that's an indication that we
        // actually have a higher frame rate than what we're currently optimizing.
        // We adjust our heuristic dynamically accordingly. For example, if we're
        // running on 120hz display or 90hz VR display.
        // Take the max of the two in case one of them was an anomaly due to
        // missed frame deadlines.
        activeFrameTime = nextFrameTime < previousFrameTime ? previousFrameTime : nextFrameTime;
      } else {
        previousFrameTime = nextFrameTime;
      }
      frameDeadline = rafTime + activeFrameTime;
      if (!isIdleScheduled) {
        isIdleScheduled = true;
        window.postMessage(messageKey, '*');
      }
    };
  
    rIC = function (callback, options) {
      // This assumes that we only schedule one callback at a time because that's
      // how Fiber uses it.
      scheduledRICCallback = callback;
      if (options != null && typeof options.timeout === 'number') {
        timeoutTime = now() + options.timeout;
      }
      if (!isAnimationFrameScheduled) {
        // If rAF didn't already schedule one, we need to schedule a frame.
        // TODO: If this rAF doesn't materialize because the browser throttles, we
        // might want to still have setTimeout trigger rIC as a backup to ensure
        // that we keep performing work.
        isAnimationFrameScheduled = true;
        requestAnimationFrame(animationTick);
      }
      return 0;
    };
  
    cIC = function () {
      scheduledRICCallback = null;
      isIdleScheduled = false;
      timeoutTime = -1;
    };
  } else {
    rIC = window.requestIdleCallback;
    cIC = window.cancelIdleCallback;
  }
  
  /**
   * Forked from fbjs/warning:
   * https://github.com/facebook/fbjs/blob/e66ba20ad5be433eb54423f2b097d829324d9de6/packages/fbjs/src/__forks__/warning.js
   *
   * Only change is we use console.warn instead of console.error,
   * and do nothing when 'console' is not supported.
   * This really simplifies the code.
   * ---
   * Similar to invariant but only logs a warning if the condition is not met.
   * This can be used to log issues in development environments in critical
   * paths. Removing the logging code for production environments will keep the
   * same logic and follow the same code paths.
   */
  
  var lowPriorityWarning = function () {};
  
  {
    var printWarning = function (format) {
      for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
        args[_key - 1] = arguments[_key];
      }
  
      var argIndex = 0;
      var message = 'Warning: ' + format.replace(/%s/g, function () {
        return args[argIndex++];
      });
      if (typeof console !== 'undefined') {
        console.warn(message);
      }
      try {
        // --- Welcome to debugging React ---
        // This error was thrown as a convenience so that you can use this stack
        // to find the callsite that caused this warning to fire.
        throw new Error(message);
      } catch (x) {}
    };
  
    lowPriorityWarning = function (condition, format) {
      if (format === undefined) {
        throw new Error('`warning(condition, format, ...args)` requires a warning ' + 'message argument');
      }
      if (!condition) {
        for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
          args[_key2 - 2] = arguments[_key2];
        }
  
        printWarning.apply(undefined, [format].concat(args));
      }
    };
  }
  
  var lowPriorityWarning$1 = lowPriorityWarning;
  
  // isAttributeNameSafe() is currently duplicated in DOMMarkupOperations.
  // TODO: Find a better place for this.
  var VALID_ATTRIBUTE_NAME_REGEX = new RegExp('^[' + ATTRIBUTE_NAME_START_CHAR + '][' + ATTRIBUTE_NAME_CHAR + ']*$');
  var illegalAttributeNameCache = {};
  var validatedAttributeNameCache = {};
  function isAttributeNameSafe(attributeName) {
    if (validatedAttributeNameCache.hasOwnProperty(attributeName)) {
      return true;
    }
    if (illegalAttributeNameCache.hasOwnProperty(attributeName)) {
      return false;
    }
    if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) {
      validatedAttributeNameCache[attributeName] = true;
      return true;
    }
    illegalAttributeNameCache[attributeName] = true;
    {
      warning(false, 'Invalid attribute name: `%s`', attributeName);
    }
    return false;
  }
  
  // shouldIgnoreValue() is currently duplicated in DOMMarkupOperations.
  // TODO: Find a better place for this.
  function shouldIgnoreValue(propertyInfo, value) {
    return value == null || propertyInfo.hasBooleanValue && !value || propertyInfo.hasNumericValue && isNaN(value) || propertyInfo.hasPositiveNumericValue && value < 1 || propertyInfo.hasOverloadedBooleanValue && value === false;
  }
  
  /**
   * Operations for dealing with DOM properties.
   */
  
  
  
  
  
  /**
   * Get the value for a property on a node. Only used in DEV for SSR validation.
   * The "expected" argument is used as a hint of what the expected value is.
   * Some properties have multiple equivalent values.
   */
  function getValueForProperty(node, name, expected) {
    {
      var propertyInfo = getPropertyInfo(name);
      if (propertyInfo) {
        var mutationMethod = propertyInfo.mutationMethod;
        if (mutationMethod || propertyInfo.mustUseProperty) {
          return node[propertyInfo.propertyName];
        } else {
          var attributeName = propertyInfo.attributeName;
  
          var stringValue = null;
  
          if (propertyInfo.hasOverloadedBooleanValue) {
            if (node.hasAttribute(attributeName)) {
              var value = node.getAttribute(attributeName);
              if (value === '') {
                return true;
              }
              if (shouldIgnoreValue(propertyInfo, expected)) {
                return value;
              }
              if (value === '' + expected) {
                return expected;
              }
              return value;
            }
          } else if (node.hasAttribute(attributeName)) {
            if (shouldIgnoreValue(propertyInfo, expected)) {
              // We had an attribute but shouldn't have had one, so read it
              // for the error message.
              return node.getAttribute(attributeName);
            }
            if (propertyInfo.hasBooleanValue) {
              // If this was a boolean, it doesn't matter what the value is
              // the fact that we have it is the same as the expected.
              return expected;
            }
            // Even if this property uses a namespace we use getAttribute
            // because we assume its namespaced name is the same as our config.
            // To use getAttributeNS we need the local name which we don't have
            // in our config atm.
            stringValue = node.getAttribute(attributeName);
          }
  
          if (shouldIgnoreValue(propertyInfo, expected)) {
            return stringValue === null ? expected : stringValue;
          } else if (stringValue === '' + expected) {
            return expected;
          } else {
            return stringValue;
          }
        }
      }
    }
  }
  
  /**
   * Get the value for a attribute on a node. Only used in DEV for SSR validation.
   * The third argument is used as a hint of what the expected value is. Some
   * attributes have multiple equivalent values.
   */
  function getValueForAttribute(node, name, expected) {
    {
      if (!isAttributeNameSafe(name)) {
        return;
      }
      if (!node.hasAttribute(name)) {
        return expected === undefined ? undefined : null;
      }
      var value = node.getAttribute(name);
      if (value === '' + expected) {
        return expected;
      }
      return value;
    }
  }
  
  /**
   * Sets the value for a property on a node.
   *
   * @param {DOMElement} node
   * @param {string} name
   * @param {*} value
   */
  function setValueForProperty(node, name, value) {
    var propertyInfo = getPropertyInfo(name);
  
    if (propertyInfo && shouldSetAttribute(name, value)) {
      var mutationMethod = propertyInfo.mutationMethod;
      if (mutationMethod) {
        mutationMethod(node, value);
      } else if (shouldIgnoreValue(propertyInfo, value)) {
        deleteValueForProperty(node, name);
        return;
      } else if (propertyInfo.mustUseProperty) {
        // Contrary to `setAttribute`, object properties are properly
        // `toString`ed by IE8/9.
        node[propertyInfo.propertyName] = value;
      } else {
        var attributeName = propertyInfo.attributeName;
        var namespace = propertyInfo.attributeNamespace;
        // `setAttribute` with objects becomes only `[object]` in IE8/9,
        // ('' + value) makes it output the correct toString()-value.
        if (namespace) {
          node.setAttributeNS(namespace, attributeName, '' + value);
        } else if (propertyInfo.hasBooleanValue || propertyInfo.hasOverloadedBooleanValue && value === true) {
          node.setAttribute(attributeName, '');
        } else {
          node.setAttribute(attributeName, '' + value);
        }
      }
    } else {
      setValueForAttribute(node, name, shouldSetAttribute(name, value) ? value : null);
      return;
    }
  
    {
      
    }
  }
  
  function setValueForAttribute(node, name, value) {
    if (!isAttributeNameSafe(name)) {
      return;
    }
    if (value == null) {
      node.removeAttribute(name);
    } else {
      node.setAttribute(name, '' + value);
    }
  
    {
      
    }
  }
  
  /**
   * Deletes an attributes from a node.
   *
   * @param {DOMElement} node
   * @param {string} name
   */
  function deleteValueForAttribute(node, name) {
    node.removeAttribute(name);
  }
  
  /**
   * Deletes the value for a property on a node.
   *
   * @param {DOMElement} node
   * @param {string} name
   */
  function deleteValueForProperty(node, name) {
    var propertyInfo = getPropertyInfo(name);
    if (propertyInfo) {
      var mutationMethod = propertyInfo.mutationMethod;
      if (mutationMethod) {
        mutationMethod(node, undefined);
      } else if (propertyInfo.mustUseProperty) {
        var propName = propertyInfo.propertyName;
        if (propertyInfo.hasBooleanValue) {
          node[propName] = false;
        } else {
          node[propName] = '';
        }
      } else {
        node.removeAttribute(propertyInfo.attributeName);
      }
    } else {
      node.removeAttribute(name);
    }
  }
  
  var ReactControlledValuePropTypes = {
    checkPropTypes: null
  };
  
  {
    var hasReadOnlyValue = {
      button: true,
      checkbox: true,
      image: true,
      hidden: true,
      radio: true,
      reset: true,
      submit: true
    };
  
    var propTypes = {
      value: function (props, propName, componentName) {
        if (!props[propName] || hasReadOnlyValue[props.type] || props.onChange || props.readOnly || props.disabled) {
          return null;
        }
        return new Error('You provided a `value` prop to a form field without an ' + '`onChange` handler. This will render a read-only field. If ' + 'the field should be mutable use `defaultValue`. Otherwise, ' + 'set either `onChange` or `readOnly`.');
      },
      checked: function (props, propName, componentName) {
        if (!props[propName] || props.onChange || props.readOnly || props.disabled) {
          return null;
        }
        return new Error('You provided a `checked` prop to a form field without an ' + '`onChange` handler. This will render a read-only field. If ' + 'the field should be mutable use `defaultChecked`. Otherwise, ' + 'set either `onChange` or `readOnly`.');
      }
    };
  
    /**
     * Provide a linked `value` attribute for controlled forms. You should not use
     * this outside of the ReactDOM controlled form components.
     */
    ReactControlledValuePropTypes.checkPropTypes = function (tagName, props, getStack) {
      checkPropTypes(propTypes, props, 'prop', tagName, getStack);
    };
  }
  
  // TODO: direct imports like some-package/src/* are bad. Fix me.
  var getCurrentFiberOwnerName$2 = ReactDebugCurrentFiber.getCurrentFiberOwnerName;
  var getCurrentFiberStackAddendum$3 = ReactDebugCurrentFiber.getCurrentFiberStackAddendum;
  
  var didWarnValueDefaultValue = false;
  var didWarnCheckedDefaultChecked = false;
  var didWarnControlledToUncontrolled = false;
  var didWarnUncontrolledToControlled = false;
  
  function isControlled(props) {
    var usesChecked = props.type === 'checkbox' || props.type === 'radio';
    return usesChecked ? props.checked != null : props.value != null;
  }
  
  /**
   * Implements an <input> host component that allows setting these optional
   * props: `checked`, `value`, `defaultChecked`, and `defaultValue`.
   *
   * If `checked` or `value` are not supplied (or null/undefined), user actions
   * that affect the checked state or value will trigger updates to the element.
   *
   * If they are supplied (and not null/undefined), the rendered element will not
   * trigger updates to the element. Instead, the props must change in order for
   * the rendered element to be updated.
   *
   * The rendered element will be initialized as unchecked (or `defaultChecked`)
   * with an empty value (or `defaultValue`).
   *
   * See http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html
   */
  
  function getHostProps(element, props) {
    var node = element;
    var value = props.value;
    var checked = props.checked;
  
    var hostProps = _assign({
      // Make sure we set .type before any other properties (setting .value
      // before .type means .value is lost in IE11 and below)
      type: undefined,
      // Make sure we set .step before .value (setting .value before .step
      // means .value is rounded on mount, based upon step precision)
      step: undefined,
      // Make sure we set .min & .max before .value (to ensure proper order
      // in corner cases such as min or max deriving from value, e.g. Issue #7170)
      min: undefined,
      max: undefined
    }, props, {
      defaultChecked: undefined,
      defaultValue: undefined,
      value: value != null ? value : node._wrapperState.initialValue,
      checked: checked != null ? checked : node._wrapperState.initialChecked
    });
  
    return hostProps;
  }
  
  function initWrapperState(element, props) {
    {
      ReactControlledValuePropTypes.checkPropTypes('input', props, getCurrentFiberStackAddendum$3);
  
      if (props.checked !== undefined && props.defaultChecked !== undefined && !didWarnCheckedDefaultChecked) {
        warning(false, '%s contains an input of type %s with both checked and defaultChecked props. ' + 'Input elements must be either controlled or uncontrolled ' + '(specify either the checked prop, or the defaultChecked prop, but not ' + 'both). Decide between using a controlled or uncontrolled input ' + 'element and remove one of these props. More info: ' + 'https://fb.me/react-controlled-components', getCurrentFiberOwnerName$2() || 'A component', props.type);
        didWarnCheckedDefaultChecked = true;
      }
      if (props.value !== undefined && props.defaultValue !== undefined && !didWarnValueDefaultValue) {
        warning(false, '%s contains an input of type %s with both value and defaultValue props. ' + 'Input elements must be either controlled or uncontrolled ' + '(specify either the value prop, or the defaultValue prop, but not ' + 'both). Decide between using a controlled or uncontrolled input ' + 'element and remove one of these props. More info: ' + 'https://fb.me/react-controlled-components', getCurrentFiberOwnerName$2() || 'A component', props.type);
        didWarnValueDefaultValue = true;
      }
    }
  
    var defaultValue = props.defaultValue;
    var node = element;
    node._wrapperState = {
      initialChecked: props.checked != null ? props.checked : props.defaultChecked,
      initialValue: props.value != null ? props.value : defaultValue,
      controlled: isControlled(props)
    };
  }
  
  function updateChecked(element, props) {
    var node = element;
    var checked = props.checked;
    if (checked != null) {
      setValueForProperty(node, 'checked', checked);
    }
  }
  
  function updateWrapper(element, props) {
    var node = element;
    {
      var controlled = isControlled(props);
  
      if (!node._wrapperState.controlled && controlled && !didWarnUncontrolledToControlled) {
        warning(false, 'A component is changing an uncontrolled input of type %s to be controlled. ' + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components%s', props.type, getCurrentFiberStackAddendum$3());
        didWarnUncontrolledToControlled = true;
      }
      if (node._wrapperState.controlled && !controlled && !didWarnControlledToUncontrolled) {
        warning(false, 'A component is changing a controlled input of type %s to be uncontrolled. ' + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components%s', props.type, getCurrentFiberStackAddendum$3());
        didWarnControlledToUncontrolled = true;
      }
    }
  
    updateChecked(element, props);
  
    var value = props.value;
    if (value != null) {
      if (value === 0 && node.value === '') {
        node.value = '0';
        // Note: IE9 reports a number inputs as 'text', so check props instead.
      } else if (props.type === 'number') {
        // Simulate `input.valueAsNumber`. IE9 does not support it
        var valueAsNumber = parseFloat(node.value) || 0;
  
        if (
        // eslint-disable-next-line
        value != valueAsNumber ||
        // eslint-disable-next-line
        value == valueAsNumber && node.value != value) {
          // Cast `value` to a string to ensure the value is set correctly. While
          // browsers typically do this as necessary, jsdom doesn't.
          node.value = '' + value;
        }
      } else if (node.value !== '' + value) {
        // Cast `value` to a string to ensure the value is set correctly. While
        // browsers typically do this as necessary, jsdom doesn't.
        node.value = '' + value;
      }
    } else {
      if (props.value == null && props.defaultValue != null) {
        // In Chrome, assigning defaultValue to certain input types triggers input validation.
        // For number inputs, the display value loses trailing decimal points. For email inputs,
        // Chrome raises "The specified value <x> is not a valid email address".
        //
        // Here we check to see if the defaultValue has actually changed, avoiding these problems
        // when the user is inputting text
        //
        // https://github.com/facebook/react/issues/7253
        if (node.defaultValue !== '' + props.defaultValue) {
          node.defaultValue = '' + props.defaultValue;
        }
      }
      if (props.checked == null && props.defaultChecked != null) {
        node.defaultChecked = !!props.defaultChecked;
      }
    }
  }
  
  function postMountWrapper(element, props) {
    var node = element;
  
    // Detach value from defaultValue. We won't do anything if we're working on
    // submit or reset inputs as those values & defaultValues are linked. They
    // are not resetable nodes so this operation doesn't matter and actually
    // removes browser-default values (eg "Submit Query") when no value is
    // provided.
  
    switch (props.type) {
      case 'submit':
      case 'reset':
        break;
      case 'color':
      case 'date':
      case 'datetime':
      case 'datetime-local':
      case 'month':
      case 'time':
      case 'week':
        // This fixes the no-show issue on iOS Safari and Android Chrome:
        // https://github.com/facebook/react/issues/7233
        node.value = '';
        node.value = node.defaultValue;
        break;
      default:
        node.value = node.value;
        break;
    }
  
    // Normally, we'd just do `node.checked = node.checked` upon initial mount, less this bug
    // this is needed to work around a chrome bug where setting defaultChecked
    // will sometimes influence the value of checked (even after detachment).
    // Reference: https://bugs.chromium.org/p/chromium/issues/detail?id=608416
    // We need to temporarily unset name to avoid disrupting radio button groups.
    var name = node.name;
    if (name !== '') {
      node.name = '';
    }
    node.defaultChecked = !node.defaultChecked;
    node.defaultChecked = !node.defaultChecked;
    if (name !== '') {
      node.name = name;
    }
  }
  
  function restoreControlledState$1(element, props) {
    var node = element;
    updateWrapper(node, props);
    updateNamedCousins(node, props);
  }
  
  function updateNamedCousins(rootNode, props) {
    var name = props.name;
    if (props.type === 'radio' && name != null) {
      var queryRoot = rootNode;
  
      while (queryRoot.parentNode) {
        queryRoot = queryRoot.parentNode;
      }
  
      // If `rootNode.form` was non-null, then we could try `form.elements`,
      // but that sometimes behaves strangely in IE8. We could also try using
      // `form.getElementsByName`, but that will only return direct children
      // and won't include inputs that use the HTML5 `form=` attribute. Since
      // the input might not even be in a form. It might not even be in the
      // document. Let's just use the local `querySelectorAll` to ensure we don't
      // miss anything.
      var group = queryRoot.querySelectorAll('input[name=' + JSON.stringify('' + name) + '][type="radio"]');
  
      for (var i = 0; i < group.length; i++) {
        var otherNode = group[i];
        if (otherNode === rootNode || otherNode.form !== rootNode.form) {
          continue;
        }
        // This will throw if radio buttons rendered by different copies of React
        // and the same name are rendered into the same form (same as #1939).
        // That's probably okay; we don't support it just as we don't support
        // mixing React radio buttons with non-React ones.
        var otherProps = getFiberCurrentPropsFromNode$1(otherNode);
        !otherProps ? invariant(false, 'ReactDOMInput: Mixing React and non-React radio inputs with the same `name` is not supported.') : void 0;
  
        // We need update the tracked value on the named cousin since the value
        // was changed but the input saw no event or value set
        updateValueIfChanged(otherNode);
  
        // If this is a controlled radio button group, forcing the input that
        // was previously checked to update will cause it to be come re-checked
        // as appropriate.
        updateWrapper(otherNode, otherProps);
      }
    }
  }
  
  function flattenChildren(children) {
    var content = '';
  
    // Flatten children and warn if they aren't strings or numbers;
    // invalid types are ignored.
    // We can silently skip them because invalid DOM nesting warning
    // catches these cases in Fiber.
    React.Children.forEach(children, function (child) {
      if (child == null) {
        return;
      }
      if (typeof child === 'string' || typeof child === 'number') {
        content += child;
      }
    });
  
    return content;
  }
  
  /**
   * Implements an <option> host component that warns when `selected` is set.
   */
  
  function validateProps(element, props) {
    // TODO (yungsters): Remove support for `selected` in <option>.
    {
      warning(props.selected == null, 'Use the `defaultValue` or `value` props on <select> instead of ' + 'setting `selected` on <option>.');
    }
  }
  
  function postMountWrapper$1(element, props) {
    // value="" should make a value attribute (#6219)
    if (props.value != null) {
      element.setAttribute('value', props.value);
    }
  }
  
  function getHostProps$1(element, props) {
    var hostProps = _assign({ children: undefined }, props);
    var content = flattenChildren(props.children);
  
    if (content) {
      hostProps.children = content;
    }
  
    return hostProps;
  }
  
  // TODO: direct imports like some-package/src/* are bad. Fix me.
  var getCurrentFiberOwnerName$3 = ReactDebugCurrentFiber.getCurrentFiberOwnerName;
  var getCurrentFiberStackAddendum$4 = ReactDebugCurrentFiber.getCurrentFiberStackAddendum;
  
  
  {
    var didWarnValueDefaultValue$1 = false;
  }
  
  function getDeclarationErrorAddendum() {
    var ownerName = getCurrentFiberOwnerName$3();
    if (ownerName) {
      return '\n\nCheck the render method of `' + ownerName + '`.';
    }
    return '';
  }
  
  var valuePropNames = ['value', 'defaultValue'];
  
  /**
   * Validation function for `value` and `defaultValue`.
   */
  function checkSelectPropTypes(props) {
    ReactControlledValuePropTypes.checkPropTypes('select', props, getCurrentFiberStackAddendum$4);
  
    for (var i = 0; i < valuePropNames.length; i++) {
      var propName = valuePropNames[i];
      if (props[propName] == null) {
        continue;
      }
      var isArray = Array.isArray(props[propName]);
      if (props.multiple && !isArray) {
        warning(false, 'The `%s` prop supplied to <select> must be an array if ' + '`multiple` is true.%s', propName, getDeclarationErrorAddendum());
      } else if (!props.multiple && isArray) {
        warning(false, 'The `%s` prop supplied to <select> must be a scalar ' + 'value if `multiple` is false.%s', propName, getDeclarationErrorAddendum());
      }
    }
  }
  
  function updateOptions(node, multiple, propValue, setDefaultSelected) {
    var options = node.options;
  
    if (multiple) {
      var selectedValues = propValue;
      var selectedValue = {};
      for (var i = 0; i < selectedValues.length; i++) {
        // Prefix to avoid chaos with special keys.
        selectedValue['$' + selectedValues[i]] = true;
      }
      for (var _i = 0; _i < options.length; _i++) {
        var selected = selectedValue.hasOwnProperty('$' + options[_i].value);
        if (options[_i].selected !== selected) {
          options[_i].selected = selected;
        }
        if (selected && setDefaultSelected) {
          options[_i].defaultSelected = true;
        }
      }
    } else {
      // Do not set `select.value` as exact behavior isn't consistent across all
      // browsers for all cases.
      var _selectedValue = '' + propValue;
      var defaultSelected = null;
      for (var _i2 = 0; _i2 < options.length; _i2++) {
        if (options[_i2].value === _selectedValue) {
          options[_i2].selected = true;
          if (setDefaultSelected) {
            options[_i2].defaultSelected = true;
          }
          return;
        }
        if (defaultSelected === null && !options[_i2].disabled) {
          defaultSelected = options[_i2];
        }
      }
      if (defaultSelected !== null) {
        defaultSelected.selected = true;
      }
    }
  }
  
  /**
   * Implements a <select> host component that allows optionally setting the
   * props `value` and `defaultValue`. If `multiple` is false, the prop must be a
   * stringable. If `multiple` is true, the prop must be an array of stringables.
   *
   * If `value` is not supplied (or null/undefined), user actions that change the
   * selected option will trigger updates to the rendered options.
   *
   * If it is supplied (and not null/undefined), the rendered options will not
   * update in response to user actions. Instead, the `value` prop must change in
   * order for the rendered options to update.
   *
   * If `defaultValue` is provided, any options with the supplied values will be
   * selected.
   */
  
  function getHostProps$2(element, props) {
    return _assign({}, props, {
      value: undefined
    });
  }
  
  function initWrapperState$1(element, props) {
    var node = element;
    {
      checkSelectPropTypes(props);
    }
  
    var value = props.value;
    node._wrapperState = {
      initialValue: value != null ? value : props.defaultValue,
      wasMultiple: !!props.multiple
    };
  
    {
      if (props.value !== undefined && props.defaultValue !== undefined && !didWarnValueDefaultValue$1) {
        warning(false, 'Select elements must be either controlled or uncontrolled ' + '(specify either the value prop, or the defaultValue prop, but not ' + 'both). Decide between using a controlled or uncontrolled select ' + 'element and remove one of these props. More info: ' + 'https://fb.me/react-controlled-components');
        didWarnValueDefaultValue$1 = true;
      }
    }
  }
  
  function postMountWrapper$2(element, props) {
    var node = element;
    node.multiple = !!props.multiple;
    var value = props.value;
    if (value != null) {
      updateOptions(node, !!props.multiple, value, false);
    } else if (props.defaultValue != null) {
      updateOptions(node, !!props.multiple, props.defaultValue, true);
    }
  }
  
  function postUpdateWrapper(element, props) {
    var node = element;
    // After the initial mount, we control selected-ness manually so don't pass
    // this value down
    node._wrapperState.initialValue = undefined;
  
    var wasMultiple = node._wrapperState.wasMultiple;
    node._wrapperState.wasMultiple = !!props.multiple;
  
    var value = props.value;
    if (value != null) {
      updateOptions(node, !!props.multiple, value, false);
    } else if (wasMultiple !== !!props.multiple) {
      // For simplicity, reapply `defaultValue` if `multiple` is toggled.
      if (props.defaultValue != null) {
        updateOptions(node, !!props.multiple, props.defaultValue, true);
      } else {
        // Revert the select back to its default unselected state.
        updateOptions(node, !!props.multiple, props.multiple ? [] : '', false);
      }
    }
  }
  
  function restoreControlledState$2(element, props) {
    var node = element;
    var value = props.value;
  
    if (value != null) {
      updateOptions(node, !!props.multiple, value, false);
    }
  }
  
  // TODO: direct imports like some-package/src/* are bad. Fix me.
  var getCurrentFiberStackAddendum$5 = ReactDebugCurrentFiber.getCurrentFiberStackAddendum;
  
  var didWarnValDefaultVal = false;
  
  /**
   * Implements a <textarea> host component that allows setting `value`, and
   * `defaultValue`. This differs from the traditional DOM API because value is
   * usually set as PCDATA children.
   *
   * If `value` is not supplied (or null/undefined), user actions that affect the
   * value will trigger updates to the element.
   *
   * If `value` is supplied (and not null/undefined), the rendered element will
   * not trigger updates to the element. Instead, the `value` prop must change in
   * order for the rendered element to be updated.
   *
   * The rendered element will be initialized with an empty value, the prop
   * `defaultValue` if specified, or the children content (deprecated).
   */
  
  function getHostProps$3(element, props) {
    var node = element;
    !(props.dangerouslySetInnerHTML == null) ? invariant(false, '`dangerouslySetInnerHTML` does not make sense on <textarea>.') : void 0;
  
    // Always set children to the same thing. In IE9, the selection range will
    // get reset if `textContent` is mutated.  We could add a check in setTextContent
    // to only set the value if/when the value differs from the node value (which would
    // completely solve this IE9 bug), but Sebastian+Sophie seemed to like this
    // solution. The value can be a boolean or object so that's why it's forced
    // to be a string.
    var hostProps = _assign({}, props, {
      value: undefined,
      defaultValue: undefined,
      children: '' + node._wrapperState.initialValue
    });
  
    return hostProps;
  }
  
  function initWrapperState$2(element, props) {
    var node = element;
    {
      ReactControlledValuePropTypes.checkPropTypes('textarea', props, getCurrentFiberStackAddendum$5);
      if (props.value !== undefined && props.defaultValue !== undefined && !didWarnValDefaultVal) {
        warning(false, 'Textarea elements must be either controlled or uncontrolled ' + '(specify either the value prop, or the defaultValue prop, but not ' + 'both). Decide between using a controlled or uncontrolled textarea ' + 'and remove one of these props. More info: ' + 'https://fb.me/react-controlled-components');
        didWarnValDefaultVal = true;
      }
    }
  
    var initialValue = props.value;
  
    // Only bother fetching default value if we're going to use it
    if (initialValue == null) {
      var defaultValue = props.defaultValue;
      // TODO (yungsters): Remove support for children content in <textarea>.
      var children = props.children;
      if (children != null) {
        {
          warning(false, 'Use the `defaultValue` or `value` props instead of setting ' + 'children on <textarea>.');
        }
        !(defaultValue == null) ? invariant(false, 'If you supply `defaultValue` on a <textarea>, do not pass children.') : void 0;
        if (Array.isArray(children)) {
          !(children.length <= 1) ? invariant(false, '<textarea> can only have at most one child.') : void 0;
          children = children[0];
        }
  
        defaultValue = '' + children;
      }
      if (defaultValue == null) {
        defaultValue = '';
      }
      initialValue = defaultValue;
    }
  
    node._wrapperState = {
      initialValue: '' + initialValue
    };
  }
  
  function updateWrapper$1(element, props) {
    var node = element;
    var value = props.value;
    if (value != null) {
      // Cast `value` to a string to ensure the value is set correctly. While
      // browsers typically do this as necessary, jsdom doesn't.
      var newValue = '' + value;
  
      // To avoid side effects (such as losing text selection), only set value if changed
      if (newValue !== node.value) {
        node.value = newValue;
      }
      if (props.defaultValue == null) {
        node.defaultValue = newValue;
      }
    }
    if (props.defaultValue != null) {
      node.defaultValue = props.defaultValue;
    }
  }
  
  function postMountWrapper$3(element, props) {
    var node = element;
    // This is in postMount because we need access to the DOM node, which is not
    // available until after the component has mounted.
    var textContent = node.textContent;
  
    // Only set node.value if textContent is equal to the expected
    // initial value. In IE10/IE11 there is a bug where the placeholder attribute
    // will populate textContent as well.
    // https://developer.microsoft.com/microsoft-edge/platform/issues/101525/
    if (textContent === node._wrapperState.initialValue) {
      node.value = textContent;
    }
  }
  
  function restoreControlledState$3(element, props) {
    // DOM component is still mounted; update
    updateWrapper$1(element, props);
  }
  
  var HTML_NAMESPACE$1 = 'http://www.w3.org/1999/xhtml';
  var MATH_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
  var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
  
  var Namespaces = {
    html: HTML_NAMESPACE$1,
    mathml: MATH_NAMESPACE,
    svg: SVG_NAMESPACE
  };
  
  // Assumes there is no parent namespace.
  function getIntrinsicNamespace(type) {
    switch (type) {
      case 'svg':
        return SVG_NAMESPACE;
      case 'math':
        return MATH_NAMESPACE;
      default:
        return HTML_NAMESPACE$1;
    }
  }
  
  function getChildNamespace(parentNamespace, type) {
    if (parentNamespace == null || parentNamespace === HTML_NAMESPACE$1) {
      // No (or default) parent namespace: potential entry point.
      return getIntrinsicNamespace(type);
    }
    if (parentNamespace === SVG_NAMESPACE && type === 'foreignObject') {
      // We're leaving SVG.
      return HTML_NAMESPACE$1;
    }
    // By default, pass namespace below.
    return parentNamespace;
  }
  
  /* globals MSApp */
  
  /**
   * Create a function which has 'unsafe' privileges (required by windows8 apps)
   */
  var createMicrosoftUnsafeLocalFunction = function (func) {
    if (typeof MSApp !== 'undefined' && MSApp.execUnsafeLocalFunction) {
      return function (arg0, arg1, arg2, arg3) {
        MSApp.execUnsafeLocalFunction(function () {
          return func(arg0, arg1, arg2, arg3);
        });
      };
    } else {
      return func;
    }
  };
  
  // SVG temp container for IE lacking innerHTML
  var reusableSVGContainer = void 0;
  
  /**
   * Set the innerHTML property of a node
   *
   * @param {DOMElement} node
   * @param {string} html
   * @internal
   */
  var setInnerHTML = createMicrosoftUnsafeLocalFunction(function (node, html) {
    // IE does not have innerHTML for SVG nodes, so instead we inject the
    // new markup in a temp node and then move the child nodes across into
    // the target node
  
    if (node.namespaceURI === Namespaces.svg && !('innerHTML' in node)) {
      reusableSVGContainer = reusableSVGContainer || document.createElement('div');
      reusableSVGContainer.innerHTML = '<svg>' + html + '</svg>';
      var svgNode = reusableSVGContainer.firstChild;
      while (node.firstChild) {
        node.removeChild(node.firstChild);
      }
      while (svgNode.firstChild) {
        node.appendChild(svgNode.firstChild);
      }
    } else {
      node.innerHTML = html;
    }
  });
  
  /**
   * Set the textContent property of a node, ensuring that whitespace is preserved
   * even in IE8. innerText is a poor substitute for textContent and, among many
   * issues, inserts <br> instead of the literal newline chars. innerHTML behaves
   * as it should.
   *
   * @param {DOMElement} node
   * @param {string} text
   * @internal
   */
  var setTextContent = function (node, text) {
    if (text) {
      var firstChild = node.firstChild;
  
      if (firstChild && firstChild === node.lastChild && firstChild.nodeType === TEXT_NODE) {
        firstChild.nodeValue = text;
        return;
      }
    }
    node.textContent = text;
  };
  
  /**
   * CSS properties which accept numbers but are not in units of "px".
   */
  var isUnitlessNumber = {
    animationIterationCount: true,
    borderImageOutset: true,
    borderImageSlice: true,
    borderImageWidth: true,
    boxFlex: true,
    boxFlexGroup: true,
    boxOrdinalGroup: true,
    columnCount: true,
    columns: true,
    flex: true,
    flexGrow: true,
    flexPositive: true,
    flexShrink: true,
    flexNegative: true,
    flexOrder: true,
    gridRow: true,
    gridRowEnd: true,
    gridRowSpan: true,
    gridRowStart: true,
    gridColumn: true,
    gridColumnEnd: true,
    gridColumnSpan: true,
    gridColumnStart: true,
    fontWeight: true,
    lineClamp: true,
    lineHeight: true,
    opacity: true,
    order: true,
    orphans: true,
    tabSize: true,
    widows: true,
    zIndex: true,
    zoom: true,
  
    // SVG-related properties
    fillOpacity: true,
    floodOpacity: true,
    stopOpacity: true,
    strokeDasharray: true,
    strokeDashoffset: true,
    strokeMiterlimit: true,
    strokeOpacity: true,
    strokeWidth: true
  };
  
  /**
   * @param {string} prefix vendor-specific prefix, eg: Webkit
   * @param {string} key style name, eg: transitionDuration
   * @return {string} style name prefixed with `prefix`, properly camelCased, eg:
   * WebkitTransitionDuration
   */
  function prefixKey(prefix, key) {
    return prefix + key.charAt(0).toUpperCase() + key.substring(1);
  }
  
  /**
   * Support style names that may come passed in prefixed by adding permutations
   * of vendor prefixes.
   */
  var prefixes = ['Webkit', 'ms', 'Moz', 'O'];
  
  // Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an
  // infinite loop, because it iterates over the newly added props too.
  Object.keys(isUnitlessNumber).forEach(function (prop) {
    prefixes.forEach(function (prefix) {
      isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop];
    });
  });
  
  /**
   * Convert a value into the proper css writable value. The style name `name`
   * should be logical (no hyphens), as specified
   * in `CSSProperty.isUnitlessNumber`.
   *
   * @param {string} name CSS property name such as `topMargin`.
   * @param {*} value CSS property value such as `10px`.
   * @return {string} Normalized style value with dimensions applied.
   */
  function dangerousStyleValue(name, value, isCustomProperty) {
    // Note that we've removed escapeTextForBrowser() calls here since the
    // whole string will be escaped when the attribute is injected into
    // the markup. If you provide unsafe user data here they can inject
    // arbitrary CSS which may be problematic (I couldn't repro this):
    // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
    // http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/
    // This is not an XSS hole but instead a potential CSS injection issue
    // which has lead to a greater discussion about how we're going to
    // trust URLs moving forward. See #2115901
  
    var isEmpty = value == null || typeof value === 'boolean' || value === '';
    if (isEmpty) {
      return '';
    }
  
    if (!isCustomProperty && typeof value === 'number' && value !== 0 && !(isUnitlessNumber.hasOwnProperty(name) && isUnitlessNumber[name])) {
      return value + 'px'; // Presumes implicit 'px' suffix for unitless numbers
    }
  
    return ('' + value).trim();
  }
  
  var warnValidStyle = emptyFunction;
  
  {
    // 'msTransform' is correct, but the other prefixes should be capitalized
    var badVendoredStyleNamePattern = /^(?:webkit|moz|o)[A-Z]/;
  
    // style values shouldn't contain a semicolon
    var badStyleValueWithSemicolonPattern = /;\s*$/;
  
    var warnedStyleNames = {};
    var warnedStyleValues = {};
    var warnedForNaNValue = false;
    var warnedForInfinityValue = false;
  
    var warnHyphenatedStyleName = function (name, getStack) {
      if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
        return;
      }
  
      warnedStyleNames[name] = true;
      warning(false, 'Unsupported style property %s. Did you mean %s?%s', name, camelizeStyleName(name), getStack());
    };
  
    var warnBadVendoredStyleName = function (name, getStack) {
      if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
        return;
      }
  
      warnedStyleNames[name] = true;
      warning(false, 'Unsupported vendor-prefixed style property %s. Did you mean %s?%s', name, name.charAt(0).toUpperCase() + name.slice(1), getStack());
    };
  
    var warnStyleValueWithSemicolon = function (name, value, getStack) {
      if (warnedStyleValues.hasOwnProperty(value) && warnedStyleValues[value]) {
        return;
      }
  
      warnedStyleValues[value] = true;
      warning(false, "Style property values shouldn't contain a semicolon. " + 'Try "%s: %s" instead.%s', name, value.replace(badStyleValueWithSemicolonPattern, ''), getStack());
    };
  
    var warnStyleValueIsNaN = function (name, value, getStack) {
      if (warnedForNaNValue) {
        return;
      }
  
      warnedForNaNValue = true;
      warning(false, '`NaN` is an invalid value for the `%s` css style property.%s', name, getStack());
    };
  
    var warnStyleValueIsInfinity = function (name, value, getStack) {
      if (warnedForInfinityValue) {
        return;
      }
  
      warnedForInfinityValue = true;
      warning(false, '`Infinity` is an invalid value for the `%s` css style property.%s', name, getStack());
    };
  
    warnValidStyle = function (name, value, getStack) {
      if (name.indexOf('-') > -1) {
        warnHyphenatedStyleName(name, getStack);
      } else if (badVendoredStyleNamePattern.test(name)) {
        warnBadVendoredStyleName(name, getStack);
      } else if (badStyleValueWithSemicolonPattern.test(value)) {
        warnStyleValueWithSemicolon(name, value, getStack);
      }
  
      if (typeof value === 'number') {
        if (isNaN(value)) {
          warnStyleValueIsNaN(name, value, getStack);
        } else if (!isFinite(value)) {
          warnStyleValueIsInfinity(name, value, getStack);
        }
      }
    };
  }
  
  var warnValidStyle$1 = warnValidStyle;
  
  /**
   * Operations for dealing with CSS properties.
   */
  
  /**
   * This creates a string that is expected to be equivalent to the style
   * attribute generated by server-side rendering. It by-passes warnings and
   * security checks so it's not safe to use this value for anything other than
   * comparison. It is only used in DEV for SSR validation.
   */
  function createDangerousStringForStyles(styles) {
    {
      var serialized = '';
      var delimiter = '';
      for (var styleName in styles) {
        if (!styles.hasOwnProperty(styleName)) {
          continue;
        }
        var styleValue = styles[styleName];
        if (styleValue != null) {
          var isCustomProperty = styleName.indexOf('--') === 0;
          serialized += delimiter + hyphenateStyleName(styleName) + ':';
          serialized += dangerousStyleValue(styleName, styleValue, isCustomProperty);
  
          delimiter = ';';
        }
      }
      return serialized || null;
    }
  }
  
  /**
   * Sets the value for multiple styles on a node.  If a value is specified as
   * '' (empty string), the corresponding style property will be unset.
   *
   * @param {DOMElement} node
   * @param {object} styles
   */
  function setValueForStyles(node, styles, getStack) {
    var style = node.style;
    for (var styleName in styles) {
      if (!styles.hasOwnProperty(styleName)) {
        continue;
      }
      var isCustomProperty = styleName.indexOf('--') === 0;
      {
        if (!isCustomProperty) {
          warnValidStyle$1(styleName, styles[styleName], getStack);
        }
      }
      var styleValue = dangerousStyleValue(styleName, styles[styleName], isCustomProperty);
      if (styleName === 'float') {
        styleName = 'cssFloat';
      }
      if (isCustomProperty) {
        style.setProperty(styleName, styleValue);
      } else {
        style[styleName] = styleValue;
      }
    }
  }
  
  // For HTML, certain tags should omit their close tag. We keep a whitelist for
  // those special-case tags.
  
  var omittedCloseTags = {
    area: true,
    base: true,
    br: true,
    col: true,
    embed: true,
    hr: true,
    img: true,
    input: true,
    keygen: true,
    link: true,
    meta: true,
    param: true,
    source: true,
    track: true,
    wbr: true
  };
  
  // For HTML, certain tags cannot have children. This has the same purpose as
  // `omittedCloseTags` except that `menuitem` should still have its closing tag.
  
  var voidElementTags = _assign({
    menuitem: true
  }, omittedCloseTags);
  
  var HTML$1 = '__html';
  
  function assertValidProps(tag, props, getStack) {
    if (!props) {
      return;
    }
    // Note the use of `==` which checks for null or undefined.
    if (voidElementTags[tag]) {
      !(props.children == null && props.dangerouslySetInnerHTML == null) ? invariant(false, '%s is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`.%s', tag, getStack()) : void 0;
    }
    if (props.dangerouslySetInnerHTML != null) {
      !(props.children == null) ? invariant(false, 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.') : void 0;
      !(typeof props.dangerouslySetInnerHTML === 'object' && HTML$1 in props.dangerouslySetInnerHTML) ? invariant(false, '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. Please visit https://fb.me/react-invariant-dangerously-set-inner-html for more information.') : void 0;
    }
    {
      warning(props.suppressContentEditableWarning || !props.contentEditable || props.children == null, 'A component is `contentEditable` and contains `children` managed by ' + 'React. It is now your responsibility to guarantee that none of ' + 'those nodes are unexpectedly modified or duplicated. This is ' + 'probably not intentional.%s', getStack());
    }
    !(props.style == null || typeof props.style === 'object') ? invariant(false, 'The `style` prop expects a mapping from style properties to values, not a string. For example, style={{marginRight: spacing + \'em\'}} when using JSX.%s', getStack()) : void 0;
  }
  
  function isCustomComponent(tagName, props) {
    if (tagName.indexOf('-') === -1) {
      return typeof props.is === 'string';
    }
    switch (tagName) {
      // These are reserved SVG and MathML elements.
      // We don't mind this whitelist too much because we expect it to never grow.
      // The alternative is to track the namespace in a few places which is convoluted.
      // https://w3c.github.io/webcomponents/spec/custom/#custom-elements-core-concepts
      case 'annotation-xml':
      case 'color-profile':
      case 'font-face':
      case 'font-face-src':
      case 'font-face-uri':
      case 'font-face-format':
      case 'font-face-name':
      case 'missing-glyph':
        return false;
      default:
        return true;
    }
  }
  
  var ariaProperties = {
    'aria-current': 0, // state
    'aria-details': 0,
    'aria-disabled': 0, // state
    'aria-hidden': 0, // state
    'aria-invalid': 0, // state
    'aria-keyshortcuts': 0,
    'aria-label': 0,
    'aria-roledescription': 0,
    // Widget Attributes
    'aria-autocomplete': 0,
    'aria-checked': 0,
    'aria-expanded': 0,
    'aria-haspopup': 0,
    'aria-level': 0,
    'aria-modal': 0,
    'aria-multiline': 0,
    'aria-multiselectable': 0,
    'aria-orientation': 0,
    'aria-placeholder': 0,
    'aria-pressed': 0,
    'aria-readonly': 0,
    'aria-required': 0,
    'aria-selected': 0,
    'aria-sort': 0,
    'aria-valuemax': 0,
    'aria-valuemin': 0,
    'aria-valuenow': 0,
    'aria-valuetext': 0,
    // Live Region Attributes
    'aria-atomic': 0,
    'aria-busy': 0,
    'aria-live': 0,
    'aria-relevant': 0,
    // Drag-and-Drop Attributes
    'aria-dropeffect': 0,
    'aria-grabbed': 0,
    // Relationship Attributes
    'aria-activedescendant': 0,
    'aria-colcount': 0,
    'aria-colindex': 0,
    'aria-colspan': 0,
    'aria-controls': 0,
    'aria-describedby': 0,
    'aria-errormessage': 0,
    'aria-flowto': 0,
    'aria-labelledby': 0,
    'aria-owns': 0,
    'aria-posinset': 0,
    'aria-rowcount': 0,
    'aria-rowindex': 0,
    'aria-rowspan': 0,
    'aria-setsize': 0
  };
  
  var warnedProperties = {};
  var rARIA = new RegExp('^(aria)-[' + ATTRIBUTE_NAME_CHAR + ']*$');
  var rARIACamel = new RegExp('^(aria)[A-Z][' + ATTRIBUTE_NAME_CHAR + ']*$');
  
  var hasOwnProperty = Object.prototype.hasOwnProperty;
  
  function getStackAddendum() {
    var stack = ReactDebugCurrentFrame.getStackAddendum();
    return stack != null ? stack : '';
  }
  
  function validateProperty(tagName, name) {
    if (hasOwnProperty.call(warnedProperties, name) && warnedProperties[name]) {
      return true;
    }
  
    if (rARIACamel.test(name)) {
      var ariaName = 'aria-' + name.slice(4).toLowerCase();
      var correctName = ariaProperties.hasOwnProperty(ariaName) ? ariaName : null;
  
      // If this is an aria-* attribute, but is not listed in the known DOM
      // DOM properties, then it is an invalid aria-* attribute.
      if (correctName == null) {
        warning(false, 'Invalid ARIA attribute `%s`. ARIA attributes follow the pattern aria-* and must be lowercase.%s', name, getStackAddendum());
        warnedProperties[name] = true;
        return true;
      }
      // aria-* attributes should be lowercase; suggest the lowercase version.
      if (name !== correctName) {
        warning(false, 'Invalid ARIA attribute `%s`. Did you mean `%s`?%s', name, correctName, getStackAddendum());
        warnedProperties[name] = true;
        return true;
      }
    }
  
    if (rARIA.test(name)) {
      var lowerCasedName = name.toLowerCase();
      var standardName = ariaProperties.hasOwnProperty(lowerCasedName) ? lowerCasedName : null;
  
      // If this is an aria-* attribute, but is not listed in the known DOM
      // DOM properties, then it is an invalid aria-* attribute.
      if (standardName == null) {
        warnedProperties[name] = true;
        return false;
      }
      // aria-* attributes should be lowercase; suggest the lowercase version.
      if (name !== standardName) {
        warning(false, 'Unknown ARIA attribute `%s`. Did you mean `%s`?%s', name, standardName, getStackAddendum());
        warnedProperties[name] = true;
        return true;
      }
    }
  
    return true;
  }
  
  function warnInvalidARIAProps(type, props) {
    var invalidProps = [];
  
    for (var key in props) {
      var isValid = validateProperty(type, key);
      if (!isValid) {
        invalidProps.push(key);
      }
    }
  
    var unknownPropString = invalidProps.map(function (prop) {
      return '`' + prop + '`';
    }).join(', ');
  
    if (invalidProps.length === 1) {
      warning(false, 'Invalid aria prop %s on <%s> tag. ' + 'For details, see https://fb.me/invalid-aria-prop%s', unknownPropString, type, getStackAddendum());
    } else if (invalidProps.length > 1) {
      warning(false, 'Invalid aria props %s on <%s> tag. ' + 'For details, see https://fb.me/invalid-aria-prop%s', unknownPropString, type, getStackAddendum());
    }
  }
  
  function validateProperties(type, props) {
    if (isCustomComponent(type, props)) {
      return;
    }
    warnInvalidARIAProps(type, props);
  }
  
  var didWarnValueNull = false;
  
  function getStackAddendum$1() {
    var stack = ReactDebugCurrentFrame.getStackAddendum();
    return stack != null ? stack : '';
  }
  
  function validateProperties$1(type, props) {
    if (type !== 'input' && type !== 'textarea' && type !== 'select') {
      return;
    }
  
    if (props != null && props.value === null && !didWarnValueNull) {
      didWarnValueNull = true;
      if (type === 'select' && props.multiple) {
        warning(false, '`value` prop on `%s` should not be null. ' + 'Consider using an empty array when `multiple` is set to `true` ' + 'to clear the component or `undefined` for uncontrolled components.%s', type, getStackAddendum$1());
      } else {
        warning(false, '`value` prop on `%s` should not be null. ' + 'Consider using an empty string to clear the component or `undefined` ' + 'for uncontrolled components.%s', type, getStackAddendum$1());
      }
    }
  }
  
  // When adding attributes to the HTML or SVG whitelist, be sure to
  // also add them to this module to ensure casing and incorrect name
  // warnings.
  var possibleStandardNames = {
    // HTML
    accept: 'accept',
    acceptcharset: 'acceptCharset',
    'accept-charset': 'acceptCharset',
    accesskey: 'accessKey',
    action: 'action',
    allowfullscreen: 'allowFullScreen',
    alt: 'alt',
    as: 'as',
    async: 'async',
    autocapitalize: 'autoCapitalize',
    autocomplete: 'autoComplete',
    autocorrect: 'autoCorrect',
    autofocus: 'autoFocus',
    autoplay: 'autoPlay',
    autosave: 'autoSave',
    capture: 'capture',
    cellpadding: 'cellPadding',
    cellspacing: 'cellSpacing',
    challenge: 'challenge',
    charset: 'charSet',
    checked: 'checked',
    children: 'children',
    cite: 'cite',
    'class': 'className',
    classid: 'classID',
    classname: 'className',
    cols: 'cols',
    colspan: 'colSpan',
    content: 'content',
    contenteditable: 'contentEditable',
    contextmenu: 'contextMenu',
    controls: 'controls',
    controlslist: 'controlsList',
    coords: 'coords',
    crossorigin: 'crossOrigin',
    dangerouslysetinnerhtml: 'dangerouslySetInnerHTML',
    data: 'data',
    datetime: 'dateTime',
    'default': 'default',
    defaultchecked: 'defaultChecked',
    defaultvalue: 'defaultValue',
    defer: 'defer',
    dir: 'dir',
    disabled: 'disabled',
    download: 'download',
    draggable: 'draggable',
    enctype: 'encType',
    'for': 'htmlFor',
    form: 'form',
    formmethod: 'formMethod',
    formaction: 'formAction',
    formenctype: 'formEncType',
    formnovalidate: 'formNoValidate',
    formtarget: 'formTarget',
    frameborder: 'frameBorder',
    headers: 'headers',
    height: 'height',
    hidden: 'hidden',
    high: 'high',
    href: 'href',
    hreflang: 'hrefLang',
    htmlfor: 'htmlFor',
    httpequiv: 'httpEquiv',
    'http-equiv': 'httpEquiv',
    icon: 'icon',
    id: 'id',
    innerhtml: 'innerHTML',
    inputmode: 'inputMode',
    integrity: 'integrity',
    is: 'is',
    itemid: 'itemID',
    itemprop: 'itemProp',
    itemref: 'itemRef',
    itemscope: 'itemScope',
    itemtype: 'itemType',
    keyparams: 'keyParams',
    keytype: 'keyType',
    kind: 'kind',
    label: 'label',
    lang: 'lang',
    list: 'list',
    loop: 'loop',
    low: 'low',
    manifest: 'manifest',
    marginwidth: 'marginWidth',
    marginheight: 'marginHeight',
    max: 'max',
    maxlength: 'maxLength',
    media: 'media',
    mediagroup: 'mediaGroup',
    method: 'method',
    min: 'min',
    minlength: 'minLength',
    multiple: 'multiple',
    muted: 'muted',
    name: 'name',
    nonce: 'nonce',
    novalidate: 'noValidate',
    open: 'open',
    optimum: 'optimum',
    pattern: 'pattern',
    placeholder: 'placeholder',
    playsinline: 'playsInline',
    poster: 'poster',
    preload: 'preload',
    profile: 'profile',
    radiogroup: 'radioGroup',
    readonly: 'readOnly',
    referrerpolicy: 'referrerPolicy',
    rel: 'rel',
    required: 'required',
    reversed: 'reversed',
    role: 'role',
    rows: 'rows',
    rowspan: 'rowSpan',
    sandbox: 'sandbox',
    scope: 'scope',
    scoped: 'scoped',
    scrolling: 'scrolling',
    seamless: 'seamless',
    selected: 'selected',
    shape: 'shape',
    size: 'size',
    sizes: 'sizes',
    span: 'span',
    spellcheck: 'spellCheck',
    src: 'src',
    srcdoc: 'srcDoc',
    srclang: 'srcLang',
    srcset: 'srcSet',
    start: 'start',
    step: 'step',
    style: 'style',
    summary: 'summary',
    tabindex: 'tabIndex',
    target: 'target',
    title: 'title',
    type: 'type',
    usemap: 'useMap',
    value: 'value',
    width: 'width',
    wmode: 'wmode',
    wrap: 'wrap',
  
    // SVG
    about: 'about',
    accentheight: 'accentHeight',
    'accent-height': 'accentHeight',
    accumulate: 'accumulate',
    additive: 'additive',
    alignmentbaseline: 'alignmentBaseline',
    'alignment-baseline': 'alignmentBaseline',
    allowreorder: 'allowReorder',
    alphabetic: 'alphabetic',
    amplitude: 'amplitude',
    arabicform: 'arabicForm',
    'arabic-form': 'arabicForm',
    ascent: 'ascent',
    attributename: 'attributeName',
    attributetype: 'attributeType',
    autoreverse: 'autoReverse',
    azimuth: 'azimuth',
    basefrequency: 'baseFrequency',
    baselineshift: 'baselineShift',
    'baseline-shift': 'baselineShift',
    baseprofile: 'baseProfile',
    bbox: 'bbox',
    begin: 'begin',
    bias: 'bias',
    by: 'by',
    calcmode: 'calcMode',
    capheight: 'capHeight',
    'cap-height': 'capHeight',
    clip: 'clip',
    clippath: 'clipPath',
    'clip-path': 'clipPath',
    clippathunits: 'clipPathUnits',
    cliprule: 'clipRule',
    'clip-rule': 'clipRule',
    color: 'color',
    colorinterpolation: 'colorInterpolation',
    'color-interpolation': 'colorInterpolation',
    colorinterpolationfilters: 'colorInterpolationFilters',
    'color-interpolation-filters': 'colorInterpolationFilters',
    colorprofile: 'colorProfile',
    'color-profile': 'colorProfile',
    colorrendering: 'colorRendering',
    'color-rendering': 'colorRendering',
    contentscripttype: 'contentScriptType',
    contentstyletype: 'contentStyleType',
    cursor: 'cursor',
    cx: 'cx',
    cy: 'cy',
    d: 'd',
    datatype: 'datatype',
    decelerate: 'decelerate',
    descent: 'descent',
    diffuseconstant: 'diffuseConstant',
    direction: 'direction',
    display: 'display',
    divisor: 'divisor',
    dominantbaseline: 'dominantBaseline',
    'dominant-baseline': 'dominantBaseline',
    dur: 'dur',
    dx: 'dx',
    dy: 'dy',
    edgemode: 'edgeMode',
    elevation: 'elevation',
    enablebackground: 'enableBackground',
    'enable-background': 'enableBackground',
    end: 'end',
    exponent: 'exponent',
    externalresourcesrequired: 'externalResourcesRequired',
    fill: 'fill',
    fillopacity: 'fillOpacity',
    'fill-opacity': 'fillOpacity',
    fillrule: 'fillRule',
    'fill-rule': 'fillRule',
    filter: 'filter',
    filterres: 'filterRes',
    filterunits: 'filterUnits',
    floodopacity: 'floodOpacity',
    'flood-opacity': 'floodOpacity',
    floodcolor: 'floodColor',
    'flood-color': 'floodColor',
    focusable: 'focusable',
    fontfamily: 'fontFamily',
    'font-family': 'fontFamily',
    fontsize: 'fontSize',
    'font-size': 'fontSize',
    fontsizeadjust: 'fontSizeAdjust',
    'font-size-adjust': 'fontSizeAdjust',
    fontstretch: 'fontStretch',
    'font-stretch': 'fontStretch',
    fontstyle: 'fontStyle',
    'font-style': 'fontStyle',
    fontvariant: 'fontVariant',
    'font-variant': 'fontVariant',
    fontweight: 'fontWeight',
    'font-weight': 'fontWeight',
    format: 'format',
    from: 'from',
    fx: 'fx',
    fy: 'fy',
    g1: 'g1',
    g2: 'g2',
    glyphname: 'glyphName',
    'glyph-name': 'glyphName',
    glyphorientationhorizontal: 'glyphOrientationHorizontal',
    'glyph-orientation-horizontal': 'glyphOrientationHorizontal',
    glyphorientationvertical: 'glyphOrientationVertical',
    'glyph-orientation-vertical': 'glyphOrientationVertical',
    glyphref: 'glyphRef',
    gradienttransform: 'gradientTransform',
    gradientunits: 'gradientUnits',
    hanging: 'hanging',
    horizadvx: 'horizAdvX',
    'horiz-adv-x': 'horizAdvX',
    horizoriginx: 'horizOriginX',
    'horiz-origin-x': 'horizOriginX',
    ideographic: 'ideographic',
    imagerendering: 'imageRendering',
    'image-rendering': 'imageRendering',
    in2: 'in2',
    'in': 'in',
    inlist: 'inlist',
    intercept: 'intercept',
    k1: 'k1',
    k2: 'k2',
    k3: 'k3',
    k4: 'k4',
    k: 'k',
    kernelmatrix: 'kernelMatrix',
    kernelunitlength: 'kernelUnitLength',
    kerning: 'kerning',
    keypoints: 'keyPoints',
    keysplines: 'keySplines',
    keytimes: 'keyTimes',
    lengthadjust: 'lengthAdjust',
    letterspacing: 'letterSpacing',
    'letter-spacing': 'letterSpacing',
    lightingcolor: 'lightingColor',
    'lighting-color': 'lightingColor',
    limitingconeangle: 'limitingConeAngle',
    local: 'local',
    markerend: 'markerEnd',
    'marker-end': 'markerEnd',
    markerheight: 'markerHeight',
    markermid: 'markerMid',
    'marker-mid': 'markerMid',
    markerstart: 'markerStart',
    'marker-start': 'markerStart',
    markerunits: 'markerUnits',
    markerwidth: 'markerWidth',
    mask: 'mask',
    maskcontentunits: 'maskContentUnits',
    maskunits: 'maskUnits',
    mathematical: 'mathematical',
    mode: 'mode',
    numoctaves: 'numOctaves',
    offset: 'offset',
    opacity: 'opacity',
    operator: 'operator',
    order: 'order',
    orient: 'orient',
    orientation: 'orientation',
    origin: 'origin',
    overflow: 'overflow',
    overlineposition: 'overlinePosition',
    'overline-position': 'overlinePosition',
    overlinethickness: 'overlineThickness',
    'overline-thickness': 'overlineThickness',
    paintorder: 'paintOrder',
    'paint-order': 'paintOrder',
    panose1: 'panose1',
    'panose-1': 'panose1',
    pathlength: 'pathLength',
    patterncontentunits: 'patternContentUnits',
    patterntransform: 'patternTransform',
    patternunits: 'patternUnits',
    pointerevents: 'pointerEvents',
    'pointer-events': 'pointerEvents',
    points: 'points',
    pointsatx: 'pointsAtX',
    pointsaty: 'pointsAtY',
    pointsatz: 'pointsAtZ',
    prefix: 'prefix',
    preservealpha: 'preserveAlpha',
    preserveaspectratio: 'preserveAspectRatio',
    primitiveunits: 'primitiveUnits',
    property: 'property',
    r: 'r',
    radius: 'radius',
    refx: 'refX',
    refy: 'refY',
    renderingintent: 'renderingIntent',
    'rendering-intent': 'renderingIntent',
    repeatcount: 'repeatCount',
    repeatdur: 'repeatDur',
    requiredextensions: 'requiredExtensions',
    requiredfeatures: 'requiredFeatures',
    resource: 'resource',
    restart: 'restart',
    result: 'result',
    results: 'results',
    rotate: 'rotate',
    rx: 'rx',
    ry: 'ry',
    scale: 'scale',
    security: 'security',
    seed: 'seed',
    shaperendering: 'shapeRendering',
    'shape-rendering': 'shapeRendering',
    slope: 'slope',
    spacing: 'spacing',
    specularconstant: 'specularConstant',
    specularexponent: 'specularExponent',
    speed: 'speed',
    spreadmethod: 'spreadMethod',
    startoffset: 'startOffset',
    stddeviation: 'stdDeviation',
    stemh: 'stemh',
    stemv: 'stemv',
    stitchtiles: 'stitchTiles',
    stopcolor: 'stopColor',
    'stop-color': 'stopColor',
    stopopacity: 'stopOpacity',
    'stop-opacity': 'stopOpacity',
    strikethroughposition: 'strikethroughPosition',
    'strikethrough-position': 'strikethroughPosition',
    strikethroughthickness: 'strikethroughThickness',
    'strikethrough-thickness': 'strikethroughThickness',
    string: 'string',
    stroke: 'stroke',
    strokedasharray: 'strokeDasharray',
    'stroke-dasharray': 'strokeDasharray',
    strokedashoffset: 'strokeDashoffset',
    'stroke-dashoffset': 'strokeDashoffset',
    strokelinecap: 'strokeLinecap',
    'stroke-linecap': 'strokeLinecap',
    strokelinejoin: 'strokeLinejoin',
    'stroke-linejoin': 'strokeLinejoin',
    strokemiterlimit: 'strokeMiterlimit',
    'stroke-miterlimit': 'strokeMiterlimit',
    strokewidth: 'strokeWidth',
    'stroke-width': 'strokeWidth',
    strokeopacity: 'strokeOpacity',
    'stroke-opacity': 'strokeOpacity',
    suppresscontenteditablewarning: 'suppressContentEditableWarning',
    suppresshydrationwarning: 'suppressHydrationWarning',
    surfacescale: 'surfaceScale',
    systemlanguage: 'systemLanguage',
    tablevalues: 'tableValues',
    targetx: 'targetX',
    targety: 'targetY',
    textanchor: 'textAnchor',
    'text-anchor': 'textAnchor',
    textdecoration: 'textDecoration',
    'text-decoration': 'textDecoration',
    textlength: 'textLength',
    textrendering: 'textRendering',
    'text-rendering': 'textRendering',
    to: 'to',
    transform: 'transform',
    'typeof': 'typeof',
    u1: 'u1',
    u2: 'u2',
    underlineposition: 'underlinePosition',
    'underline-position': 'underlinePosition',
    underlinethickness: 'underlineThickness',
    'underline-thickness': 'underlineThickness',
    unicode: 'unicode',
    unicodebidi: 'unicodeBidi',
    'unicode-bidi': 'unicodeBidi',
    unicoderange: 'unicodeRange',
    'unicode-range': 'unicodeRange',
    unitsperem: 'unitsPerEm',
    'units-per-em': 'unitsPerEm',
    unselectable: 'unselectable',
    valphabetic: 'vAlphabetic',
    'v-alphabetic': 'vAlphabetic',
    values: 'values',
    vectoreffect: 'vectorEffect',
    'vector-effect': 'vectorEffect',
    version: 'version',
    vertadvy: 'vertAdvY',
    'vert-adv-y': 'vertAdvY',
    vertoriginx: 'vertOriginX',
    'vert-origin-x': 'vertOriginX',
    vertoriginy: 'vertOriginY',
    'vert-origin-y': 'vertOriginY',
    vhanging: 'vHanging',
    'v-hanging': 'vHanging',
    videographic: 'vIdeographic',
    'v-ideographic': 'vIdeographic',
    viewbox: 'viewBox',
    viewtarget: 'viewTarget',
    visibility: 'visibility',
    vmathematical: 'vMathematical',
    'v-mathematical': 'vMathematical',
    vocab: 'vocab',
    widths: 'widths',
    wordspacing: 'wordSpacing',
    'word-spacing': 'wordSpacing',
    writingmode: 'writingMode',
    'writing-mode': 'writingMode',
    x1: 'x1',
    x2: 'x2',
    x: 'x',
    xchannelselector: 'xChannelSelector',
    xheight: 'xHeight',
    'x-height': 'xHeight',
    xlinkactuate: 'xlinkActuate',
    'xlink:actuate': 'xlinkActuate',
    xlinkarcrole: 'xlinkArcrole',
    'xlink:arcrole': 'xlinkArcrole',
    xlinkhref: 'xlinkHref',
    'xlink:href': 'xlinkHref',
    xlinkrole: 'xlinkRole',
    'xlink:role': 'xlinkRole',
    xlinkshow: 'xlinkShow',
    'xlink:show': 'xlinkShow',
    xlinktitle: 'xlinkTitle',
    'xlink:title': 'xlinkTitle',
    xlinktype: 'xlinkType',
    'xlink:type': 'xlinkType',
    xmlbase: 'xmlBase',
    'xml:base': 'xmlBase',
    xmllang: 'xmlLang',
    'xml:lang': 'xmlLang',
    xmlns: 'xmlns',
    'xml:space': 'xmlSpace',
    xmlnsxlink: 'xmlnsXlink',
    'xmlns:xlink': 'xmlnsXlink',
    xmlspace: 'xmlSpace',
    y1: 'y1',
    y2: 'y2',
    y: 'y',
    ychannelselector: 'yChannelSelector',
    z: 'z',
    zoomandpan: 'zoomAndPan'
  };
  
  function getStackAddendum$2() {
    var stack = ReactDebugCurrentFrame.getStackAddendum();
    return stack != null ? stack : '';
  }
  
  {
    var warnedProperties$1 = {};
    var hasOwnProperty$1 = Object.prototype.hasOwnProperty;
    var EVENT_NAME_REGEX = /^on./;
    var INVALID_EVENT_NAME_REGEX = /^on[^A-Z]/;
    var rARIA$1 = new RegExp('^(aria)-[' + ATTRIBUTE_NAME_CHAR + ']*$');
    var rARIACamel$1 = new RegExp('^(aria)[A-Z][' + ATTRIBUTE_NAME_CHAR + ']*$');
  
    var validateProperty$1 = function (tagName, name, value, canUseEventSystem) {
      if (hasOwnProperty$1.call(warnedProperties$1, name) && warnedProperties$1[name]) {
        return true;
      }
  
      var lowerCasedName = name.toLowerCase();
      if (lowerCasedName === 'onfocusin' || lowerCasedName === 'onfocusout') {
        warning(false, 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut. ' + 'All React events are normalized to bubble, so onFocusIn and onFocusOut ' + 'are not needed/supported by React.');
        warnedProperties$1[name] = true;
        return true;
      }
  
      // We can't rely on the event system being injected on the server.
      if (canUseEventSystem) {
        if (registrationNameModules.hasOwnProperty(name)) {
          return true;
        }
        var registrationName = possibleRegistrationNames.hasOwnProperty(lowerCasedName) ? possibleRegistrationNames[lowerCasedName] : null;
        if (registrationName != null) {
          warning(false, 'Invalid event handler property `%s`. Did you mean `%s`?%s', name, registrationName, getStackAddendum$2());
          warnedProperties$1[name] = true;
          return true;
        }
        if (EVENT_NAME_REGEX.test(name)) {
          warning(false, 'Unknown event handler property `%s`. It will be ignored.%s', name, getStackAddendum$2());
          warnedProperties$1[name] = true;
          return true;
        }
      } else if (EVENT_NAME_REGEX.test(name)) {
        // If no event plugins have been injected, we are in a server environment.
        // So we can't tell if the event name is correct for sure, but we can filter
        // out known bad ones like `onclick`. We can't suggest a specific replacement though.
        if (INVALID_EVENT_NAME_REGEX.test(name)) {
          warning(false, 'Invalid event handler property `%s`. ' + 'React events use the camelCase naming convention, for example `onClick`.%s', name, getStackAddendum$2());
        }
        warnedProperties$1[name] = true;
        return true;
      }
  
      // Let the ARIA attribute hook validate ARIA attributes
      if (rARIA$1.test(name) || rARIACamel$1.test(name)) {
        return true;
      }
  
      if (lowerCasedName === 'innerhtml') {
        warning(false, 'Directly setting property `innerHTML` is not permitted. ' + 'For more information, lookup documentation on `dangerouslySetInnerHTML`.');
        warnedProperties$1[name] = true;
        return true;
      }
  
      if (lowerCasedName === 'aria') {
        warning(false, 'The `aria` attribute is reserved for future use in React. ' + 'Pass individual `aria-` attributes instead.');
        warnedProperties$1[name] = true;
        return true;
      }
  
      if (lowerCasedName === 'is' && value !== null && value !== undefined && typeof value !== 'string') {
        warning(false, 'Received a `%s` for a string attribute `is`. If this is expected, cast ' + 'the value to a string.%s', typeof value, getStackAddendum$2());
        warnedProperties$1[name] = true;
        return true;
      }
  
      if (typeof value === 'number' && isNaN(value)) {
        warning(false, 'Received NaN for the `%s` attribute. If this is expected, cast ' + 'the value to a string.%s', name, getStackAddendum$2());
        warnedProperties$1[name] = true;
        return true;
      }
  
      var isReserved = isReservedProp(name);
  
      // Known attributes should match the casing specified in the property config.
      if (possibleStandardNames.hasOwnProperty(lowerCasedName)) {
        var standardName = possibleStandardNames[lowerCasedName];
        if (standardName !== name) {
          warning(false, 'Invalid DOM property `%s`. Did you mean `%s`?%s', name, standardName, getStackAddendum$2());
          warnedProperties$1[name] = true;
          return true;
        }
      } else if (!isReserved && name !== lowerCasedName) {
        // Unknown attributes should have lowercase casing since that's how they
        // will be cased anyway with server rendering.
        warning(false, 'React does not recognize the `%s` prop on a DOM element. If you ' + 'intentionally want it to appear in the DOM as a custom ' + 'attribute, spell it as lowercase `%s` instead. ' + 'If you accidentally passed it from a parent component, remove ' + 'it from the DOM element.%s', name, lowerCasedName, getStackAddendum$2());
        warnedProperties$1[name] = true;
        return true;
      }
  
      if (typeof value === 'boolean' && !shouldAttributeAcceptBooleanValue(name)) {
        if (value) {
          warning(false, 'Received `%s` for a non-boolean attribute `%s`.\n\n' + 'If you want to write it to the DOM, pass a string instead: ' + '%s="%s" or %s={value.toString()}.%s', value, name, name, value, name, getStackAddendum$2());
        } else {
          warning(false, 'Received `%s` for a non-boolean attribute `%s`.\n\n' + 'If you want to write it to the DOM, pass a string instead: ' + '%s="%s" or %s={value.toString()}.\n\n' + 'If you used to conditionally omit it with %s={condition && value}, ' + 'pass %s={condition ? value : undefined} instead.%s', value, name, name, value, name, name, name, getStackAddendum$2());
        }
        warnedProperties$1[name] = true;
        return true;
      }
  
      // Now that we've validated casing, do not validate
      // data types for reserved props
      if (isReserved) {
        return true;
      }
  
      // Warn when a known attribute is a bad type
      if (!shouldSetAttribute(name, value)) {
        warnedProperties$1[name] = true;
        return false;
      }
  
      return true;
    };
  }
  
  var warnUnknownProperties = function (type, props, canUseEventSystem) {
    var unknownProps = [];
    for (var key in props) {
      var isValid = validateProperty$1(type, key, props[key], canUseEventSystem);
      if (!isValid) {
        unknownProps.push(key);
      }
    }
  
    var unknownPropString = unknownProps.map(function (prop) {
      return '`' + prop + '`';
    }).join(', ');
    if (unknownProps.length === 1) {
      warning(false, 'Invalid value for prop %s on <%s> tag. Either remove it from the element, ' + 'or pass a string or number value to keep it in the DOM. ' + 'For details, see https://fb.me/react-attribute-behavior%s', unknownPropString, type, getStackAddendum$2());
    } else if (unknownProps.length > 1) {
      warning(false, 'Invalid values for props %s on <%s> tag. Either remove them from the element, ' + 'or pass a string or number value to keep them in the DOM. ' + 'For details, see https://fb.me/react-attribute-behavior%s', unknownPropString, type, getStackAddendum$2());
    }
  };
  
  function validateProperties$2(type, props, canUseEventSystem) {
    if (isCustomComponent(type, props)) {
      return;
    }
    warnUnknownProperties(type, props, canUseEventSystem);
  }
  
  // TODO: direct imports like some-package/src/* are bad. Fix me.
  var getCurrentFiberOwnerName$1 = ReactDebugCurrentFiber.getCurrentFiberOwnerName;
  var getCurrentFiberStackAddendum$2 = ReactDebugCurrentFiber.getCurrentFiberStackAddendum;
  
  var didWarnInvalidHydration = false;
  var didWarnShadyDOM = false;
  
  var DANGEROUSLY_SET_INNER_HTML = 'dangerouslySetInnerHTML';
  var SUPPRESS_CONTENT_EDITABLE_WARNING = 'suppressContentEditableWarning';
  var SUPPRESS_HYDRATION_WARNING$1 = 'suppressHydrationWarning';
  var AUTOFOCUS = 'autoFocus';
  var CHILDREN = 'children';
  var STYLE = 'style';
  var HTML = '__html';
  
  var HTML_NAMESPACE = Namespaces.html;
  
  
  var getStack = emptyFunction.thatReturns('');
  
  {
    getStack = getCurrentFiberStackAddendum$2;
  
    var warnedUnknownTags = {
      // Chrome is the only major browser not shipping <time>. But as of July
      // 2017 it intends to ship it due to widespread usage. We intentionally
      // *don't* warn for <time> even if it's unrecognized by Chrome because
      // it soon will be, and many apps have been using it anyway.
      time: true,
      // There are working polyfills for <dialog>. Let people use it.
      dialog: true
    };
  
    var validatePropertiesInDevelopment = function (type, props) {
      validateProperties(type, props);
      validateProperties$1(type, props);
      validateProperties$2(type, props, /* canUseEventSystem */true);
    };
  
    // HTML parsing normalizes CR and CRLF to LF.
    // It also can turn \u0000 into \uFFFD inside attributes.
    // https://www.w3.org/TR/html5/single-page.html#preprocessing-the-input-stream
    // If we have a mismatch, it might be caused by that.
    // We will still patch up in this case but not fire the warning.
    var NORMALIZE_NEWLINES_REGEX = /\r\n?/g;
    var NORMALIZE_NULL_AND_REPLACEMENT_REGEX = /\u0000|\uFFFD/g;
  
    var normalizeMarkupForTextOrAttribute = function (markup) {
      var markupString = typeof markup === 'string' ? markup : '' + markup;
      return markupString.replace(NORMALIZE_NEWLINES_REGEX, '\n').replace(NORMALIZE_NULL_AND_REPLACEMENT_REGEX, '');
    };
  
    var warnForTextDifference = function (serverText, clientText) {
      if (didWarnInvalidHydration) {
        return;
      }
      var normalizedClientText = normalizeMarkupForTextOrAttribute(clientText);
      var normalizedServerText = normalizeMarkupForTextOrAttribute(serverText);
      if (normalizedServerText === normalizedClientText) {
        return;
      }
      didWarnInvalidHydration = true;
      warning(false, 'Text content did not match. Server: "%s" Client: "%s"', normalizedServerText, normalizedClientText);
    };
  
    var warnForPropDifference = function (propName, serverValue, clientValue) {
      if (didWarnInvalidHydration) {
        return;
      }
      var normalizedClientValue = normalizeMarkupForTextOrAttribute(clientValue);
      var normalizedServerValue = normalizeMarkupForTextOrAttribute(serverValue);
      if (normalizedServerValue === normalizedClientValue) {
        return;
      }
      didWarnInvalidHydration = true;
      warning(false, 'Prop `%s` did not match. Server: %s Client: %s', propName, JSON.stringify(normalizedServerValue), JSON.stringify(normalizedClientValue));
    };
  
    var warnForExtraAttributes = function (attributeNames) {
      if (didWarnInvalidHydration) {
        return;
      }
      didWarnInvalidHydration = true;
      var names = [];
      attributeNames.forEach(function (name) {
        names.push(name);
      });
      warning(false, 'Extra attributes from the server: %s', names);
    };
  
    var warnForInvalidEventListener = function (registrationName, listener) {
      if (listener === false) {
        warning(false, 'Expected `%s` listener to be a function, instead got `false`.\n\n' + 'If you used to conditionally omit it with %s={condition && value}, ' + 'pass %s={condition ? value : undefined} instead.%s', registrationName, registrationName, registrationName, getCurrentFiberStackAddendum$2());
      } else {
        warning(false, 'Expected `%s` listener to be a function, instead got a value of `%s` type.%s', registrationName, typeof listener, getCurrentFiberStackAddendum$2());
      }
    };
  
    // Parse the HTML and read it back to normalize the HTML string so that it
    // can be used for comparison.
    var normalizeHTML = function (parent, html) {
      // We could have created a separate document here to avoid
      // re-initializing custom elements if they exist. But this breaks
      // how <noscript> is being handled. So we use the same document.
      // See the discussion in https://github.com/facebook/react/pull/11157.
      var testElement = parent.namespaceURI === HTML_NAMESPACE ? parent.ownerDocument.createElement(parent.tagName) : parent.ownerDocument.createElementNS(parent.namespaceURI, parent.tagName);
      testElement.innerHTML = html;
      return testElement.innerHTML;
    };
  }
  
  function ensureListeningTo(rootContainerElement, registrationName) {
    var isDocumentOrFragment = rootContainerElement.nodeType === DOCUMENT_NODE || rootContainerElement.nodeType === DOCUMENT_FRAGMENT_NODE;
    var doc = isDocumentOrFragment ? rootContainerElement : rootContainerElement.ownerDocument;
    listenTo(registrationName, doc);
  }
  
  function getOwnerDocumentFromRootContainer(rootContainerElement) {
    return rootContainerElement.nodeType === DOCUMENT_NODE ? rootContainerElement : rootContainerElement.ownerDocument;
  }
  
  // There are so many media events, it makes sense to just
  // maintain a list rather than create a `trapBubbledEvent` for each
  var mediaEvents = {
    topAbort: 'abort',
    topCanPlay: 'canplay',
    topCanPlayThrough: 'canplaythrough',
    topDurationChange: 'durationchange',
    topEmptied: 'emptied',
    topEncrypted: 'encrypted',
    topEnded: 'ended',
    topError: 'error',
    topLoadedData: 'loadeddata',
    topLoadedMetadata: 'loadedmetadata',
    topLoadStart: 'loadstart',
    topPause: 'pause',
    topPlay: 'play',
    topPlaying: 'playing',
    topProgress: 'progress',
    topRateChange: 'ratechange',
    topSeeked: 'seeked',
    topSeeking: 'seeking',
    topStalled: 'stalled',
    topSuspend: 'suspend',
    topTimeUpdate: 'timeupdate',
    topVolumeChange: 'volumechange',
    topWaiting: 'waiting'
  };
  
  function trapClickOnNonInteractiveElement(node) {
    // Mobile Safari does not fire properly bubble click events on
    // non-interactive elements, which means delegated click listeners do not
    // fire. The workaround for this bug involves attaching an empty click
    // listener on the target node.
    // http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
    // Just set it using the onclick property so that we don't have to manage any
    // bookkeeping for it. Not sure if we need to clear it when the listener is
    // removed.
    // TODO: Only do this for the relevant Safaris maybe?
    node.onclick = emptyFunction;
  }
  
  function setInitialDOMProperties(tag, domElement, rootContainerElement, nextProps, isCustomComponentTag) {
    for (var propKey in nextProps) {
      if (!nextProps.hasOwnProperty(propKey)) {
        continue;
      }
      var nextProp = nextProps[propKey];
      if (propKey === STYLE) {
        {
          if (nextProp) {
            // Freeze the next style object so that we can assume it won't be
            // mutated. We have already warned for this in the past.
            Object.freeze(nextProp);
          }
        }
        // Relies on `updateStylesByID` not mutating `styleUpdates`.
        setValueForStyles(domElement, nextProp, getStack);
      } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
        var nextHtml = nextProp ? nextProp[HTML] : undefined;
        if (nextHtml != null) {
          setInnerHTML(domElement, nextHtml);
        }
      } else if (propKey === CHILDREN) {
        if (typeof nextProp === 'string') {
          // Avoid setting initial textContent when the text is empty. In IE11 setting
          // textContent on a <textarea> will cause the placeholder to not
          // show within the <textarea> until it has been focused and blurred again.
          // https://github.com/facebook/react/issues/6731#issuecomment-254874553
          var canSetTextContent = tag !== 'textarea' || nextProp !== '';
          if (canSetTextContent) {
            setTextContent(domElement, nextProp);
          }
        } else if (typeof nextProp === 'number') {
          setTextContent(domElement, '' + nextProp);
        }
      } else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING$1) {
        // Noop
      } else if (propKey === AUTOFOCUS) {
        // We polyfill it separately on the client during commit.
        // We blacklist it here rather than in the property list because we emit it in SSR.
      } else if (registrationNameModules.hasOwnProperty(propKey)) {
        if (nextProp != null) {
          if (true && typeof nextProp !== 'function') {
            warnForInvalidEventListener(propKey, nextProp);
          }
          ensureListeningTo(rootContainerElement, propKey);
        }
      } else if (isCustomComponentTag) {
        setValueForAttribute(domElement, propKey, nextProp);
      } else if (nextProp != null) {
        // If we're updating to null or undefined, we should remove the property
        // from the DOM node instead of inadvertently setting to a string. This
        // brings us in line with the same behavior we have on initial render.
        setValueForProperty(domElement, propKey, nextProp);
      }
    }
  }
  
  function updateDOMProperties(domElement, updatePayload, wasCustomComponentTag, isCustomComponentTag) {
    // TODO: Handle wasCustomComponentTag
    for (var i = 0; i < updatePayload.length; i += 2) {
      var propKey = updatePayload[i];
      var propValue = updatePayload[i + 1];
      if (propKey === STYLE) {
        setValueForStyles(domElement, propValue, getStack);
      } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
        setInnerHTML(domElement, propValue);
      } else if (propKey === CHILDREN) {
        setTextContent(domElement, propValue);
      } else if (isCustomComponentTag) {
        if (propValue != null) {
          setValueForAttribute(domElement, propKey, propValue);
        } else {
          deleteValueForAttribute(domElement, propKey);
        }
      } else if (propValue != null) {
        setValueForProperty(domElement, propKey, propValue);
      } else {
        // If we're updating to null or undefined, we should remove the property
        // from the DOM node instead of inadvertently setting to a string. This
        // brings us in line with the same behavior we have on initial render.
        deleteValueForProperty(domElement, propKey);
      }
    }
  }
  
  function createElement$1(type, props, rootContainerElement, parentNamespace) {
    // We create tags in the namespace of their parent container, except HTML
    var ownerDocument = getOwnerDocumentFromRootContainer(rootContainerElement);
    var domElement;
    var namespaceURI = parentNamespace;
    if (namespaceURI === HTML_NAMESPACE) {
      namespaceURI = getIntrinsicNamespace(type);
    }
    if (namespaceURI === HTML_NAMESPACE) {
      {
        var isCustomComponentTag = isCustomComponent(type, props);
        // Should this check be gated by parent namespace? Not sure we want to
        // allow <SVG> or <mATH>.
        warning(isCustomComponentTag || type === type.toLowerCase(), '<%s /> is using uppercase HTML. Always use lowercase HTML tags ' + 'in React.', type);
      }
  
      if (type === 'script') {
        // Create the script via .innerHTML so its "parser-inserted" flag is
        // set to true and it does not execute
        var div = ownerDocument.createElement('div');
        div.innerHTML = '<script><' + '/script>'; // eslint-disable-line
        // This is guaranteed to yield a script element.
        var firstChild = div.firstChild;
        domElement = div.removeChild(firstChild);
      } else if (typeof props.is === 'string') {
        // $FlowIssue `createElement` should be updated for Web Components
        domElement = ownerDocument.createElement(type, { is: props.is });
      } else {
        // Separate else branch instead of using `props.is || undefined` above because of a Firefox bug.
        // See discussion in https://github.com/facebook/react/pull/6896
        // and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
        domElement = ownerDocument.createElement(type);
      }
    } else {
      domElement = ownerDocument.createElementNS(namespaceURI, type);
    }
  
    {
      if (namespaceURI === HTML_NAMESPACE) {
        if (!isCustomComponentTag && Object.prototype.toString.call(domElement) === '[object HTMLUnknownElement]' && !Object.prototype.hasOwnProperty.call(warnedUnknownTags, type)) {
          warnedUnknownTags[type] = true;
          warning(false, 'The tag <%s> is unrecognized in this browser. ' + 'If you meant to render a React component, start its name with ' + 'an uppercase letter.', type);
        }
      }
    }
  
    return domElement;
  }
  
  function createTextNode$1(text, rootContainerElement) {
    return getOwnerDocumentFromRootContainer(rootContainerElement).createTextNode(text);
  }
  
  function setInitialProperties$1(domElement, tag, rawProps, rootContainerElement) {
    var isCustomComponentTag = isCustomComponent(tag, rawProps);
    {
      validatePropertiesInDevelopment(tag, rawProps);
      if (isCustomComponentTag && !didWarnShadyDOM && domElement.shadyRoot) {
        warning(false, '%s is using shady DOM. Using shady DOM with React can ' + 'cause things to break subtly.', getCurrentFiberOwnerName$1() || 'A component');
        didWarnShadyDOM = true;
      }
    }
  
    // TODO: Make sure that we check isMounted before firing any of these events.
    var props;
    switch (tag) {
      case 'iframe':
      case 'object':
        trapBubbledEvent('topLoad', 'load', domElement);
        props = rawProps;
        break;
      case 'video':
      case 'audio':
        // Create listener for each media event
        for (var event in mediaEvents) {
          if (mediaEvents.hasOwnProperty(event)) {
            trapBubbledEvent(event, mediaEvents[event], domElement);
          }
        }
        props = rawProps;
        break;
      case 'source':
        trapBubbledEvent('topError', 'error', domElement);
        props = rawProps;
        break;
      case 'img':
      case 'image':
        trapBubbledEvent('topError', 'error', domElement);
        trapBubbledEvent('topLoad', 'load', domElement);
        props = rawProps;
        break;
      case 'form':
        trapBubbledEvent('topReset', 'reset', domElement);
        trapBubbledEvent('topSubmit', 'submit', domElement);
        props = rawProps;
        break;
      case 'details':
        trapBubbledEvent('topToggle', 'toggle', domElement);
        props = rawProps;
        break;
      case 'input':
        initWrapperState(domElement, rawProps);
        props = getHostProps(domElement, rawProps);
        trapBubbledEvent('topInvalid', 'invalid', domElement);
        // For controlled components we always need to ensure we're listening
        // to onChange. Even if there is no listener.
        ensureListeningTo(rootContainerElement, 'onChange');
        break;
      case 'option':
        validateProps(domElement, rawProps);
        props = getHostProps$1(domElement, rawProps);
        break;
      case 'select':
        initWrapperState$1(domElement, rawProps);
        props = getHostProps$2(domElement, rawProps);
        trapBubbledEvent('topInvalid', 'invalid', domElement);
        // For controlled components we always need to ensure we're listening
        // to onChange. Even if there is no listener.
        ensureListeningTo(rootContainerElement, 'onChange');
        break;
      case 'textarea':
        initWrapperState$2(domElement, rawProps);
        props = getHostProps$3(domElement, rawProps);
        trapBubbledEvent('topInvalid', 'invalid', domElement);
        // For controlled components we always need to ensure we're listening
        // to onChange. Even if there is no listener.
        ensureListeningTo(rootContainerElement, 'onChange');
        break;
      default:
        props = rawProps;
    }
  
    assertValidProps(tag, props, getStack);
  
    setInitialDOMProperties(tag, domElement, rootContainerElement, props, isCustomComponentTag);
  
    switch (tag) {
      case 'input':
        // TODO: Make sure we check if this is still unmounted or do any clean
        // up necessary since we never stop tracking anymore.
        track(domElement);
        postMountWrapper(domElement, rawProps);
        break;
      case 'textarea':
        // TODO: Make sure we check if this is still unmounted or do any clean
        // up necessary since we never stop tracking anymore.
        track(domElement);
        postMountWrapper$3(domElement, rawProps);
        break;
      case 'option':
        postMountWrapper$1(domElement, rawProps);
        break;
      case 'select':
        postMountWrapper$2(domElement, rawProps);
        break;
      default:
        if (typeof props.onClick === 'function') {
          // TODO: This cast may not be sound for SVG, MathML or custom elements.
          trapClickOnNonInteractiveElement(domElement);
        }
        break;
    }
  }
  
  // Calculate the diff between the two objects.
  function diffProperties$1(domElement, tag, lastRawProps, nextRawProps, rootContainerElement) {
    {
      validatePropertiesInDevelopment(tag, nextRawProps);
    }
  
    var updatePayload = null;
  
    var lastProps;
    var nextProps;
    switch (tag) {
      case 'input':
        lastProps = getHostProps(domElement, lastRawProps);
        nextProps = getHostProps(domElement, nextRawProps);
        updatePayload = [];
        break;
      case 'option':
        lastProps = getHostProps$1(domElement, lastRawProps);
        nextProps = getHostProps$1(domElement, nextRawProps);
        updatePayload = [];
        break;
      case 'select':
        lastProps = getHostProps$2(domElement, lastRawProps);
        nextProps = getHostProps$2(domElement, nextRawProps);
        updatePayload = [];
        break;
      case 'textarea':
        lastProps = getHostProps$3(domElement, lastRawProps);
        nextProps = getHostProps$3(domElement, nextRawProps);
        updatePayload = [];
        break;
      default:
        lastProps = lastRawProps;
        nextProps = nextRawProps;
        if (typeof lastProps.onClick !== 'function' && typeof nextProps.onClick === 'function') {
          // TODO: This cast may not be sound for SVG, MathML or custom elements.
          trapClickOnNonInteractiveElement(domElement);
        }
        break;
    }
  
    assertValidProps(tag, nextProps, getStack);
  
    var propKey;
    var styleName;
    var styleUpdates = null;
    for (propKey in lastProps) {
      if (nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey) || lastProps[propKey] == null) {
        continue;
      }
      if (propKey === STYLE) {
        var lastStyle = lastProps[propKey];
        for (styleName in lastStyle) {
          if (lastStyle.hasOwnProperty(styleName)) {
            if (!styleUpdates) {
              styleUpdates = {};
            }
            styleUpdates[styleName] = '';
          }
        }
      } else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) {
        // Noop. This is handled by the clear text mechanism.
      } else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING$1) {
        // Noop
      } else if (propKey === AUTOFOCUS) {
        // Noop. It doesn't work on updates anyway.
      } else if (registrationNameModules.hasOwnProperty(propKey)) {
        // This is a special case. If any listener updates we need to ensure
        // that the "current" fiber pointer gets updated so we need a commit
        // to update this element.
        if (!updatePayload) {
          updatePayload = [];
        }
      } else {
        // For all other deleted properties we add it to the queue. We use
        // the whitelist in the commit phase instead.
        (updatePayload = updatePayload || []).push(propKey, null);
      }
    }
    for (propKey in nextProps) {
      var nextProp = nextProps[propKey];
      var lastProp = lastProps != null ? lastProps[propKey] : undefined;
      if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp || nextProp == null && lastProp == null) {
        continue;
      }
      if (propKey === STYLE) {
        {
          if (nextProp) {
            // Freeze the next style object so that we can assume it won't be
            // mutated. We have already warned for this in the past.
            Object.freeze(nextProp);
          }
        }
        if (lastProp) {
          // Unset styles on `lastProp` but not on `nextProp`.
          for (styleName in lastProp) {
            if (lastProp.hasOwnProperty(styleName) && (!nextProp || !nextProp.hasOwnProperty(styleName))) {
              if (!styleUpdates) {
                styleUpdates = {};
              }
              styleUpdates[styleName] = '';
            }
          }
          // Update styles that changed since `lastProp`.
          for (styleName in nextProp) {
            if (nextProp.hasOwnProperty(styleName) && lastProp[styleName] !== nextProp[styleName]) {
              if (!styleUpdates) {
                styleUpdates = {};
              }
              styleUpdates[styleName] = nextProp[styleName];
            }
          }
        } else {
          // Relies on `updateStylesByID` not mutating `styleUpdates`.
          if (!styleUpdates) {
            if (!updatePayload) {
              updatePayload = [];
            }
            updatePayload.push(propKey, styleUpdates);
          }
          styleUpdates = nextProp;
        }
      } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
        var nextHtml = nextProp ? nextProp[HTML] : undefined;
        var lastHtml = lastProp ? lastProp[HTML] : undefined;
        if (nextHtml != null) {
          if (lastHtml !== nextHtml) {
            (updatePayload = updatePayload || []).push(propKey, '' + nextHtml);
          }
        } else {
          // TODO: It might be too late to clear this if we have children
          // inserted already.
        }
      } else if (propKey === CHILDREN) {
        if (lastProp !== nextProp && (typeof nextProp === 'string' || typeof nextProp === 'number')) {
          (updatePayload = updatePayload || []).push(propKey, '' + nextProp);
        }
      } else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING$1) {
        // Noop
      } else if (registrationNameModules.hasOwnProperty(propKey)) {
        if (nextProp != null) {
          // We eagerly listen to this even though we haven't committed yet.
          if (true && typeof nextProp !== 'function') {
            warnForInvalidEventListener(propKey, nextProp);
          }
          ensureListeningTo(rootContainerElement, propKey);
        }
        if (!updatePayload && lastProp !== nextProp) {
          // This is a special case. If any listener updates we need to ensure
          // that the "current" props pointer gets updated so we need a commit
          // to update this element.
          updatePayload = [];
        }
      } else {
        // For any other property we always add it to the queue and then we
        // filter it out using the whitelist during the commit.
        (updatePayload = updatePayload || []).push(propKey, nextProp);
      }
    }
    if (styleUpdates) {
      (updatePayload = updatePayload || []).push(STYLE, styleUpdates);
    }
    return updatePayload;
  }
  
  // Apply the diff.
  function updateProperties$1(domElement, updatePayload, tag, lastRawProps, nextRawProps) {
    // Update checked *before* name.
    // In the middle of an update, it is possible to have multiple checked.
    // When a checked radio tries to change name, browser makes another radio's checked false.
    if (tag === 'input' && nextRawProps.type === 'radio' && nextRawProps.name != null) {
      updateChecked(domElement, nextRawProps);
    }
  
    var wasCustomComponentTag = isCustomComponent(tag, lastRawProps);
    var isCustomComponentTag = isCustomComponent(tag, nextRawProps);
    // Apply the diff.
    updateDOMProperties(domElement, updatePayload, wasCustomComponentTag, isCustomComponentTag);
  
    // TODO: Ensure that an update gets scheduled if any of the special props
    // changed.
    switch (tag) {
      case 'input':
        // Update the wrapper around inputs *after* updating props. This has to
        // happen after `updateDOMProperties`. Otherwise HTML5 input validations
        // raise warnings and prevent the new value from being assigned.
        updateWrapper(domElement, nextRawProps);
        break;
      case 'textarea':
        updateWrapper$1(domElement, nextRawProps);
        break;
      case 'select':
        // <select> value update needs to occur after <option> children
        // reconciliation
        postUpdateWrapper(domElement, nextRawProps);
        break;
    }
  }
  
  function diffHydratedProperties$1(domElement, tag, rawProps, parentNamespace, rootContainerElement) {
    {
      var suppressHydrationWarning = rawProps[SUPPRESS_HYDRATION_WARNING$1] === true;
      var isCustomComponentTag = isCustomComponent(tag, rawProps);
      validatePropertiesInDevelopment(tag, rawProps);
      if (isCustomComponentTag && !didWarnShadyDOM && domElement.shadyRoot) {
        warning(false, '%s is using shady DOM. Using shady DOM with React can ' + 'cause things to break subtly.', getCurrentFiberOwnerName$1() || 'A component');
        didWarnShadyDOM = true;
      }
    }
  
    // TODO: Make sure that we check isMounted before firing any of these events.
    switch (tag) {
      case 'iframe':
      case 'object':
        trapBubbledEvent('topLoad', 'load', domElement);
        break;
      case 'video':
      case 'audio':
        // Create listener for each media event
        for (var event in mediaEvents) {
          if (mediaEvents.hasOwnProperty(event)) {
            trapBubbledEvent(event, mediaEvents[event], domElement);
          }
        }
        break;
      case 'source':
        trapBubbledEvent('topError', 'error', domElement);
        break;
      case 'img':
      case 'image':
        trapBubbledEvent('topError', 'error', domElement);
        trapBubbledEvent('topLoad', 'load', domElement);
        break;
      case 'form':
        trapBubbledEvent('topReset', 'reset', domElement);
        trapBubbledEvent('topSubmit', 'submit', domElement);
        break;
      case 'details':
        trapBubbledEvent('topToggle', 'toggle', domElement);
        break;
      case 'input':
        initWrapperState(domElement, rawProps);
        trapBubbledEvent('topInvalid', 'invalid', domElement);
        // For controlled components we always need to ensure we're listening
        // to onChange. Even if there is no listener.
        ensureListeningTo(rootContainerElement, 'onChange');
        break;
      case 'option':
        validateProps(domElement, rawProps);
        break;
      case 'select':
        initWrapperState$1(domElement, rawProps);
        trapBubbledEvent('topInvalid', 'invalid', domElement);
        // For controlled components we always need to ensure we're listening
        // to onChange. Even if there is no listener.
        ensureListeningTo(rootContainerElement, 'onChange');
        break;
      case 'textarea':
        initWrapperState$2(domElement, rawProps);
        trapBubbledEvent('topInvalid', 'invalid', domElement);
        // For controlled components we always need to ensure we're listening
        // to onChange. Even if there is no listener.
        ensureListeningTo(rootContainerElement, 'onChange');
        break;
    }
  
    assertValidProps(tag, rawProps, getStack);
  
    {
      var extraAttributeNames = new Set();
      var attributes = domElement.attributes;
      for (var i = 0; i < attributes.length; i++) {
        var name = attributes[i].name.toLowerCase();
        switch (name) {
          // Built-in SSR attribute is whitelisted
          case 'data-reactroot':
            break;
          // Controlled attributes are not validated
          // TODO: Only ignore them on controlled tags.
          case 'value':
            break;
          case 'checked':
            break;
          case 'selected':
            break;
          default:
            // Intentionally use the original name.
            // See discussion in https://github.com/facebook/react/pull/10676.
            extraAttributeNames.add(attributes[i].name);
        }
      }
    }
  
    var updatePayload = null;
    for (var propKey in rawProps) {
      if (!rawProps.hasOwnProperty(propKey)) {
        continue;
      }
      var nextProp = rawProps[propKey];
      if (propKey === CHILDREN) {
        // For text content children we compare against textContent. This
        // might match additional HTML that is hidden when we read it using
        // textContent. E.g. "foo" will match "f<span>oo</span>" but that still
        // satisfies our requirement. Our requirement is not to produce perfect
        // HTML and attributes. Ideally we should preserve structure but it's
        // ok not to if the visible content is still enough to indicate what
        // even listeners these nodes might be wired up to.
        // TODO: Warn if there is more than a single textNode as a child.
        // TODO: Should we use domElement.firstChild.nodeValue to compare?
        if (typeof nextProp === 'string') {
          if (domElement.textContent !== nextProp) {
            if (true && !suppressHydrationWarning) {
              warnForTextDifference(domElement.textContent, nextProp);
            }
            updatePayload = [CHILDREN, nextProp];
          }
        } else if (typeof nextProp === 'number') {
          if (domElement.textContent !== '' + nextProp) {
            if (true && !suppressHydrationWarning) {
              warnForTextDifference(domElement.textContent, nextProp);
            }
            updatePayload = [CHILDREN, '' + nextProp];
          }
        }
      } else if (registrationNameModules.hasOwnProperty(propKey)) {
        if (nextProp != null) {
          if (true && typeof nextProp !== 'function') {
            warnForInvalidEventListener(propKey, nextProp);
          }
          ensureListeningTo(rootContainerElement, propKey);
        }
      } else {
        // Validate that the properties correspond to their expected values.
        var serverValue;
        var propertyInfo;
        if (suppressHydrationWarning) {
          // Don't bother comparing. We're ignoring all these warnings.
        } else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING$1 ||
        // Controlled attributes are not validated
        // TODO: Only ignore them on controlled tags.
        propKey === 'value' || propKey === 'checked' || propKey === 'selected') {
          // Noop
        } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
          var rawHtml = nextProp ? nextProp[HTML] || '' : '';
          var serverHTML = domElement.innerHTML;
          var expectedHTML = normalizeHTML(domElement, rawHtml);
          if (expectedHTML !== serverHTML) {
            warnForPropDifference(propKey, serverHTML, expectedHTML);
          }
        } else if (propKey === STYLE) {
          // $FlowFixMe - Should be inferred as not undefined.
          extraAttributeNames['delete'](propKey);
          var expectedStyle = createDangerousStringForStyles(nextProp);
          serverValue = domElement.getAttribute('style');
          if (expectedStyle !== serverValue) {
            warnForPropDifference(propKey, serverValue, expectedStyle);
          }
        } else if (isCustomComponentTag) {
          // $FlowFixMe - Should be inferred as not undefined.
          extraAttributeNames['delete'](propKey.toLowerCase());
          serverValue = getValueForAttribute(domElement, propKey, nextProp);
  
          if (nextProp !== serverValue) {
            warnForPropDifference(propKey, serverValue, nextProp);
          }
        } else if (shouldSetAttribute(propKey, nextProp)) {
          if (propertyInfo = getPropertyInfo(propKey)) {
            // $FlowFixMe - Should be inferred as not undefined.
            extraAttributeNames['delete'](propertyInfo.attributeName);
            serverValue = getValueForProperty(domElement, propKey, nextProp);
          } else {
            var ownNamespace = parentNamespace;
            if (ownNamespace === HTML_NAMESPACE) {
              ownNamespace = getIntrinsicNamespace(tag);
            }
            if (ownNamespace === HTML_NAMESPACE) {
              // $FlowFixMe - Should be inferred as not undefined.
              extraAttributeNames['delete'](propKey.toLowerCase());
            } else {
              // $FlowFixMe - Should be inferred as not undefined.
              extraAttributeNames['delete'](propKey);
            }
            serverValue = getValueForAttribute(domElement, propKey, nextProp);
          }
  
          if (nextProp !== serverValue) {
            warnForPropDifference(propKey, serverValue, nextProp);
          }
        }
      }
    }
  
    {
      // $FlowFixMe - Should be inferred as not undefined.
      if (extraAttributeNames.size > 0 && !suppressHydrationWarning) {
        // $FlowFixMe - Should be inferred as not undefined.
        warnForExtraAttributes(extraAttributeNames);
      }
    }
  
    switch (tag) {
      case 'input':
        // TODO: Make sure we check if this is still unmounted or do any clean
        // up necessary since we never stop tracking anymore.
        track(domElement);
        postMountWrapper(domElement, rawProps);
        break;
      case 'textarea':
        // TODO: Make sure we check if this is still unmounted or do any clean
        // up necessary since we never stop tracking anymore.
        track(domElement);
        postMountWrapper$3(domElement, rawProps);
        break;
      case 'select':
      case 'option':
        // For input and textarea we current always set the value property at
        // post mount to force it to diverge from attributes. However, for
        // option and select we don't quite do the same thing and select
        // is not resilient to the DOM state changing so we don't do that here.
        // TODO: Consider not doing this for input and textarea.
        break;
      default:
        if (typeof rawProps.onClick === 'function') {
          // TODO: This cast may not be sound for SVG, MathML or custom elements.
          trapClickOnNonInteractiveElement(domElement);
        }
        break;
    }
  
    return updatePayload;
  }
  
  function diffHydratedText$1(textNode, text) {
    var isDifferent = textNode.nodeValue !== text;
    return isDifferent;
  }
  
  function warnForUnmatchedText$1(textNode, text) {
    {
      warnForTextDifference(textNode.nodeValue, text);
    }
  }
  
  function warnForDeletedHydratableElement$1(parentNode, child) {
    {
      if (didWarnInvalidHydration) {
        return;
      }
      didWarnInvalidHydration = true;
      warning(false, 'Did not expect server HTML to contain a <%s> in <%s>.', child.nodeName.toLowerCase(), parentNode.nodeName.toLowerCase());
    }
  }
  
  function warnForDeletedHydratableText$1(parentNode, child) {
    {
      if (didWarnInvalidHydration) {
        return;
      }
      didWarnInvalidHydration = true;
      warning(false, 'Did not expect server HTML to contain the text node "%s" in <%s>.', child.nodeValue, parentNode.nodeName.toLowerCase());
    }
  }
  
  function warnForInsertedHydratedElement$1(parentNode, tag, props) {
    {
      if (didWarnInvalidHydration) {
        return;
      }
      didWarnInvalidHydration = true;
      warning(false, 'Expected server HTML to contain a matching <%s> in <%s>.', tag, parentNode.nodeName.toLowerCase());
    }
  }
  
  function warnForInsertedHydratedText$1(parentNode, text) {
    {
      if (text === '') {
        // We expect to insert empty text nodes since they're not represented in
        // the HTML.
        // TODO: Remove this special case if we can just avoid inserting empty
        // text nodes.
        return;
      }
      if (didWarnInvalidHydration) {
        return;
      }
      didWarnInvalidHydration = true;
      warning(false, 'Expected server HTML to contain a matching text node for "%s" in <%s>.', text, parentNode.nodeName.toLowerCase());
    }
  }
  
  function restoreControlledState(domElement, tag, props) {
    switch (tag) {
      case 'input':
        restoreControlledState$1(domElement, props);
        return;
      case 'textarea':
        restoreControlledState$3(domElement, props);
        return;
      case 'select':
        restoreControlledState$2(domElement, props);
        return;
    }
  }
  
  var ReactDOMFiberComponent = Object.freeze({
    createElement: createElement$1,
    createTextNode: createTextNode$1,
    setInitialProperties: setInitialProperties$1,
    diffProperties: diffProperties$1,
    updateProperties: updateProperties$1,
    diffHydratedProperties: diffHydratedProperties$1,
    diffHydratedText: diffHydratedText$1,
    warnForUnmatchedText: warnForUnmatchedText$1,
    warnForDeletedHydratableElement: warnForDeletedHydratableElement$1,
    warnForDeletedHydratableText: warnForDeletedHydratableText$1,
    warnForInsertedHydratedElement: warnForInsertedHydratedElement$1,
    warnForInsertedHydratedText: warnForInsertedHydratedText$1,
    restoreControlledState: restoreControlledState
  });
  
  // TODO: direct imports like some-package/src/* are bad. Fix me.
  var getCurrentFiberStackAddendum$6 = ReactDebugCurrentFiber.getCurrentFiberStackAddendum;
  
  var validateDOMNesting = emptyFunction;
  
  {
    // This validation code was written based on the HTML5 parsing spec:
    // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope
    //
    // Note: this does not catch all invalid nesting, nor does it try to (as it's
    // not clear what practical benefit doing so provides); instead, we warn only
    // for cases where the parser will give a parse tree differing from what React
    // intended. For example, <b><div></div></b> is invalid but we don't warn
    // because it still parses correctly; we do warn for other cases like nested
    // <p> tags where the beginning of the second element implicitly closes the
    // first, causing a confusing mess.
  
    // https://html.spec.whatwg.org/multipage/syntax.html#special
    var specialTags = ['address', 'applet', 'area', 'article', 'aside', 'base', 'basefont', 'bgsound', 'blockquote', 'body', 'br', 'button', 'caption', 'center', 'col', 'colgroup', 'dd', 'details', 'dir', 'div', 'dl', 'dt', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'iframe', 'img', 'input', 'isindex', 'li', 'link', 'listing', 'main', 'marquee', 'menu', 'menuitem', 'meta', 'nav', 'noembed', 'noframes', 'noscript', 'object', 'ol', 'p', 'param', 'plaintext', 'pre', 'script', 'section', 'select', 'source', 'style', 'summary', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'title', 'tr', 'track', 'ul', 'wbr', 'xmp'];
  
    // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope
    var inScopeTags = ['applet', 'caption', 'html', 'table', 'td', 'th', 'marquee', 'object', 'template',
  
    // https://html.spec.whatwg.org/multipage/syntax.html#html-integration-point
    // TODO: Distinguish by namespace here -- for <title>, including it here
    // errs on the side of fewer warnings
    'foreignObject', 'desc', 'title'];
  
    // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-button-scope
    var buttonScopeTags = inScopeTags.concat(['button']);
  
    // https://html.spec.whatwg.org/multipage/syntax.html#generate-implied-end-tags
    var impliedEndTags = ['dd', 'dt', 'li', 'option', 'optgroup', 'p', 'rp', 'rt'];
  
    var emptyAncestorInfo = {
      current: null,
  
      formTag: null,
      aTagInScope: null,
      buttonTagInScope: null,
      nobrTagInScope: null,
      pTagInButtonScope: null,
  
      listItemTagAutoclosing: null,
      dlItemTagAutoclosing: null
    };
  
    var updatedAncestorInfo$1 = function (oldInfo, tag, instance) {
      var ancestorInfo = _assign({}, oldInfo || emptyAncestorInfo);
      var info = { tag: tag, instance: instance };
  
      if (inScopeTags.indexOf(tag) !== -1) {
        ancestorInfo.aTagInScope = null;
        ancestorInfo.buttonTagInScope = null;
        ancestorInfo.nobrTagInScope = null;
      }
      if (buttonScopeTags.indexOf(tag) !== -1) {
        ancestorInfo.pTagInButtonScope = null;
      }
  
      // See rules for 'li', 'dd', 'dt' start tags in
      // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody
      if (specialTags.indexOf(tag) !== -1 && tag !== 'address' && tag !== 'div' && tag !== 'p') {
        ancestorInfo.listItemTagAutoclosing = null;
        ancestorInfo.dlItemTagAutoclosing = null;
      }
  
      ancestorInfo.current = info;
  
      if (tag === 'form') {
        ancestorInfo.formTag = info;
      }
      if (tag === 'a') {
        ancestorInfo.aTagInScope = info;
      }
      if (tag === 'button') {
        ancestorInfo.buttonTagInScope = info;
      }
      if (tag === 'nobr') {
        ancestorInfo.nobrTagInScope = info;
      }
      if (tag === 'p') {
        ancestorInfo.pTagInButtonScope = info;
      }
      if (tag === 'li') {
        ancestorInfo.listItemTagAutoclosing = info;
      }
      if (tag === 'dd' || tag === 'dt') {
        ancestorInfo.dlItemTagAutoclosing = info;
      }
  
      return ancestorInfo;
    };
  
    /**
     * Returns whether
     */
    var isTagValidWithParent = function (tag, parentTag) {
      // First, let's check if we're in an unusual parsing mode...
      switch (parentTag) {
        // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect
        case 'select':
          return tag === 'option' || tag === 'optgroup' || tag === '#text';
        case 'optgroup':
          return tag === 'option' || tag === '#text';
        // Strictly speaking, seeing an <option> doesn't mean we're in a <select>
        // but
        case 'option':
          return tag === '#text';
        // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intd
        // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incaption
        // No special behavior since these rules fall back to "in body" mode for
        // all except special table nodes which cause bad parsing behavior anyway.
  
        // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intr
        case 'tr':
          return tag === 'th' || tag === 'td' || tag === 'style' || tag === 'script' || tag === 'template';
        // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intbody
        case 'tbody':
        case 'thead':
        case 'tfoot':
          return tag === 'tr' || tag === 'style' || tag === 'script' || tag === 'template';
        // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incolgroup
        case 'colgroup':
          return tag === 'col' || tag === 'template';
        // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intable
        case 'table':
          return tag === 'caption' || tag === 'colgroup' || tag === 'tbody' || tag === 'tfoot' || tag === 'thead' || tag === 'style' || tag === 'script' || tag === 'template';
        // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inhead
        case 'head':
          return tag === 'base' || tag === 'basefont' || tag === 'bgsound' || tag === 'link' || tag === 'meta' || tag === 'title' || tag === 'noscript' || tag === 'noframes' || tag === 'style' || tag === 'script' || tag === 'template';
        // https://html.spec.whatwg.org/multipage/semantics.html#the-html-element
        case 'html':
          return tag === 'head' || tag === 'body';
        case '#document':
          return tag === 'html';
      }
  
      // Probably in the "in body" parsing mode, so we outlaw only tag combos
      // where the parsing rules cause implicit opens or closes to be added.
      // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody
      switch (tag) {
        case 'h1':
        case 'h2':
        case 'h3':
        case 'h4':
        case 'h5':
        case 'h6':
          return parentTag !== 'h1' && parentTag !== 'h2' && parentTag !== 'h3' && parentTag !== 'h4' && parentTag !== 'h5' && parentTag !== 'h6';
  
        case 'rp':
        case 'rt':
          return impliedEndTags.indexOf(parentTag) === -1;
  
        case 'body':
        case 'caption':
        case 'col':
        case 'colgroup':
        case 'frame':
        case 'head':
        case 'html':
        case 'tbody':
        case 'td':
        case 'tfoot':
        case 'th':
        case 'thead':
        case 'tr':
          // These tags are only valid with a few parents that have special child
          // parsing rules -- if we're down here, then none of those matched and
          // so we allow it only if we don't know what the parent is, as all other
          // cases are invalid.
          return parentTag == null;
      }
  
      return true;
    };
  
    /**
     * Returns whether
     */
    var findInvalidAncestorForTag = function (tag, ancestorInfo) {
      switch (tag) {
        case 'address':
        case 'article':
        case 'aside':
        case 'blockquote':
        case 'center':
        case 'details':
        case 'dialog':
        case 'dir':
        case 'div':
        case 'dl':
        case 'fieldset':
        case 'figcaption':
        case 'figure':
        case 'footer':
        case 'header':
        case 'hgroup':
        case 'main':
        case 'menu':
        case 'nav':
        case 'ol':
        case 'p':
        case 'section':
        case 'summary':
        case 'ul':
        case 'pre':
        case 'listing':
        case 'table':
        case 'hr':
        case 'xmp':
        case 'h1':
        case 'h2':
        case 'h3':
        case 'h4':
        case 'h5':
        case 'h6':
          return ancestorInfo.pTagInButtonScope;
  
        case 'form':
          return ancestorInfo.formTag || ancestorInfo.pTagInButtonScope;
  
        case 'li':
          return ancestorInfo.listItemTagAutoclosing;
  
        case 'dd':
        case 'dt':
          return ancestorInfo.dlItemTagAutoclosing;
  
        case 'button':
          return ancestorInfo.buttonTagInScope;
  
        case 'a':
          // Spec says something about storing a list of markers, but it sounds
          // equivalent to this check.
          return ancestorInfo.aTagInScope;
  
        case 'nobr':
          return ancestorInfo.nobrTagInScope;
      }
  
      return null;
    };
  
    var didWarn = {};
  
    validateDOMNesting = function (childTag, childText, ancestorInfo) {
      ancestorInfo = ancestorInfo || emptyAncestorInfo;
      var parentInfo = ancestorInfo.current;
      var parentTag = parentInfo && parentInfo.tag;
  
      if (childText != null) {
        warning(childTag == null, 'validateDOMNesting: when childText is passed, childTag should be null');
        childTag = '#text';
      }
  
      var invalidParent = isTagValidWithParent(childTag, parentTag) ? null : parentInfo;
      var invalidAncestor = invalidParent ? null : findInvalidAncestorForTag(childTag, ancestorInfo);
      var invalidParentOrAncestor = invalidParent || invalidAncestor;
      if (!invalidParentOrAncestor) {
        return;
      }
  
      var ancestorTag = invalidParentOrAncestor.tag;
      var addendum = getCurrentFiberStackAddendum$6();
  
      var warnKey = !!invalidParent + '|' + childTag + '|' + ancestorTag + '|' + addendum;
      if (didWarn[warnKey]) {
        return;
      }
      didWarn[warnKey] = true;
  
      var tagDisplayName = childTag;
      var whitespaceInfo = '';
      if (childTag === '#text') {
        if (/\S/.test(childText)) {
          tagDisplayName = 'Text nodes';
        } else {
          tagDisplayName = 'Whitespace text nodes';
          whitespaceInfo = " Make sure you don't have any extra whitespace between tags on " + 'each line of your source code.';
        }
      } else {
        tagDisplayName = '<' + childTag + '>';
      }
  
      if (invalidParent) {
        var info = '';
        if (ancestorTag === 'table' && childTag === 'tr') {
          info += ' Add a <tbody> to your code to match the DOM tree generated by ' + 'the browser.';
        }
        warning(false, 'validateDOMNesting(...): %s cannot appear as a child of <%s>.%s%s%s', tagDisplayName, ancestorTag, whitespaceInfo, info, addendum);
      } else {
        warning(false, 'validateDOMNesting(...): %s cannot appear as a descendant of ' + '<%s>.%s', tagDisplayName, ancestorTag, addendum);
      }
    };
  
    // TODO: turn this into a named export
    validateDOMNesting.updatedAncestorInfo = updatedAncestorInfo$1;
  
    // For testing
    validateDOMNesting.isTagValidInContext = function (tag, ancestorInfo) {
      ancestorInfo = ancestorInfo || emptyAncestorInfo;
      var parentInfo = ancestorInfo.current;
      var parentTag = parentInfo && parentInfo.tag;
      return isTagValidWithParent(tag, parentTag) && !findInvalidAncestorForTag(tag, ancestorInfo);
    };
  }
  
  var validateDOMNesting$1 = validateDOMNesting;
  
  // TODO: direct imports like some-package/src/* are bad. Fix me.
  var createElement = createElement$1;
  var createTextNode = createTextNode$1;
  var setInitialProperties = setInitialProperties$1;
  var diffProperties = diffProperties$1;
  var updateProperties = updateProperties$1;
  var diffHydratedProperties = diffHydratedProperties$1;
  var diffHydratedText = diffHydratedText$1;
  var warnForUnmatchedText = warnForUnmatchedText$1;
  var warnForDeletedHydratableElement = warnForDeletedHydratableElement$1;
  var warnForDeletedHydratableText = warnForDeletedHydratableText$1;
  var warnForInsertedHydratedElement = warnForInsertedHydratedElement$1;
  var warnForInsertedHydratedText = warnForInsertedHydratedText$1;
  var updatedAncestorInfo = validateDOMNesting$1.updatedAncestorInfo;
  var precacheFiberNode = precacheFiberNode$1;
  var updateFiberProps = updateFiberProps$1;
  
  
  {
    var SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
    if (typeof Map !== 'function' || Map.prototype == null || typeof Map.prototype.forEach !== 'function' || typeof Set !== 'function' || Set.prototype == null || typeof Set.prototype.clear !== 'function' || typeof Set.prototype.forEach !== 'function') {
      warning(false, 'React depends on Map and Set built-in types. Make sure that you load a ' + 'polyfill in older browsers. http://fb.me/react-polyfills');
    }
  }
  
  injection$3.injectFiberControlledHostComponent(ReactDOMFiberComponent);
  
  var eventsEnabled = null;
  var selectionInformation = null;
  
  /**
   * True if the supplied DOM node is a valid node element.
   *
   * @param {?DOMElement} node The candidate DOM node.
   * @return {boolean} True if the DOM is a valid DOM node.
   * @internal
   */
  function isValidContainer(node) {
    return !!(node && (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE || node.nodeType === COMMENT_NODE && node.nodeValue === ' react-mount-point-unstable '));
  }
  
  function getReactRootElementInContainer(container) {
    if (!container) {
      return null;
    }
  
    if (container.nodeType === DOCUMENT_NODE) {
      return container.documentElement;
    } else {
      return container.firstChild;
    }
  }
  
  function shouldHydrateDueToLegacyHeuristic(container) {
    var rootElement = getReactRootElementInContainer(container);
    return !!(rootElement && rootElement.nodeType === ELEMENT_NODE && rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME));
  }
  
  function shouldAutoFocusHostComponent(type, props) {
    switch (type) {
      case 'button':
      case 'input':
      case 'select':
      case 'textarea':
        return !!props.autoFocus;
    }
    return false;
  }
  
  var DOMRenderer = reactReconciler({
    getRootHostContext: function (rootContainerInstance) {
      var type = void 0;
      var namespace = void 0;
      var nodeType = rootContainerInstance.nodeType;
      switch (nodeType) {
        case DOCUMENT_NODE:
        case DOCUMENT_FRAGMENT_NODE:
          {
            type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';
            var root = rootContainerInstance.documentElement;
            namespace = root ? root.namespaceURI : getChildNamespace(null, '');
            break;
          }
        default:
          {
            var container = nodeType === COMMENT_NODE ? rootContainerInstance.parentNode : rootContainerInstance;
            var ownNamespace = container.namespaceURI || null;
            type = container.tagName;
            namespace = getChildNamespace(ownNamespace, type);
            break;
          }
      }
      {
        var validatedTag = type.toLowerCase();
        var _ancestorInfo = updatedAncestorInfo(null, validatedTag, null);
        return { namespace: namespace, ancestorInfo: _ancestorInfo };
      }
      return namespace;
    },
    getChildHostContext: function (parentHostContext, type) {
      {
        var parentHostContextDev = parentHostContext;
        var _namespace = getChildNamespace(parentHostContextDev.namespace, type);
        var _ancestorInfo2 = updatedAncestorInfo(parentHostContextDev.ancestorInfo, type, null);
        return { namespace: _namespace, ancestorInfo: _ancestorInfo2 };
      }
      var parentNamespace = parentHostContext;
      return getChildNamespace(parentNamespace, type);
    },
    getPublicInstance: function (instance) {
      return instance;
    },
    prepareForCommit: function () {
      eventsEnabled = isEnabled();
      selectionInformation = getSelectionInformation();
      setEnabled(false);
    },
    resetAfterCommit: function () {
      restoreSelection(selectionInformation);
      selectionInformation = null;
      setEnabled(eventsEnabled);
      eventsEnabled = null;
    },
    createInstance: function (type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
      var parentNamespace = void 0;
      {
        // TODO: take namespace into account when validating.
        var hostContextDev = hostContext;
        validateDOMNesting$1(type, null, hostContextDev.ancestorInfo);
        if (typeof props.children === 'string' || typeof props.children === 'number') {
          var string = '' + props.children;
          var ownAncestorInfo = updatedAncestorInfo(hostContextDev.ancestorInfo, type, null);
          validateDOMNesting$1(null, string, ownAncestorInfo);
        }
        parentNamespace = hostContextDev.namespace;
      }
      var domElement = createElement(type, props, rootContainerInstance, parentNamespace);
      precacheFiberNode(internalInstanceHandle, domElement);
      updateFiberProps(domElement, props);
      return domElement;
    },
    appendInitialChild: function (parentInstance, child) {
      parentInstance.appendChild(child);
    },
    finalizeInitialChildren: function (domElement, type, props, rootContainerInstance) {
      setInitialProperties(domElement, type, props, rootContainerInstance);
      return shouldAutoFocusHostComponent(type, props);
    },
    prepareUpdate: function (domElement, type, oldProps, newProps, rootContainerInstance, hostContext) {
      {
        var hostContextDev = hostContext;
        if (typeof newProps.children !== typeof oldProps.children && (typeof newProps.children === 'string' || typeof newProps.children === 'number')) {
          var string = '' + newProps.children;
          var ownAncestorInfo = updatedAncestorInfo(hostContextDev.ancestorInfo, type, null);
          validateDOMNesting$1(null, string, ownAncestorInfo);
        }
      }
      return diffProperties(domElement, type, oldProps, newProps, rootContainerInstance);
    },
    shouldSetTextContent: function (type, props) {
      return type === 'textarea' || typeof props.children === 'string' || typeof props.children === 'number' || typeof props.dangerouslySetInnerHTML === 'object' && props.dangerouslySetInnerHTML !== null && typeof props.dangerouslySetInnerHTML.__html === 'string';
    },
    shouldDeprioritizeSubtree: function (type, props) {
      return !!props.hidden;
    },
    createTextInstance: function (text, rootContainerInstance, hostContext, internalInstanceHandle) {
      {
        var hostContextDev = hostContext;
        validateDOMNesting$1(null, text, hostContextDev.ancestorInfo);
      }
      var textNode = createTextNode(text, rootContainerInstance);
      precacheFiberNode(internalInstanceHandle, textNode);
      return textNode;
    },
  
  
    now: now,
  
    mutation: {
      commitMount: function (domElement, type, newProps, internalInstanceHandle) {
        domElement.focus();
      },
      commitUpdate: function (domElement, updatePayload, type, oldProps, newProps, internalInstanceHandle) {
        // Update the props handle so that we know which props are the ones with
        // with current event handlers.
        updateFiberProps(domElement, newProps);
        // Apply the diff to the DOM node.
        updateProperties(domElement, updatePayload, type, oldProps, newProps);
      },
      resetTextContent: function (domElement) {
        domElement.textContent = '';
      },
      commitTextUpdate: function (textInstance, oldText, newText) {
        textInstance.nodeValue = newText;
      },
      appendChild: function (parentInstance, child) {
        parentInstance.appendChild(child);
      },
      appendChildToContainer: function (container, child) {
        if (container.nodeType === COMMENT_NODE) {
          container.parentNode.insertBefore(child, container);
        } else {
          container.appendChild(child);
        }
      },
      insertBefore: function (parentInstance, child, beforeChild) {
        parentInstance.insertBefore(child, beforeChild);
      },
      insertInContainerBefore: function (container, child, beforeChild) {
        if (container.nodeType === COMMENT_NODE) {
          container.parentNode.insertBefore(child, beforeChild);
        } else {
          container.insertBefore(child, beforeChild);
        }
      },
      removeChild: function (parentInstance, child) {
        parentInstance.removeChild(child);
      },
      removeChildFromContainer: function (container, child) {
        if (container.nodeType === COMMENT_NODE) {
          container.parentNode.removeChild(child);
        } else {
          container.removeChild(child);
        }
      }
    },
  
    hydration: {
      canHydrateInstance: function (instance, type, props) {
        if (instance.nodeType !== ELEMENT_NODE || type.toLowerCase() !== instance.nodeName.toLowerCase()) {
          return null;
        }
        // This has now been refined to an element node.
        return instance;
      },
      canHydrateTextInstance: function (instance, text) {
        if (text === '' || instance.nodeType !== TEXT_NODE) {
          // Empty strings are not parsed by HTML so there won't be a correct match here.
          return null;
        }
        // This has now been refined to a text node.
        return instance;
      },
      getNextHydratableSibling: function (instance) {
        var node = instance.nextSibling;
        // Skip non-hydratable nodes.
        while (node && node.nodeType !== ELEMENT_NODE && node.nodeType !== TEXT_NODE) {
          node = node.nextSibling;
        }
        return node;
      },
      getFirstHydratableChild: function (parentInstance) {
        var next = parentInstance.firstChild;
        // Skip non-hydratable nodes.
        while (next && next.nodeType !== ELEMENT_NODE && next.nodeType !== TEXT_NODE) {
          next = next.nextSibling;
        }
        return next;
      },
      hydrateInstance: function (instance, type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
        precacheFiberNode(internalInstanceHandle, instance);
        // TODO: Possibly defer this until the commit phase where all the events
        // get attached.
        updateFiberProps(instance, props);
        var parentNamespace = void 0;
        {
          var hostContextDev = hostContext;
          parentNamespace = hostContextDev.namespace;
        }
        return diffHydratedProperties(instance, type, props, parentNamespace, rootContainerInstance);
      },
      hydrateTextInstance: function (textInstance, text, internalInstanceHandle) {
        precacheFiberNode(internalInstanceHandle, textInstance);
        return diffHydratedText(textInstance, text);
      },
      didNotMatchHydratedContainerTextInstance: function (parentContainer, textInstance, text) {
        {
          warnForUnmatchedText(textInstance, text);
        }
      },
      didNotMatchHydratedTextInstance: function (parentType, parentProps, parentInstance, textInstance, text) {
        if (true && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
          warnForUnmatchedText(textInstance, text);
        }
      },
      didNotHydrateContainerInstance: function (parentContainer, instance) {
        {
          if (instance.nodeType === 1) {
            warnForDeletedHydratableElement(parentContainer, instance);
          } else {
            warnForDeletedHydratableText(parentContainer, instance);
          }
        }
      },
      didNotHydrateInstance: function (parentType, parentProps, parentInstance, instance) {
        if (true && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
          if (instance.nodeType === 1) {
            warnForDeletedHydratableElement(parentInstance, instance);
          } else {
            warnForDeletedHydratableText(parentInstance, instance);
          }
        }
      },
      didNotFindHydratableContainerInstance: function (parentContainer, type, props) {
        {
          warnForInsertedHydratedElement(parentContainer, type, props);
        }
      },
      didNotFindHydratableContainerTextInstance: function (parentContainer, text) {
        {
          warnForInsertedHydratedText(parentContainer, text);
        }
      },
      didNotFindHydratableInstance: function (parentType, parentProps, parentInstance, type, props) {
        if (true && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
          warnForInsertedHydratedElement(parentInstance, type, props);
        }
      },
      didNotFindHydratableTextInstance: function (parentType, parentProps, parentInstance, text) {
        if (true && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
          warnForInsertedHydratedText(parentInstance, text);
        }
      }
    },
  
    scheduleDeferredCallback: rIC,
    cancelDeferredCallback: cIC,
  
    useSyncScheduling: !enableAsyncSchedulingByDefaultInReactDOM
  });
  
  injection$4.injectFiberBatchedUpdates(DOMRenderer.batchedUpdates);
  
  var warnedAboutHydrateAPI = false;
  
  function renderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
    !isValidContainer(container) ? invariant(false, 'Target container is not a DOM element.') : void 0;
  
    {
      if (container._reactRootContainer && container.nodeType !== COMMENT_NODE) {
        var hostInstance = DOMRenderer.findHostInstanceWithNoPortals(container._reactRootContainer.current);
        if (hostInstance) {
          warning(hostInstance.parentNode === container, 'render(...): It looks like the React-rendered content of this ' + 'container was removed without using React. This is not ' + 'supported and will cause errors. Instead, call ' + 'ReactDOM.unmountComponentAtNode to empty a container.');
        }
      }
  
      var isRootRenderedBySomeReact = !!container._reactRootContainer;
      var rootEl = getReactRootElementInContainer(container);
      var hasNonRootReactChild = !!(rootEl && getInstanceFromNode$1(rootEl));
  
      warning(!hasNonRootReactChild || isRootRenderedBySomeReact, 'render(...): Replacing React-rendered children with a new root ' + 'component. If you intended to update the children of this node, ' + 'you should instead have the existing children update their state ' + 'and render the new components instead of calling ReactDOM.render.');
  
      warning(container.nodeType !== ELEMENT_NODE || !container.tagName || container.tagName.toUpperCase() !== 'BODY', 'render(): Rendering components directly into document.body is ' + 'discouraged, since its children are often manipulated by third-party ' + 'scripts and browser extensions. This may lead to subtle ' + 'reconciliation issues. Try rendering into a container element created ' + 'for your app.');
    }
  
    var root = container._reactRootContainer;
    if (!root) {
      var shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
      // First clear any existing content.
      if (!shouldHydrate) {
        var warned = false;
        var rootSibling = void 0;
        while (rootSibling = container.lastChild) {
          {
            if (!warned && rootSibling.nodeType === ELEMENT_NODE && rootSibling.hasAttribute(ROOT_ATTRIBUTE_NAME)) {
              warned = true;
              warning(false, 'render(): Target node has markup rendered by React, but there ' + 'are unrelated nodes as well. This is most commonly caused by ' + 'white-space inserted around server-rendered markup.');
            }
          }
          container.removeChild(rootSibling);
        }
      }
      {
        if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
          warnedAboutHydrateAPI = true;
          lowPriorityWarning$1(false, 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' + 'will stop working in React v17. Replace the ReactDOM.render() call ' + 'with ReactDOM.hydrate() if you want React to attach to the server HTML.');
        }
      }
      var newRoot = DOMRenderer.createContainer(container, shouldHydrate);
      root = container._reactRootContainer = newRoot;
      // Initial mount should not be batched.
      DOMRenderer.unbatchedUpdates(function () {
        DOMRenderer.updateContainer(children, newRoot, parentComponent, callback);
      });
    } else {
      DOMRenderer.updateContainer(children, root, parentComponent, callback);
    }
    return DOMRenderer.getPublicRootInstance(root);
  }
  
  function createPortal(children, container) {
    var key = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
  
    !isValidContainer(container) ? invariant(false, 'Target container is not a DOM element.') : void 0;
    // TODO: pass ReactDOM portal implementation as third argument
    return createPortal$1(children, container, null, key);
  }
  
  function ReactRoot(container, hydrate) {
    var root = DOMRenderer.createContainer(container, hydrate);
    this._reactRootContainer = root;
  }
  ReactRoot.prototype.render = function (children, callback) {
    var root = this._reactRootContainer;
    DOMRenderer.updateContainer(children, root, null, callback);
  };
  ReactRoot.prototype.unmount = function (callback) {
    var root = this._reactRootContainer;
    DOMRenderer.updateContainer(null, root, null, callback);
  };
  
  var ReactDOM = {
    createPortal: createPortal,
  
    findDOMNode: function (componentOrElement) {
      {
        var owner = ReactCurrentOwner.current;
        if (owner !== null) {
          var warnedAboutRefsInRender = owner.stateNode._warnedAboutRefsInRender;
          warning(warnedAboutRefsInRender, '%s is accessing findDOMNode inside its render(). ' + 'render() should be a pure function of props and state. It should ' + 'never access something that requires stale data from the previous ' + 'render, such as refs. Move this logic to componentDidMount and ' + 'componentDidUpdate instead.', getComponentName(owner) || 'A component');
          owner.stateNode._warnedAboutRefsInRender = true;
        }
      }
      if (componentOrElement == null) {
        return null;
      }
      if (componentOrElement.nodeType === ELEMENT_NODE) {
        return componentOrElement;
      }
  
      var inst = get(componentOrElement);
      if (inst) {
        return DOMRenderer.findHostInstance(inst);
      }
  
      if (typeof componentOrElement.render === 'function') {
        invariant(false, 'Unable to find node on an unmounted component.');
      } else {
        invariant(false, 'Element appears to be neither ReactComponent nor DOMNode. Keys: %s', Object.keys(componentOrElement));
      }
    },
    hydrate: function (element, container, callback) {
      // TODO: throw or warn if we couldn't hydrate?
      return renderSubtreeIntoContainer(null, element, container, true, callback);
    },
    render: function (element, container, callback) {
      return renderSubtreeIntoContainer(null, element, container, false, callback);
    },
    unstable_renderSubtreeIntoContainer: function (parentComponent, element, containerNode, callback) {
      !(parentComponent != null && has(parentComponent)) ? invariant(false, 'parentComponent must be a valid React Component') : void 0;
      return renderSubtreeIntoContainer(parentComponent, element, containerNode, false, callback);
    },
    unmountComponentAtNode: function (container) {
      !isValidContainer(container) ? invariant(false, 'unmountComponentAtNode(...): Target container is not a DOM element.') : void 0;
  
      if (container._reactRootContainer) {
        {
          var rootEl = getReactRootElementInContainer(container);
          var renderedByDifferentReact = rootEl && !getInstanceFromNode$1(rootEl);
          warning(!renderedByDifferentReact, "unmountComponentAtNode(): The node you're attempting to unmount " + 'was rendered by another copy of React.');
        }
  
        // Unmount should not be batched.
        DOMRenderer.unbatchedUpdates(function () {
          renderSubtreeIntoContainer(null, null, container, false, function () {
            container._reactRootContainer = null;
          });
        });
        // If you call unmountComponentAtNode twice in quick succession, you'll
        // get `true` twice. That's probably fine?
        return true;
      } else {
        {
          var _rootEl = getReactRootElementInContainer(container);
          var hasNonRootReactChild = !!(_rootEl && getInstanceFromNode$1(_rootEl));
  
          // Check if the container itself is a React root node.
          var isContainerReactRoot = container.nodeType === 1 && isValidContainer(container.parentNode) && !!container.parentNode._reactRootContainer;
  
          warning(!hasNonRootReactChild, "unmountComponentAtNode(): The node you're attempting to unmount " + 'was rendered by React and is not a top-level container. %s', isContainerReactRoot ? 'You may have accidentally passed in a React root node instead ' + 'of its container.' : 'Instead, have the parent component update its state and ' + 'rerender in order to remove this component.');
        }
  
        return false;
      }
    },
  
  
    // Temporary alias since we already shipped React 16 RC with it.
    // TODO: remove in React 17.
    unstable_createPortal: createPortal,
  
    unstable_batchedUpdates: batchedUpdates,
  
    unstable_deferredUpdates: DOMRenderer.deferredUpdates,
  
    flushSync: DOMRenderer.flushSync,
  
    __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
      // For TapEventPlugin which is popular in open source
      EventPluginHub: EventPluginHub,
      // Used by test-utils
      EventPluginRegistry: EventPluginRegistry,
      EventPropagators: EventPropagators,
      ReactControlledComponent: ReactControlledComponent,
      ReactDOMComponentTree: ReactDOMComponentTree,
      ReactDOMEventListener: ReactDOMEventListener
    }
  };
  
  if (enableCreateRoot) {
    ReactDOM.createRoot = function createRoot(container, options) {
      var hydrate = options != null && options.hydrate === true;
      return new ReactRoot(container, hydrate);
    };
  }
  
  var foundDevTools = DOMRenderer.injectIntoDevTools({
    findFiberByHostInstance: getClosestInstanceFromNode,
    bundleType: 1,
    version: ReactVersion,
    rendererPackageName: 'react-dom'
  });
  
  {
    if (!foundDevTools && ExecutionEnvironment.canUseDOM && window.top === window.self) {
      // If we're in Chrome or Firefox, provide a download link if not installed.
      if (navigator.userAgent.indexOf('Chrome') > -1 && navigator.userAgent.indexOf('Edge') === -1 || navigator.userAgent.indexOf('Firefox') > -1) {
        var protocol = window.location.protocol;
        // Don't warn in exotic cases like chrome-extension://.
        if (/^(https?|file):$/.test(protocol)) {
          console.info('%cDownload the React DevTools ' + 'for a better development experience: ' + 'https://fb.me/react-devtools' + (protocol === 'file:' ? '\nYou might need to use a local HTTP server (instead of file://): ' + 'https://fb.me/react-devtools-faq' : ''), 'font-weight:bold');
        }
      }
    }
  }
  
  
  
  var ReactDOM$2 = Object.freeze({
    default: ReactDOM
  });
  
  var ReactDOM$3 = ( ReactDOM$2 && ReactDOM ) || ReactDOM$2;
  
  // TODO: decide on the top-level export form.
  // This is hacky but makes it work with both Rollup and Jest.
  var reactDom = ReactDOM$3['default'] ? ReactDOM$3['default'] : ReactDOM$3;
  
  module.exports = reactDom;
    })();
  }
  
  },{"fbjs/lib/EventListener":333,"fbjs/lib/ExecutionEnvironment":334,"fbjs/lib/camelizeStyleName":336,"fbjs/lib/containsNode":337,"fbjs/lib/emptyFunction":338,"fbjs/lib/emptyObject":339,"fbjs/lib/focusNode":340,"fbjs/lib/getActiveElement":341,"fbjs/lib/hyphenateStyleName":343,"fbjs/lib/invariant":344,"fbjs/lib/shallowEqual":347,"fbjs/lib/warning":348,"object-assign":480,"prop-types/checkPropTypes":498,"react":522}],496:[function(require,module,exports){
  /** @license React v16.2.0
   * react-dom.production.min.js
   *
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   */
  
  /*
   Modernizr 3.0.0pre (Custom Build) | MIT
  */
  'use strict';var aa=require("react"),l=require("fbjs/lib/ExecutionEnvironment"),B=require("object-assign"),C=require("fbjs/lib/emptyFunction"),ba=require("fbjs/lib/EventListener"),da=require("fbjs/lib/getActiveElement"),ea=require("fbjs/lib/shallowEqual"),fa=require("fbjs/lib/containsNode"),ia=require("fbjs/lib/focusNode"),D=require("fbjs/lib/emptyObject");
  function E(a){for(var b=arguments.length-1,c="Minified React error #"+a+"; visit http://facebook.github.io/react/docs/error-decoder.html?invariant\x3d"+a,d=0;d<b;d++)c+="\x26args[]\x3d"+encodeURIComponent(arguments[d+1]);b=Error(c+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings.");b.name="Invariant Violation";b.framesToPop=1;throw b;}aa?void 0:E("227");
  var oa={children:!0,dangerouslySetInnerHTML:!0,defaultValue:!0,defaultChecked:!0,innerHTML:!0,suppressContentEditableWarning:!0,suppressHydrationWarning:!0,style:!0};function pa(a,b){return(a&b)===b}
  var ta={MUST_USE_PROPERTY:1,HAS_BOOLEAN_VALUE:4,HAS_NUMERIC_VALUE:8,HAS_POSITIVE_NUMERIC_VALUE:24,HAS_OVERLOADED_BOOLEAN_VALUE:32,HAS_STRING_BOOLEAN_VALUE:64,injectDOMPropertyConfig:function(a){var b=ta,c=a.Properties||{},d=a.DOMAttributeNamespaces||{},e=a.DOMAttributeNames||{};a=a.DOMMutationMethods||{};for(var f in c){ua.hasOwnProperty(f)?E("48",f):void 0;var g=f.toLowerCase(),h=c[f];g={attributeName:g,attributeNamespace:null,propertyName:f,mutationMethod:null,mustUseProperty:pa(h,b.MUST_USE_PROPERTY),
  hasBooleanValue:pa(h,b.HAS_BOOLEAN_VALUE),hasNumericValue:pa(h,b.HAS_NUMERIC_VALUE),hasPositiveNumericValue:pa(h,b.HAS_POSITIVE_NUMERIC_VALUE),hasOverloadedBooleanValue:pa(h,b.HAS_OVERLOADED_BOOLEAN_VALUE),hasStringBooleanValue:pa(h,b.HAS_STRING_BOOLEAN_VALUE)};1>=g.hasBooleanValue+g.hasNumericValue+g.hasOverloadedBooleanValue?void 0:E("50",f);e.hasOwnProperty(f)&&(g.attributeName=e[f]);d.hasOwnProperty(f)&&(g.attributeNamespace=d[f]);a.hasOwnProperty(f)&&(g.mutationMethod=a[f]);ua[f]=g}}},ua={};
  function va(a,b){if(oa.hasOwnProperty(a)||2<a.length&&("o"===a[0]||"O"===a[0])&&("n"===a[1]||"N"===a[1]))return!1;if(null===b)return!0;switch(typeof b){case "boolean":return oa.hasOwnProperty(a)?a=!0:(b=wa(a))?a=b.hasBooleanValue||b.hasStringBooleanValue||b.hasOverloadedBooleanValue:(a=a.toLowerCase().slice(0,5),a="data-"===a||"aria-"===a),a;case "undefined":case "number":case "string":case "object":return!0;default:return!1}}function wa(a){return ua.hasOwnProperty(a)?ua[a]:null}
  var xa=ta,ya=xa.MUST_USE_PROPERTY,K=xa.HAS_BOOLEAN_VALUE,za=xa.HAS_NUMERIC_VALUE,Aa=xa.HAS_POSITIVE_NUMERIC_VALUE,Ba=xa.HAS_OVERLOADED_BOOLEAN_VALUE,Ca=xa.HAS_STRING_BOOLEAN_VALUE,Da={Properties:{allowFullScreen:K,async:K,autoFocus:K,autoPlay:K,capture:Ba,checked:ya|K,cols:Aa,contentEditable:Ca,controls:K,"default":K,defer:K,disabled:K,download:Ba,draggable:Ca,formNoValidate:K,hidden:K,loop:K,multiple:ya|K,muted:ya|K,noValidate:K,open:K,playsInline:K,readOnly:K,required:K,reversed:K,rows:Aa,rowSpan:za,
  scoped:K,seamless:K,selected:ya|K,size:Aa,start:za,span:Aa,spellCheck:Ca,style:0,tabIndex:0,itemScope:K,acceptCharset:0,className:0,htmlFor:0,httpEquiv:0,value:Ca},DOMAttributeNames:{acceptCharset:"accept-charset",className:"class",htmlFor:"for",httpEquiv:"http-equiv"},DOMMutationMethods:{value:function(a,b){if(null==b)return a.removeAttribute("value");"number"!==a.type||!1===a.hasAttribute("value")?a.setAttribute("value",""+b):a.validity&&!a.validity.badInput&&a.ownerDocument.activeElement!==a&&
  a.setAttribute("value",""+b)}}},Ea=xa.HAS_STRING_BOOLEAN_VALUE,M={xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace"},Ga={Properties:{autoReverse:Ea,externalResourcesRequired:Ea,preserveAlpha:Ea},DOMAttributeNames:{autoReverse:"autoReverse",externalResourcesRequired:"externalResourcesRequired",preserveAlpha:"preserveAlpha"},DOMAttributeNamespaces:{xlinkActuate:M.xlink,xlinkArcrole:M.xlink,xlinkHref:M.xlink,xlinkRole:M.xlink,xlinkShow:M.xlink,xlinkTitle:M.xlink,xlinkType:M.xlink,
  xmlBase:M.xml,xmlLang:M.xml,xmlSpace:M.xml}},Ha=/[\-\:]([a-z])/g;function Ia(a){return a[1].toUpperCase()}
  "accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode x-height xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xmlns:xlink xml:lang xml:space".split(" ").forEach(function(a){var b=a.replace(Ha,
  Ia);Ga.Properties[b]=0;Ga.DOMAttributeNames[b]=a});xa.injectDOMPropertyConfig(Da);xa.injectDOMPropertyConfig(Ga);
  var P={_caughtError:null,_hasCaughtError:!1,_rethrowError:null,_hasRethrowError:!1,injection:{injectErrorUtils:function(a){"function"!==typeof a.invokeGuardedCallback?E("197"):void 0;Ja=a.invokeGuardedCallback}},invokeGuardedCallback:function(a,b,c,d,e,f,g,h,k){Ja.apply(P,arguments)},invokeGuardedCallbackAndCatchFirstError:function(a,b,c,d,e,f,g,h,k){P.invokeGuardedCallback.apply(this,arguments);if(P.hasCaughtError()){var q=P.clearCaughtError();P._hasRethrowError||(P._hasRethrowError=!0,P._rethrowError=
  q)}},rethrowCaughtError:function(){return Ka.apply(P,arguments)},hasCaughtError:function(){return P._hasCaughtError},clearCaughtError:function(){if(P._hasCaughtError){var a=P._caughtError;P._caughtError=null;P._hasCaughtError=!1;return a}E("198")}};function Ja(a,b,c,d,e,f,g,h,k){P._hasCaughtError=!1;P._caughtError=null;var q=Array.prototype.slice.call(arguments,3);try{b.apply(c,q)}catch(v){P._caughtError=v,P._hasCaughtError=!0}}
  function Ka(){if(P._hasRethrowError){var a=P._rethrowError;P._rethrowError=null;P._hasRethrowError=!1;throw a;}}var La=null,Ma={};
  function Na(){if(La)for(var a in Ma){var b=Ma[a],c=La.indexOf(a);-1<c?void 0:E("96",a);if(!Oa[c]){b.extractEvents?void 0:E("97",a);Oa[c]=b;c=b.eventTypes;for(var d in c){var e=void 0;var f=c[d],g=b,h=d;Pa.hasOwnProperty(h)?E("99",h):void 0;Pa[h]=f;var k=f.phasedRegistrationNames;if(k){for(e in k)k.hasOwnProperty(e)&&Qa(k[e],g,h);e=!0}else f.registrationName?(Qa(f.registrationName,g,h),e=!0):e=!1;e?void 0:E("98",d,a)}}}}
  function Qa(a,b,c){Ra[a]?E("100",a):void 0;Ra[a]=b;Sa[a]=b.eventTypes[c].dependencies}var Oa=[],Pa={},Ra={},Sa={};function Ta(a){La?E("101"):void 0;La=Array.prototype.slice.call(a);Na()}function Ua(a){var b=!1,c;for(c in a)if(a.hasOwnProperty(c)){var d=a[c];Ma.hasOwnProperty(c)&&Ma[c]===d||(Ma[c]?E("102",c):void 0,Ma[c]=d,b=!0)}b&&Na()}
  var Va=Object.freeze({plugins:Oa,eventNameDispatchConfigs:Pa,registrationNameModules:Ra,registrationNameDependencies:Sa,possibleRegistrationNames:null,injectEventPluginOrder:Ta,injectEventPluginsByName:Ua}),Wa=null,Xa=null,Ya=null;function Za(a,b,c,d){b=a.type||"unknown-event";a.currentTarget=Ya(d);P.invokeGuardedCallbackAndCatchFirstError(b,c,void 0,a);a.currentTarget=null}
  function $a(a,b){null==b?E("30"):void 0;if(null==a)return b;if(Array.isArray(a)){if(Array.isArray(b))return a.push.apply(a,b),a;a.push(b);return a}return Array.isArray(b)?[a].concat(b):[a,b]}function ab(a,b,c){Array.isArray(a)?a.forEach(b,c):a&&b.call(c,a)}var bb=null;
  function cb(a,b){if(a){var c=a._dispatchListeners,d=a._dispatchInstances;if(Array.isArray(c))for(var e=0;e<c.length&&!a.isPropagationStopped();e++)Za(a,b,c[e],d[e]);else c&&Za(a,b,c,d);a._dispatchListeners=null;a._dispatchInstances=null;a.isPersistent()||a.constructor.release(a)}}function db(a){return cb(a,!0)}function gb(a){return cb(a,!1)}var hb={injectEventPluginOrder:Ta,injectEventPluginsByName:Ua};
  function ib(a,b){var c=a.stateNode;if(!c)return null;var d=Wa(c);if(!d)return null;c=d[b];a:switch(b){case "onClick":case "onClickCapture":case "onDoubleClick":case "onDoubleClickCapture":case "onMouseDown":case "onMouseDownCapture":case "onMouseMove":case "onMouseMoveCapture":case "onMouseUp":case "onMouseUpCapture":(d=!d.disabled)||(a=a.type,d=!("button"===a||"input"===a||"select"===a||"textarea"===a));a=!d;break a;default:a=!1}if(a)return null;c&&"function"!==typeof c?E("231",b,typeof c):void 0;
  return c}function jb(a,b,c,d){for(var e,f=0;f<Oa.length;f++){var g=Oa[f];g&&(g=g.extractEvents(a,b,c,d))&&(e=$a(e,g))}return e}function kb(a){a&&(bb=$a(bb,a))}function lb(a){var b=bb;bb=null;b&&(a?ab(b,db):ab(b,gb),bb?E("95"):void 0,P.rethrowCaughtError())}var mb=Object.freeze({injection:hb,getListener:ib,extractEvents:jb,enqueueEvents:kb,processEventQueue:lb}),nb=Math.random().toString(36).slice(2),Q="__reactInternalInstance$"+nb,ob="__reactEventHandlers$"+nb;
  function pb(a){if(a[Q])return a[Q];for(var b=[];!a[Q];)if(b.push(a),a.parentNode)a=a.parentNode;else return null;var c=void 0,d=a[Q];if(5===d.tag||6===d.tag)return d;for(;a&&(d=a[Q]);a=b.pop())c=d;return c}function qb(a){if(5===a.tag||6===a.tag)return a.stateNode;E("33")}function rb(a){return a[ob]||null}
  var sb=Object.freeze({precacheFiberNode:function(a,b){b[Q]=a},getClosestInstanceFromNode:pb,getInstanceFromNode:function(a){a=a[Q];return!a||5!==a.tag&&6!==a.tag?null:a},getNodeFromInstance:qb,getFiberCurrentPropsFromNode:rb,updateFiberProps:function(a,b){a[ob]=b}});function tb(a){do a=a["return"];while(a&&5!==a.tag);return a?a:null}function ub(a,b,c){for(var d=[];a;)d.push(a),a=tb(a);for(a=d.length;0<a--;)b(d[a],"captured",c);for(a=0;a<d.length;a++)b(d[a],"bubbled",c)}
  function vb(a,b,c){if(b=ib(a,c.dispatchConfig.phasedRegistrationNames[b]))c._dispatchListeners=$a(c._dispatchListeners,b),c._dispatchInstances=$a(c._dispatchInstances,a)}function wb(a){a&&a.dispatchConfig.phasedRegistrationNames&&ub(a._targetInst,vb,a)}function xb(a){if(a&&a.dispatchConfig.phasedRegistrationNames){var b=a._targetInst;b=b?tb(b):null;ub(b,vb,a)}}
  function yb(a,b,c){a&&c&&c.dispatchConfig.registrationName&&(b=ib(a,c.dispatchConfig.registrationName))&&(c._dispatchListeners=$a(c._dispatchListeners,b),c._dispatchInstances=$a(c._dispatchInstances,a))}function zb(a){a&&a.dispatchConfig.registrationName&&yb(a._targetInst,null,a)}function Ab(a){ab(a,wb)}
  function Bb(a,b,c,d){if(c&&d)a:{var e=c;for(var f=d,g=0,h=e;h;h=tb(h))g++;h=0;for(var k=f;k;k=tb(k))h++;for(;0<g-h;)e=tb(e),g--;for(;0<h-g;)f=tb(f),h--;for(;g--;){if(e===f||e===f.alternate)break a;e=tb(e);f=tb(f)}e=null}else e=null;f=e;for(e=[];c&&c!==f;){g=c.alternate;if(null!==g&&g===f)break;e.push(c);c=tb(c)}for(c=[];d&&d!==f;){g=d.alternate;if(null!==g&&g===f)break;c.push(d);d=tb(d)}for(d=0;d<e.length;d++)yb(e[d],"bubbled",a);for(a=c.length;0<a--;)yb(c[a],"captured",b)}
  var Cb=Object.freeze({accumulateTwoPhaseDispatches:Ab,accumulateTwoPhaseDispatchesSkipTarget:function(a){ab(a,xb)},accumulateEnterLeaveDispatches:Bb,accumulateDirectDispatches:function(a){ab(a,zb)}}),Db=null;function Eb(){!Db&&l.canUseDOM&&(Db="textContent"in document.documentElement?"textContent":"innerText");return Db}var S={_root:null,_startText:null,_fallbackText:null};
  function Fb(){if(S._fallbackText)return S._fallbackText;var a,b=S._startText,c=b.length,d,e=Gb(),f=e.length;for(a=0;a<c&&b[a]===e[a];a++);var g=c-a;for(d=1;d<=g&&b[c-d]===e[f-d];d++);S._fallbackText=e.slice(a,1<d?1-d:void 0);return S._fallbackText}function Gb(){return"value"in S._root?S._root.value:S._root[Eb()]}
  var Hb="dispatchConfig _targetInst nativeEvent isDefaultPrevented isPropagationStopped _dispatchListeners _dispatchInstances".split(" "),Ib={type:null,target:null,currentTarget:C.thatReturnsNull,eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(a){return a.timeStamp||Date.now()},defaultPrevented:null,isTrusted:null};
  function T(a,b,c,d){this.dispatchConfig=a;this._targetInst=b;this.nativeEvent=c;a=this.constructor.Interface;for(var e in a)a.hasOwnProperty(e)&&((b=a[e])?this[e]=b(c):"target"===e?this.target=d:this[e]=c[e]);this.isDefaultPrevented=(null!=c.defaultPrevented?c.defaultPrevented:!1===c.returnValue)?C.thatReturnsTrue:C.thatReturnsFalse;this.isPropagationStopped=C.thatReturnsFalse;return this}
  B(T.prototype,{preventDefault:function(){this.defaultPrevented=!0;var a=this.nativeEvent;a&&(a.preventDefault?a.preventDefault():"unknown"!==typeof a.returnValue&&(a.returnValue=!1),this.isDefaultPrevented=C.thatReturnsTrue)},stopPropagation:function(){var a=this.nativeEvent;a&&(a.stopPropagation?a.stopPropagation():"unknown"!==typeof a.cancelBubble&&(a.cancelBubble=!0),this.isPropagationStopped=C.thatReturnsTrue)},persist:function(){this.isPersistent=C.thatReturnsTrue},isPersistent:C.thatReturnsFalse,
  destructor:function(){var a=this.constructor.Interface,b;for(b in a)this[b]=null;for(a=0;a<Hb.length;a++)this[Hb[a]]=null}});T.Interface=Ib;T.augmentClass=function(a,b){function c(){}c.prototype=this.prototype;var d=new c;B(d,a.prototype);a.prototype=d;a.prototype.constructor=a;a.Interface=B({},this.Interface,b);a.augmentClass=this.augmentClass;Jb(a)};Jb(T);function Kb(a,b,c,d){if(this.eventPool.length){var e=this.eventPool.pop();this.call(e,a,b,c,d);return e}return new this(a,b,c,d)}
  function Lb(a){a instanceof this?void 0:E("223");a.destructor();10>this.eventPool.length&&this.eventPool.push(a)}function Jb(a){a.eventPool=[];a.getPooled=Kb;a.release=Lb}function Mb(a,b,c,d){return T.call(this,a,b,c,d)}T.augmentClass(Mb,{data:null});function Nb(a,b,c,d){return T.call(this,a,b,c,d)}T.augmentClass(Nb,{data:null});var Pb=[9,13,27,32],Vb=l.canUseDOM&&"CompositionEvent"in window,Wb=null;l.canUseDOM&&"documentMode"in document&&(Wb=document.documentMode);var Xb;
  if(Xb=l.canUseDOM&&"TextEvent"in window&&!Wb){var Yb=window.opera;Xb=!("object"===typeof Yb&&"function"===typeof Yb.version&&12>=parseInt(Yb.version(),10))}
  var Zb=Xb,$b=l.canUseDOM&&(!Vb||Wb&&8<Wb&&11>=Wb),ac=String.fromCharCode(32),bc={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["topCompositionEnd","topKeyPress","topTextInput","topPaste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:"topBlur topCompositionEnd topKeyDown topKeyPress topKeyUp topMouseDown".split(" ")},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",
  captured:"onCompositionStartCapture"},dependencies:"topBlur topCompositionStart topKeyDown topKeyPress topKeyUp topMouseDown".split(" ")},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:"topBlur topCompositionUpdate topKeyDown topKeyPress topKeyUp topMouseDown".split(" ")}},cc=!1;
  function dc(a,b){switch(a){case "topKeyUp":return-1!==Pb.indexOf(b.keyCode);case "topKeyDown":return 229!==b.keyCode;case "topKeyPress":case "topMouseDown":case "topBlur":return!0;default:return!1}}function ec(a){a=a.detail;return"object"===typeof a&&"data"in a?a.data:null}var fc=!1;function gc(a,b){switch(a){case "topCompositionEnd":return ec(b);case "topKeyPress":if(32!==b.which)return null;cc=!0;return ac;case "topTextInput":return a=b.data,a===ac&&cc?null:a;default:return null}}
  function hc(a,b){if(fc)return"topCompositionEnd"===a||!Vb&&dc(a,b)?(a=Fb(),S._root=null,S._startText=null,S._fallbackText=null,fc=!1,a):null;switch(a){case "topPaste":return null;case "topKeyPress":if(!(b.ctrlKey||b.altKey||b.metaKey)||b.ctrlKey&&b.altKey){if(b.char&&1<b.char.length)return b.char;if(b.which)return String.fromCharCode(b.which)}return null;case "topCompositionEnd":return $b?null:b.data;default:return null}}
  var ic={eventTypes:bc,extractEvents:function(a,b,c,d){var e;if(Vb)b:{switch(a){case "topCompositionStart":var f=bc.compositionStart;break b;case "topCompositionEnd":f=bc.compositionEnd;break b;case "topCompositionUpdate":f=bc.compositionUpdate;break b}f=void 0}else fc?dc(a,c)&&(f=bc.compositionEnd):"topKeyDown"===a&&229===c.keyCode&&(f=bc.compositionStart);f?($b&&(fc||f!==bc.compositionStart?f===bc.compositionEnd&&fc&&(e=Fb()):(S._root=d,S._startText=Gb(),fc=!0)),f=Mb.getPooled(f,b,c,d),e?f.data=
  e:(e=ec(c),null!==e&&(f.data=e)),Ab(f),e=f):e=null;(a=Zb?gc(a,c):hc(a,c))?(b=Nb.getPooled(bc.beforeInput,b,c,d),b.data=a,Ab(b)):b=null;return[e,b]}},jc=null,kc=null,lc=null;function mc(a){if(a=Xa(a)){jc&&"function"===typeof jc.restoreControlledState?void 0:E("194");var b=Wa(a.stateNode);jc.restoreControlledState(a.stateNode,a.type,b)}}var nc={injectFiberControlledHostComponent:function(a){jc=a}};function oc(a){kc?lc?lc.push(a):lc=[a]:kc=a}
  function pc(){if(kc){var a=kc,b=lc;lc=kc=null;mc(a);if(b)for(a=0;a<b.length;a++)mc(b[a])}}var qc=Object.freeze({injection:nc,enqueueStateRestore:oc,restoreStateIfNeeded:pc});function rc(a,b){return a(b)}var sc=!1;function tc(a,b){if(sc)return rc(a,b);sc=!0;try{return rc(a,b)}finally{sc=!1,pc()}}var uc={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};
  function vc(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return"input"===b?!!uc[a.type]:"textarea"===b?!0:!1}function wc(a){a=a.target||a.srcElement||window;a.correspondingUseElement&&(a=a.correspondingUseElement);return 3===a.nodeType?a.parentNode:a}var xc;l.canUseDOM&&(xc=document.implementation&&document.implementation.hasFeature&&!0!==document.implementation.hasFeature("",""));
  function yc(a,b){if(!l.canUseDOM||b&&!("addEventListener"in document))return!1;b="on"+a;var c=b in document;c||(c=document.createElement("div"),c.setAttribute(b,"return;"),c="function"===typeof c[b]);!c&&xc&&"wheel"===a&&(c=document.implementation.hasFeature("Events.wheel","3.0"));return c}function zc(a){var b=a.type;return(a=a.nodeName)&&"input"===a.toLowerCase()&&("checkbox"===b||"radio"===b)}
  function Ac(a){var b=zc(a)?"checked":"value",c=Object.getOwnPropertyDescriptor(a.constructor.prototype,b),d=""+a[b];if(!a.hasOwnProperty(b)&&"function"===typeof c.get&&"function"===typeof c.set)return Object.defineProperty(a,b,{enumerable:c.enumerable,configurable:!0,get:function(){return c.get.call(this)},set:function(a){d=""+a;c.set.call(this,a)}}),{getValue:function(){return d},setValue:function(a){d=""+a},stopTracking:function(){a._valueTracker=null;delete a[b]}}}
  function Bc(a){a._valueTracker||(a._valueTracker=Ac(a))}function Cc(a){if(!a)return!1;var b=a._valueTracker;if(!b)return!0;var c=b.getValue();var d="";a&&(d=zc(a)?a.checked?"true":"false":a.value);a=d;return a!==c?(b.setValue(a),!0):!1}var Dc={change:{phasedRegistrationNames:{bubbled:"onChange",captured:"onChangeCapture"},dependencies:"topBlur topChange topClick topFocus topInput topKeyDown topKeyUp topSelectionChange".split(" ")}};
  function Ec(a,b,c){a=T.getPooled(Dc.change,a,b,c);a.type="change";oc(c);Ab(a);return a}var Fc=null,Gc=null;function Hc(a){kb(a);lb(!1)}function Ic(a){var b=qb(a);if(Cc(b))return a}function Jc(a,b){if("topChange"===a)return b}var Kc=!1;l.canUseDOM&&(Kc=yc("input")&&(!document.documentMode||9<document.documentMode));function Lc(){Fc&&(Fc.detachEvent("onpropertychange",Mc),Gc=Fc=null)}function Mc(a){"value"===a.propertyName&&Ic(Gc)&&(a=Ec(Gc,a,wc(a)),tc(Hc,a))}
  function Nc(a,b,c){"topFocus"===a?(Lc(),Fc=b,Gc=c,Fc.attachEvent("onpropertychange",Mc)):"topBlur"===a&&Lc()}function Oc(a){if("topSelectionChange"===a||"topKeyUp"===a||"topKeyDown"===a)return Ic(Gc)}function Pc(a,b){if("topClick"===a)return Ic(b)}function $c(a,b){if("topInput"===a||"topChange"===a)return Ic(b)}
  var ad={eventTypes:Dc,_isInputEventSupported:Kc,extractEvents:function(a,b,c,d){var e=b?qb(b):window,f=e.nodeName&&e.nodeName.toLowerCase();if("select"===f||"input"===f&&"file"===e.type)var g=Jc;else if(vc(e))if(Kc)g=$c;else{g=Oc;var h=Nc}else f=e.nodeName,!f||"input"!==f.toLowerCase()||"checkbox"!==e.type&&"radio"!==e.type||(g=Pc);if(g&&(g=g(a,b)))return Ec(g,c,d);h&&h(a,e,b);"topBlur"===a&&null!=b&&(a=b._wrapperState||e._wrapperState)&&a.controlled&&"number"===e.type&&(a=""+e.value,e.getAttribute("value")!==
  a&&e.setAttribute("value",a))}};function bd(a,b,c,d){return T.call(this,a,b,c,d)}T.augmentClass(bd,{view:null,detail:null});var cd={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};function dd(a){var b=this.nativeEvent;return b.getModifierState?b.getModifierState(a):(a=cd[a])?!!b[a]:!1}function ed(){return dd}function fd(a,b,c,d){return T.call(this,a,b,c,d)}
  bd.augmentClass(fd,{screenX:null,screenY:null,clientX:null,clientY:null,pageX:null,pageY:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,getModifierState:ed,button:null,buttons:null,relatedTarget:function(a){return a.relatedTarget||(a.fromElement===a.srcElement?a.toElement:a.fromElement)}});
  var gd={mouseEnter:{registrationName:"onMouseEnter",dependencies:["topMouseOut","topMouseOver"]},mouseLeave:{registrationName:"onMouseLeave",dependencies:["topMouseOut","topMouseOver"]}},hd={eventTypes:gd,extractEvents:function(a,b,c,d){if("topMouseOver"===a&&(c.relatedTarget||c.fromElement)||"topMouseOut"!==a&&"topMouseOver"!==a)return null;var e=d.window===d?d:(e=d.ownerDocument)?e.defaultView||e.parentWindow:window;"topMouseOut"===a?(a=b,b=(b=c.relatedTarget||c.toElement)?pb(b):null):a=null;if(a===
  b)return null;var f=null==a?e:qb(a);e=null==b?e:qb(b);var g=fd.getPooled(gd.mouseLeave,a,c,d);g.type="mouseleave";g.target=f;g.relatedTarget=e;c=fd.getPooled(gd.mouseEnter,b,c,d);c.type="mouseenter";c.target=e;c.relatedTarget=f;Bb(g,c,a,b);return[g,c]}},id=aa.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner;function jd(a){a=a.type;return"string"===typeof a?a:"function"===typeof a?a.displayName||a.name:null}
  function kd(a){var b=a;if(a.alternate)for(;b["return"];)b=b["return"];else{if(0!==(b.effectTag&2))return 1;for(;b["return"];)if(b=b["return"],0!==(b.effectTag&2))return 1}return 3===b.tag?2:3}function ld(a){return(a=a._reactInternalFiber)?2===kd(a):!1}function md(a){2!==kd(a)?E("188"):void 0}
  function nd(a){var b=a.alternate;if(!b)return b=kd(a),3===b?E("188"):void 0,1===b?null:a;for(var c=a,d=b;;){var e=c["return"],f=e?e.alternate:null;if(!e||!f)break;if(e.child===f.child){for(var g=e.child;g;){if(g===c)return md(e),a;if(g===d)return md(e),b;g=g.sibling}E("188")}if(c["return"]!==d["return"])c=e,d=f;else{g=!1;for(var h=e.child;h;){if(h===c){g=!0;c=e;d=f;break}if(h===d){g=!0;d=e;c=f;break}h=h.sibling}if(!g){for(h=f.child;h;){if(h===c){g=!0;c=f;d=e;break}if(h===d){g=!0;d=f;c=e;break}h=h.sibling}g?
  void 0:E("189")}}c.alternate!==d?E("190"):void 0}3!==c.tag?E("188"):void 0;return c.stateNode.current===c?a:b}function od(a){a=nd(a);if(!a)return null;for(var b=a;;){if(5===b.tag||6===b.tag)return b;if(b.child)b.child["return"]=b,b=b.child;else{if(b===a)break;for(;!b.sibling;){if(!b["return"]||b["return"]===a)return null;b=b["return"]}b.sibling["return"]=b["return"];b=b.sibling}}return null}
  function pd(a){a=nd(a);if(!a)return null;for(var b=a;;){if(5===b.tag||6===b.tag)return b;if(b.child&&4!==b.tag)b.child["return"]=b,b=b.child;else{if(b===a)break;for(;!b.sibling;){if(!b["return"]||b["return"]===a)return null;b=b["return"]}b.sibling["return"]=b["return"];b=b.sibling}}return null}var qd=[];
  function rd(a){var b=a.targetInst;do{if(!b){a.ancestors.push(b);break}var c;for(c=b;c["return"];)c=c["return"];c=3!==c.tag?null:c.stateNode.containerInfo;if(!c)break;a.ancestors.push(b);b=pb(c)}while(b);for(c=0;c<a.ancestors.length;c++)b=a.ancestors[c],sd(a.topLevelType,b,a.nativeEvent,wc(a.nativeEvent))}var td=!0,sd=void 0;function ud(a){td=!!a}function U(a,b,c){return c?ba.listen(c,b,vd.bind(null,a)):null}function wd(a,b,c){return c?ba.capture(c,b,vd.bind(null,a)):null}
  function vd(a,b){if(td){var c=wc(b);c=pb(c);null===c||"number"!==typeof c.tag||2===kd(c)||(c=null);if(qd.length){var d=qd.pop();d.topLevelType=a;d.nativeEvent=b;d.targetInst=c;a=d}else a={topLevelType:a,nativeEvent:b,targetInst:c,ancestors:[]};try{tc(rd,a)}finally{a.topLevelType=null,a.nativeEvent=null,a.targetInst=null,a.ancestors.length=0,10>qd.length&&qd.push(a)}}}
  var xd=Object.freeze({get _enabled(){return td},get _handleTopLevel(){return sd},setHandleTopLevel:function(a){sd=a},setEnabled:ud,isEnabled:function(){return td},trapBubbledEvent:U,trapCapturedEvent:wd,dispatchEvent:vd});function yd(a,b){var c={};c[a.toLowerCase()]=b.toLowerCase();c["Webkit"+a]="webkit"+b;c["Moz"+a]="moz"+b;c["ms"+a]="MS"+b;c["O"+a]="o"+b.toLowerCase();return c}
  var zd={animationend:yd("Animation","AnimationEnd"),animationiteration:yd("Animation","AnimationIteration"),animationstart:yd("Animation","AnimationStart"),transitionend:yd("Transition","TransitionEnd")},Ad={},Bd={};l.canUseDOM&&(Bd=document.createElement("div").style,"AnimationEvent"in window||(delete zd.animationend.animation,delete zd.animationiteration.animation,delete zd.animationstart.animation),"TransitionEvent"in window||delete zd.transitionend.transition);
  function Cd(a){if(Ad[a])return Ad[a];if(!zd[a])return a;var b=zd[a],c;for(c in b)if(b.hasOwnProperty(c)&&c in Bd)return Ad[a]=b[c];return""}
  var Dd={topAbort:"abort",topAnimationEnd:Cd("animationend")||"animationend",topAnimationIteration:Cd("animationiteration")||"animationiteration",topAnimationStart:Cd("animationstart")||"animationstart",topBlur:"blur",topCancel:"cancel",topCanPlay:"canplay",topCanPlayThrough:"canplaythrough",topChange:"change",topClick:"click",topClose:"close",topCompositionEnd:"compositionend",topCompositionStart:"compositionstart",topCompositionUpdate:"compositionupdate",topContextMenu:"contextmenu",topCopy:"copy",
  topCut:"cut",topDoubleClick:"dblclick",topDrag:"drag",topDragEnd:"dragend",topDragEnter:"dragenter",topDragExit:"dragexit",topDragLeave:"dragleave",topDragOver:"dragover",topDragStart:"dragstart",topDrop:"drop",topDurationChange:"durationchange",topEmptied:"emptied",topEncrypted:"encrypted",topEnded:"ended",topError:"error",topFocus:"focus",topInput:"input",topKeyDown:"keydown",topKeyPress:"keypress",topKeyUp:"keyup",topLoadedData:"loadeddata",topLoad:"load",topLoadedMetadata:"loadedmetadata",topLoadStart:"loadstart",
  topMouseDown:"mousedown",topMouseMove:"mousemove",topMouseOut:"mouseout",topMouseOver:"mouseover",topMouseUp:"mouseup",topPaste:"paste",topPause:"pause",topPlay:"play",topPlaying:"playing",topProgress:"progress",topRateChange:"ratechange",topScroll:"scroll",topSeeked:"seeked",topSeeking:"seeking",topSelectionChange:"selectionchange",topStalled:"stalled",topSuspend:"suspend",topTextInput:"textInput",topTimeUpdate:"timeupdate",topToggle:"toggle",topTouchCancel:"touchcancel",topTouchEnd:"touchend",topTouchMove:"touchmove",
  topTouchStart:"touchstart",topTransitionEnd:Cd("transitionend")||"transitionend",topVolumeChange:"volumechange",topWaiting:"waiting",topWheel:"wheel"},Ed={},Fd=0,Gd="_reactListenersID"+(""+Math.random()).slice(2);function Hd(a){Object.prototype.hasOwnProperty.call(a,Gd)||(a[Gd]=Fd++,Ed[a[Gd]]={});return Ed[a[Gd]]}function Id(a){for(;a&&a.firstChild;)a=a.firstChild;return a}
  function Jd(a,b){var c=Id(a);a=0;for(var d;c;){if(3===c.nodeType){d=a+c.textContent.length;if(a<=b&&d>=b)return{node:c,offset:b-a};a=d}a:{for(;c;){if(c.nextSibling){c=c.nextSibling;break a}c=c.parentNode}c=void 0}c=Id(c)}}function Kd(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return b&&("input"===b&&"text"===a.type||"textarea"===b||"true"===a.contentEditable)}
  var Ld=l.canUseDOM&&"documentMode"in document&&11>=document.documentMode,Md={select:{phasedRegistrationNames:{bubbled:"onSelect",captured:"onSelectCapture"},dependencies:"topBlur topContextMenu topFocus topKeyDown topKeyUp topMouseDown topMouseUp topSelectionChange".split(" ")}},Nd=null,Od=null,Pd=null,Qd=!1;
  function Rd(a,b){if(Qd||null==Nd||Nd!==da())return null;var c=Nd;"selectionStart"in c&&Kd(c)?c={start:c.selectionStart,end:c.selectionEnd}:window.getSelection?(c=window.getSelection(),c={anchorNode:c.anchorNode,anchorOffset:c.anchorOffset,focusNode:c.focusNode,focusOffset:c.focusOffset}):c=void 0;return Pd&&ea(Pd,c)?null:(Pd=c,a=T.getPooled(Md.select,Od,a,b),a.type="select",a.target=Nd,Ab(a),a)}
  var Sd={eventTypes:Md,extractEvents:function(a,b,c,d){var e=d.window===d?d.document:9===d.nodeType?d:d.ownerDocument,f;if(!(f=!e)){a:{e=Hd(e);f=Sa.onSelect;for(var g=0;g<f.length;g++){var h=f[g];if(!e.hasOwnProperty(h)||!e[h]){e=!1;break a}}e=!0}f=!e}if(f)return null;e=b?qb(b):window;switch(a){case "topFocus":if(vc(e)||"true"===e.contentEditable)Nd=e,Od=b,Pd=null;break;case "topBlur":Pd=Od=Nd=null;break;case "topMouseDown":Qd=!0;break;case "topContextMenu":case "topMouseUp":return Qd=!1,Rd(c,d);case "topSelectionChange":if(Ld)break;
  case "topKeyDown":case "topKeyUp":return Rd(c,d)}return null}};function Td(a,b,c,d){return T.call(this,a,b,c,d)}T.augmentClass(Td,{animationName:null,elapsedTime:null,pseudoElement:null});function Ud(a,b,c,d){return T.call(this,a,b,c,d)}T.augmentClass(Ud,{clipboardData:function(a){return"clipboardData"in a?a.clipboardData:window.clipboardData}});function Vd(a,b,c,d){return T.call(this,a,b,c,d)}bd.augmentClass(Vd,{relatedTarget:null});
  function Wd(a){var b=a.keyCode;"charCode"in a?(a=a.charCode,0===a&&13===b&&(a=13)):a=b;return 32<=a||13===a?a:0}
  var Xd={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},Yd={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",
  116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"};function Zd(a,b,c,d){return T.call(this,a,b,c,d)}
  bd.augmentClass(Zd,{key:function(a){if(a.key){var b=Xd[a.key]||a.key;if("Unidentified"!==b)return b}return"keypress"===a.type?(a=Wd(a),13===a?"Enter":String.fromCharCode(a)):"keydown"===a.type||"keyup"===a.type?Yd[a.keyCode]||"Unidentified":""},location:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,repeat:null,locale:null,getModifierState:ed,charCode:function(a){return"keypress"===a.type?Wd(a):0},keyCode:function(a){return"keydown"===a.type||"keyup"===a.type?a.keyCode:0},which:function(a){return"keypress"===
  a.type?Wd(a):"keydown"===a.type||"keyup"===a.type?a.keyCode:0}});function $d(a,b,c,d){return T.call(this,a,b,c,d)}fd.augmentClass($d,{dataTransfer:null});function ae(a,b,c,d){return T.call(this,a,b,c,d)}bd.augmentClass(ae,{touches:null,targetTouches:null,changedTouches:null,altKey:null,metaKey:null,ctrlKey:null,shiftKey:null,getModifierState:ed});function be(a,b,c,d){return T.call(this,a,b,c,d)}T.augmentClass(be,{propertyName:null,elapsedTime:null,pseudoElement:null});
  function ce(a,b,c,d){return T.call(this,a,b,c,d)}fd.augmentClass(ce,{deltaX:function(a){return"deltaX"in a?a.deltaX:"wheelDeltaX"in a?-a.wheelDeltaX:0},deltaY:function(a){return"deltaY"in a?a.deltaY:"wheelDeltaY"in a?-a.wheelDeltaY:"wheelDelta"in a?-a.wheelDelta:0},deltaZ:null,deltaMode:null});var de={},ee={};
  "abort animationEnd animationIteration animationStart blur cancel canPlay canPlayThrough click close contextMenu copy cut doubleClick drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error focus input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing progress rateChange reset scroll seeked seeking stalled submit suspend timeUpdate toggle touchCancel touchEnd touchMove touchStart transitionEnd volumeChange waiting wheel".split(" ").forEach(function(a){var b=a[0].toUpperCase()+
  a.slice(1),c="on"+b;b="top"+b;c={phasedRegistrationNames:{bubbled:c,captured:c+"Capture"},dependencies:[b]};de[a]=c;ee[b]=c});
  var fe={eventTypes:de,extractEvents:function(a,b,c,d){var e=ee[a];if(!e)return null;switch(a){case "topKeyPress":if(0===Wd(c))return null;case "topKeyDown":case "topKeyUp":a=Zd;break;case "topBlur":case "topFocus":a=Vd;break;case "topClick":if(2===c.button)return null;case "topDoubleClick":case "topMouseDown":case "topMouseMove":case "topMouseUp":case "topMouseOut":case "topMouseOver":case "topContextMenu":a=fd;break;case "topDrag":case "topDragEnd":case "topDragEnter":case "topDragExit":case "topDragLeave":case "topDragOver":case "topDragStart":case "topDrop":a=
  $d;break;case "topTouchCancel":case "topTouchEnd":case "topTouchMove":case "topTouchStart":a=ae;break;case "topAnimationEnd":case "topAnimationIteration":case "topAnimationStart":a=Td;break;case "topTransitionEnd":a=be;break;case "topScroll":a=bd;break;case "topWheel":a=ce;break;case "topCopy":case "topCut":case "topPaste":a=Ud;break;default:a=T}b=a.getPooled(e,b,c,d);Ab(b);return b}};sd=function(a,b,c,d){a=jb(a,b,c,d);kb(a);lb(!1)};hb.injectEventPluginOrder("ResponderEventPlugin SimpleEventPlugin TapEventPlugin EnterLeaveEventPlugin ChangeEventPlugin SelectEventPlugin BeforeInputEventPlugin".split(" "));
  Wa=sb.getFiberCurrentPropsFromNode;Xa=sb.getInstanceFromNode;Ya=sb.getNodeFromInstance;hb.injectEventPluginsByName({SimpleEventPlugin:fe,EnterLeaveEventPlugin:hd,ChangeEventPlugin:ad,SelectEventPlugin:Sd,BeforeInputEventPlugin:ic});var ge=[],he=-1;function V(a){0>he||(a.current=ge[he],ge[he]=null,he--)}function W(a,b){he++;ge[he]=a.current;a.current=b}new Set;var ie={current:D},X={current:!1},je=D;function ke(a){return le(a)?je:ie.current}
  function me(a,b){var c=a.type.contextTypes;if(!c)return D;var d=a.stateNode;if(d&&d.__reactInternalMemoizedUnmaskedChildContext===b)return d.__reactInternalMemoizedMaskedChildContext;var e={},f;for(f in c)e[f]=b[f];d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=b,a.__reactInternalMemoizedMaskedChildContext=e);return e}function le(a){return 2===a.tag&&null!=a.type.childContextTypes}function ne(a){le(a)&&(V(X,a),V(ie,a))}
  function oe(a,b,c){null!=ie.cursor?E("168"):void 0;W(ie,b,a);W(X,c,a)}function pe(a,b){var c=a.stateNode,d=a.type.childContextTypes;if("function"!==typeof c.getChildContext)return b;c=c.getChildContext();for(var e in c)e in d?void 0:E("108",jd(a)||"Unknown",e);return B({},b,c)}function qe(a){if(!le(a))return!1;var b=a.stateNode;b=b&&b.__reactInternalMemoizedMergedChildContext||D;je=ie.current;W(ie,b,a);W(X,X.current,a);return!0}
  function re(a,b){var c=a.stateNode;c?void 0:E("169");if(b){var d=pe(a,je);c.__reactInternalMemoizedMergedChildContext=d;V(X,a);V(ie,a);W(ie,d,a)}else V(X,a);W(X,b,a)}
  function Y(a,b,c){this.tag=a;this.key=b;this.stateNode=this.type=null;this.sibling=this.child=this["return"]=null;this.index=0;this.memoizedState=this.updateQueue=this.memoizedProps=this.pendingProps=this.ref=null;this.internalContextTag=c;this.effectTag=0;this.lastEffect=this.firstEffect=this.nextEffect=null;this.expirationTime=0;this.alternate=null}
  function se(a,b,c){var d=a.alternate;null===d?(d=new Y(a.tag,a.key,a.internalContextTag),d.type=a.type,d.stateNode=a.stateNode,d.alternate=a,a.alternate=d):(d.effectTag=0,d.nextEffect=null,d.firstEffect=null,d.lastEffect=null);d.expirationTime=c;d.pendingProps=b;d.child=a.child;d.memoizedProps=a.memoizedProps;d.memoizedState=a.memoizedState;d.updateQueue=a.updateQueue;d.sibling=a.sibling;d.index=a.index;d.ref=a.ref;return d}
  function te(a,b,c){var d=void 0,e=a.type,f=a.key;"function"===typeof e?(d=e.prototype&&e.prototype.isReactComponent?new Y(2,f,b):new Y(0,f,b),d.type=e,d.pendingProps=a.props):"string"===typeof e?(d=new Y(5,f,b),d.type=e,d.pendingProps=a.props):"object"===typeof e&&null!==e&&"number"===typeof e.tag?(d=e,d.pendingProps=a.props):E("130",null==e?e:typeof e,"");d.expirationTime=c;return d}function ue(a,b,c,d){b=new Y(10,d,b);b.pendingProps=a;b.expirationTime=c;return b}
  function ve(a,b,c){b=new Y(6,null,b);b.pendingProps=a;b.expirationTime=c;return b}function we(a,b,c){b=new Y(7,a.key,b);b.type=a.handler;b.pendingProps=a;b.expirationTime=c;return b}function xe(a,b,c){a=new Y(9,null,b);a.expirationTime=c;return a}function ye(a,b,c){b=new Y(4,a.key,b);b.pendingProps=a.children||[];b.expirationTime=c;b.stateNode={containerInfo:a.containerInfo,pendingChildren:null,implementation:a.implementation};return b}var ze=null,Ae=null;
  function Be(a){return function(b){try{return a(b)}catch(c){}}}function Ce(a){if("undefined"===typeof __REACT_DEVTOOLS_GLOBAL_HOOK__)return!1;var b=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(b.isDisabled||!b.supportsFiber)return!0;try{var c=b.inject(a);ze=Be(function(a){return b.onCommitFiberRoot(c,a)});Ae=Be(function(a){return b.onCommitFiberUnmount(c,a)})}catch(d){}return!0}function De(a){"function"===typeof ze&&ze(a)}function Ee(a){"function"===typeof Ae&&Ae(a)}
  function Fe(a){return{baseState:a,expirationTime:0,first:null,last:null,callbackList:null,hasForceUpdate:!1,isInitialized:!1}}function Ge(a,b){null===a.last?a.first=a.last=b:(a.last.next=b,a.last=b);if(0===a.expirationTime||a.expirationTime>b.expirationTime)a.expirationTime=b.expirationTime}
  function He(a,b){var c=a.alternate,d=a.updateQueue;null===d&&(d=a.updateQueue=Fe(null));null!==c?(a=c.updateQueue,null===a&&(a=c.updateQueue=Fe(null))):a=null;a=a!==d?a:null;null===a?Ge(d,b):null===d.last||null===a.last?(Ge(d,b),Ge(a,b)):(Ge(d,b),a.last=b)}function Ie(a,b,c,d){a=a.partialState;return"function"===typeof a?a.call(b,c,d):a}
  function Je(a,b,c,d,e,f){null!==a&&a.updateQueue===c&&(c=b.updateQueue={baseState:c.baseState,expirationTime:c.expirationTime,first:c.first,last:c.last,isInitialized:c.isInitialized,callbackList:null,hasForceUpdate:!1});c.expirationTime=0;c.isInitialized?a=c.baseState:(a=c.baseState=b.memoizedState,c.isInitialized=!0);for(var g=!0,h=c.first,k=!1;null!==h;){var q=h.expirationTime;if(q>f){var v=c.expirationTime;if(0===v||v>q)c.expirationTime=q;k||(k=!0,c.baseState=a)}else{k||(c.first=h.next,null===
  c.first&&(c.last=null));if(h.isReplace)a=Ie(h,d,a,e),g=!0;else if(q=Ie(h,d,a,e))a=g?B({},a,q):B(a,q),g=!1;h.isForced&&(c.hasForceUpdate=!0);null!==h.callback&&(q=c.callbackList,null===q&&(q=c.callbackList=[]),q.push(h))}h=h.next}null!==c.callbackList?b.effectTag|=32:null!==c.first||c.hasForceUpdate||(b.updateQueue=null);k||(c.baseState=a);return a}
  function Ke(a,b){var c=a.callbackList;if(null!==c)for(a.callbackList=null,a=0;a<c.length;a++){var d=c[a],e=d.callback;d.callback=null;"function"!==typeof e?E("191",e):void 0;e.call(b)}}
  function Le(a,b,c,d){function e(a,b){b.updater=f;a.stateNode=b;b._reactInternalFiber=a}var f={isMounted:ld,enqueueSetState:function(c,d,e){c=c._reactInternalFiber;e=void 0===e?null:e;var g=b(c);He(c,{expirationTime:g,partialState:d,callback:e,isReplace:!1,isForced:!1,nextCallback:null,next:null});a(c,g)},enqueueReplaceState:function(c,d,e){c=c._reactInternalFiber;e=void 0===e?null:e;var g=b(c);He(c,{expirationTime:g,partialState:d,callback:e,isReplace:!0,isForced:!1,nextCallback:null,next:null});
  a(c,g)},enqueueForceUpdate:function(c,d){c=c._reactInternalFiber;d=void 0===d?null:d;var e=b(c);He(c,{expirationTime:e,partialState:null,callback:d,isReplace:!1,isForced:!0,nextCallback:null,next:null});a(c,e)}};return{adoptClassInstance:e,constructClassInstance:function(a,b){var c=a.type,d=ke(a),f=2===a.tag&&null!=a.type.contextTypes,g=f?me(a,d):D;b=new c(b,g);e(a,b);f&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=d,a.__reactInternalMemoizedMaskedChildContext=g);return b},mountClassInstance:function(a,
  b){var c=a.alternate,d=a.stateNode,e=d.state||null,g=a.pendingProps;g?void 0:E("158");var h=ke(a);d.props=g;d.state=a.memoizedState=e;d.refs=D;d.context=me(a,h);null!=a.type&&null!=a.type.prototype&&!0===a.type.prototype.unstable_isAsyncReactComponent&&(a.internalContextTag|=1);"function"===typeof d.componentWillMount&&(e=d.state,d.componentWillMount(),e!==d.state&&f.enqueueReplaceState(d,d.state,null),e=a.updateQueue,null!==e&&(d.state=Je(c,a,e,d,g,b)));"function"===typeof d.componentDidMount&&(a.effectTag|=
  4)},updateClassInstance:function(a,b,e){var g=b.stateNode;g.props=b.memoizedProps;g.state=b.memoizedState;var h=b.memoizedProps,k=b.pendingProps;k||(k=h,null==k?E("159"):void 0);var u=g.context,z=ke(b);z=me(b,z);"function"!==typeof g.componentWillReceiveProps||h===k&&u===z||(u=g.state,g.componentWillReceiveProps(k,z),g.state!==u&&f.enqueueReplaceState(g,g.state,null));u=b.memoizedState;e=null!==b.updateQueue?Je(a,b,b.updateQueue,g,k,e):u;if(!(h!==k||u!==e||X.current||null!==b.updateQueue&&b.updateQueue.hasForceUpdate))return"function"!==
  typeof g.componentDidUpdate||h===a.memoizedProps&&u===a.memoizedState||(b.effectTag|=4),!1;var G=k;if(null===h||null!==b.updateQueue&&b.updateQueue.hasForceUpdate)G=!0;else{var I=b.stateNode,L=b.type;G="function"===typeof I.shouldComponentUpdate?I.shouldComponentUpdate(G,e,z):L.prototype&&L.prototype.isPureReactComponent?!ea(h,G)||!ea(u,e):!0}G?("function"===typeof g.componentWillUpdate&&g.componentWillUpdate(k,e,z),"function"===typeof g.componentDidUpdate&&(b.effectTag|=4)):("function"!==typeof g.componentDidUpdate||
  h===a.memoizedProps&&u===a.memoizedState||(b.effectTag|=4),c(b,k),d(b,e));g.props=k;g.state=e;g.context=z;return G}}}var Qe="function"===typeof Symbol&&Symbol["for"],Re=Qe?Symbol["for"]("react.element"):60103,Se=Qe?Symbol["for"]("react.call"):60104,Te=Qe?Symbol["for"]("react.return"):60105,Ue=Qe?Symbol["for"]("react.portal"):60106,Ve=Qe?Symbol["for"]("react.fragment"):60107,We="function"===typeof Symbol&&Symbol.iterator;
  function Xe(a){if(null===a||"undefined"===typeof a)return null;a=We&&a[We]||a["@@iterator"];return"function"===typeof a?a:null}var Ye=Array.isArray;
  function Ze(a,b){var c=b.ref;if(null!==c&&"function"!==typeof c){if(b._owner){b=b._owner;var d=void 0;b&&(2!==b.tag?E("110"):void 0,d=b.stateNode);d?void 0:E("147",c);var e=""+c;if(null!==a&&null!==a.ref&&a.ref._stringRef===e)return a.ref;a=function(a){var b=d.refs===D?d.refs={}:d.refs;null===a?delete b[e]:b[e]=a};a._stringRef=e;return a}"string"!==typeof c?E("148"):void 0;b._owner?void 0:E("149",c)}return c}
  function $e(a,b){"textarea"!==a.type&&E("31","[object Object]"===Object.prototype.toString.call(b)?"object with keys {"+Object.keys(b).join(", ")+"}":b,"")}
  function af(a){function b(b,c){if(a){var d=b.lastEffect;null!==d?(d.nextEffect=c,b.lastEffect=c):b.firstEffect=b.lastEffect=c;c.nextEffect=null;c.effectTag=8}}function c(c,d){if(!a)return null;for(;null!==d;)b(c,d),d=d.sibling;return null}function d(a,b){for(a=new Map;null!==b;)null!==b.key?a.set(b.key,b):a.set(b.index,b),b=b.sibling;return a}function e(a,b,c){a=se(a,b,c);a.index=0;a.sibling=null;return a}function f(b,c,d){b.index=d;if(!a)return c;d=b.alternate;if(null!==d)return d=d.index,d<c?(b.effectTag=
  2,c):d;b.effectTag=2;return c}function g(b){a&&null===b.alternate&&(b.effectTag=2);return b}function h(a,b,c,d){if(null===b||6!==b.tag)return b=ve(c,a.internalContextTag,d),b["return"]=a,b;b=e(b,c,d);b["return"]=a;return b}function k(a,b,c,d){if(null!==b&&b.type===c.type)return d=e(b,c.props,d),d.ref=Ze(b,c),d["return"]=a,d;d=te(c,a.internalContextTag,d);d.ref=Ze(b,c);d["return"]=a;return d}function q(a,b,c,d){if(null===b||7!==b.tag)return b=we(c,a.internalContextTag,d),b["return"]=a,b;b=e(b,c,d);
  b["return"]=a;return b}function v(a,b,c,d){if(null===b||9!==b.tag)return b=xe(c,a.internalContextTag,d),b.type=c.value,b["return"]=a,b;b=e(b,null,d);b.type=c.value;b["return"]=a;return b}function y(a,b,c,d){if(null===b||4!==b.tag||b.stateNode.containerInfo!==c.containerInfo||b.stateNode.implementation!==c.implementation)return b=ye(c,a.internalContextTag,d),b["return"]=a,b;b=e(b,c.children||[],d);b["return"]=a;return b}function u(a,b,c,d,f){if(null===b||10!==b.tag)return b=ue(c,a.internalContextTag,
  d,f),b["return"]=a,b;b=e(b,c,d);b["return"]=a;return b}function z(a,b,c){if("string"===typeof b||"number"===typeof b)return b=ve(""+b,a.internalContextTag,c),b["return"]=a,b;if("object"===typeof b&&null!==b){switch(b.$$typeof){case Re:if(b.type===Ve)return b=ue(b.props.children,a.internalContextTag,c,b.key),b["return"]=a,b;c=te(b,a.internalContextTag,c);c.ref=Ze(null,b);c["return"]=a;return c;case Se:return b=we(b,a.internalContextTag,c),b["return"]=a,b;case Te:return c=xe(b,a.internalContextTag,
  c),c.type=b.value,c["return"]=a,c;case Ue:return b=ye(b,a.internalContextTag,c),b["return"]=a,b}if(Ye(b)||Xe(b))return b=ue(b,a.internalContextTag,c,null),b["return"]=a,b;$e(a,b)}return null}function G(a,b,c,d){var e=null!==b?b.key:null;if("string"===typeof c||"number"===typeof c)return null!==e?null:h(a,b,""+c,d);if("object"===typeof c&&null!==c){switch(c.$$typeof){case Re:return c.key===e?c.type===Ve?u(a,b,c.props.children,d,e):k(a,b,c,d):null;case Se:return c.key===e?q(a,b,c,d):null;case Te:return null===
  e?v(a,b,c,d):null;case Ue:return c.key===e?y(a,b,c,d):null}if(Ye(c)||Xe(c))return null!==e?null:u(a,b,c,d,null);$e(a,c)}return null}function I(a,b,c,d,e){if("string"===typeof d||"number"===typeof d)return a=a.get(c)||null,h(b,a,""+d,e);if("object"===typeof d&&null!==d){switch(d.$$typeof){case Re:return a=a.get(null===d.key?c:d.key)||null,d.type===Ve?u(b,a,d.props.children,e,d.key):k(b,a,d,e);case Se:return a=a.get(null===d.key?c:d.key)||null,q(b,a,d,e);case Te:return a=a.get(c)||null,v(b,a,d,e);case Ue:return a=
  a.get(null===d.key?c:d.key)||null,y(b,a,d,e)}if(Ye(d)||Xe(d))return a=a.get(c)||null,u(b,a,d,e,null);$e(b,d)}return null}function L(e,g,m,A){for(var h=null,r=null,n=g,w=g=0,k=null;null!==n&&w<m.length;w++){n.index>w?(k=n,n=null):k=n.sibling;var x=G(e,n,m[w],A);if(null===x){null===n&&(n=k);break}a&&n&&null===x.alternate&&b(e,n);g=f(x,g,w);null===r?h=x:r.sibling=x;r=x;n=k}if(w===m.length)return c(e,n),h;if(null===n){for(;w<m.length;w++)if(n=z(e,m[w],A))g=f(n,g,w),null===r?h=n:r.sibling=n,r=n;return h}for(n=
  d(e,n);w<m.length;w++)if(k=I(n,e,w,m[w],A)){if(a&&null!==k.alternate)n["delete"](null===k.key?w:k.key);g=f(k,g,w);null===r?h=k:r.sibling=k;r=k}a&&n.forEach(function(a){return b(e,a)});return h}function N(e,g,m,A){var h=Xe(m);"function"!==typeof h?E("150"):void 0;m=h.call(m);null==m?E("151"):void 0;for(var r=h=null,n=g,w=g=0,k=null,x=m.next();null!==n&&!x.done;w++,x=m.next()){n.index>w?(k=n,n=null):k=n.sibling;var J=G(e,n,x.value,A);if(null===J){n||(n=k);break}a&&n&&null===J.alternate&&b(e,n);g=f(J,
  g,w);null===r?h=J:r.sibling=J;r=J;n=k}if(x.done)return c(e,n),h;if(null===n){for(;!x.done;w++,x=m.next())x=z(e,x.value,A),null!==x&&(g=f(x,g,w),null===r?h=x:r.sibling=x,r=x);return h}for(n=d(e,n);!x.done;w++,x=m.next())if(x=I(n,e,w,x.value,A),null!==x){if(a&&null!==x.alternate)n["delete"](null===x.key?w:x.key);g=f(x,g,w);null===r?h=x:r.sibling=x;r=x}a&&n.forEach(function(a){return b(e,a)});return h}return function(a,d,f,h){"object"===typeof f&&null!==f&&f.type===Ve&&null===f.key&&(f=f.props.children);
  var m="object"===typeof f&&null!==f;if(m)switch(f.$$typeof){case Re:a:{var r=f.key;for(m=d;null!==m;){if(m.key===r)if(10===m.tag?f.type===Ve:m.type===f.type){c(a,m.sibling);d=e(m,f.type===Ve?f.props.children:f.props,h);d.ref=Ze(m,f);d["return"]=a;a=d;break a}else{c(a,m);break}else b(a,m);m=m.sibling}f.type===Ve?(d=ue(f.props.children,a.internalContextTag,h,f.key),d["return"]=a,a=d):(h=te(f,a.internalContextTag,h),h.ref=Ze(d,f),h["return"]=a,a=h)}return g(a);case Se:a:{for(m=f.key;null!==d;){if(d.key===
  m)if(7===d.tag){c(a,d.sibling);d=e(d,f,h);d["return"]=a;a=d;break a}else{c(a,d);break}else b(a,d);d=d.sibling}d=we(f,a.internalContextTag,h);d["return"]=a;a=d}return g(a);case Te:a:{if(null!==d)if(9===d.tag){c(a,d.sibling);d=e(d,null,h);d.type=f.value;d["return"]=a;a=d;break a}else c(a,d);d=xe(f,a.internalContextTag,h);d.type=f.value;d["return"]=a;a=d}return g(a);case Ue:a:{for(m=f.key;null!==d;){if(d.key===m)if(4===d.tag&&d.stateNode.containerInfo===f.containerInfo&&d.stateNode.implementation===
  f.implementation){c(a,d.sibling);d=e(d,f.children||[],h);d["return"]=a;a=d;break a}else{c(a,d);break}else b(a,d);d=d.sibling}d=ye(f,a.internalContextTag,h);d["return"]=a;a=d}return g(a)}if("string"===typeof f||"number"===typeof f)return f=""+f,null!==d&&6===d.tag?(c(a,d.sibling),d=e(d,f,h)):(c(a,d),d=ve(f,a.internalContextTag,h)),d["return"]=a,a=d,g(a);if(Ye(f))return L(a,d,f,h);if(Xe(f))return N(a,d,f,h);m&&$e(a,f);if("undefined"===typeof f)switch(a.tag){case 2:case 1:h=a.type,E("152",h.displayName||
  h.name||"Component")}return c(a,d)}}var bf=af(!0),cf=af(!1);
  function df(a,b,c,d,e){function f(a,b,c){var d=b.expirationTime;b.child=null===a?cf(b,null,c,d):bf(b,a.child,c,d)}function g(a,b){var c=b.ref;null===c||a&&a.ref===c||(b.effectTag|=128)}function h(a,b,c,d){g(a,b);if(!c)return d&&re(b,!1),q(a,b);c=b.stateNode;id.current=b;var e=c.render();b.effectTag|=1;f(a,b,e);b.memoizedState=c.state;b.memoizedProps=c.props;d&&re(b,!0);return b.child}function k(a){var b=a.stateNode;b.pendingContext?oe(a,b.pendingContext,b.pendingContext!==b.context):b.context&&oe(a,
  b.context,!1);I(a,b.containerInfo)}function q(a,b){null!==a&&b.child!==a.child?E("153"):void 0;if(null!==b.child){a=b.child;var c=se(a,a.pendingProps,a.expirationTime);b.child=c;for(c["return"]=b;null!==a.sibling;)a=a.sibling,c=c.sibling=se(a,a.pendingProps,a.expirationTime),c["return"]=b;c.sibling=null}return b.child}function v(a,b){switch(b.tag){case 3:k(b);break;case 2:qe(b);break;case 4:I(b,b.stateNode.containerInfo)}return null}var y=a.shouldSetTextContent,u=a.useSyncScheduling,z=a.shouldDeprioritizeSubtree,
  G=b.pushHostContext,I=b.pushHostContainer,L=c.enterHydrationState,N=c.resetHydrationState,J=c.tryToClaimNextHydratableInstance;a=Le(d,e,function(a,b){a.memoizedProps=b},function(a,b){a.memoizedState=b});var w=a.adoptClassInstance,m=a.constructClassInstance,A=a.mountClassInstance,Ob=a.updateClassInstance;return{beginWork:function(a,b,c){if(0===b.expirationTime||b.expirationTime>c)return v(a,b);switch(b.tag){case 0:null!==a?E("155"):void 0;var d=b.type,e=b.pendingProps,r=ke(b);r=me(b,r);d=d(e,r);b.effectTag|=
  1;"object"===typeof d&&null!==d&&"function"===typeof d.render?(b.tag=2,e=qe(b),w(b,d),A(b,c),b=h(a,b,!0,e)):(b.tag=1,f(a,b,d),b.memoizedProps=e,b=b.child);return b;case 1:a:{e=b.type;c=b.pendingProps;d=b.memoizedProps;if(X.current)null===c&&(c=d);else if(null===c||d===c){b=q(a,b);break a}d=ke(b);d=me(b,d);e=e(c,d);b.effectTag|=1;f(a,b,e);b.memoizedProps=c;b=b.child}return b;case 2:return e=qe(b),d=void 0,null===a?b.stateNode?E("153"):(m(b,b.pendingProps),A(b,c),d=!0):d=Ob(a,b,c),h(a,b,d,e);case 3:return k(b),
  e=b.updateQueue,null!==e?(d=b.memoizedState,e=Je(a,b,e,null,null,c),d===e?(N(),b=q(a,b)):(d=e.element,r=b.stateNode,(null===a||null===a.child)&&r.hydrate&&L(b)?(b.effectTag|=2,b.child=cf(b,null,d,c)):(N(),f(a,b,d)),b.memoizedState=e,b=b.child)):(N(),b=q(a,b)),b;case 5:G(b);null===a&&J(b);e=b.type;var n=b.memoizedProps;d=b.pendingProps;null===d&&(d=n,null===d?E("154"):void 0);r=null!==a?a.memoizedProps:null;X.current||null!==d&&n!==d?(n=d.children,y(e,d)?n=null:r&&y(e,r)&&(b.effectTag|=16),g(a,b),
  2147483647!==c&&!u&&z(e,d)?(b.expirationTime=2147483647,b=null):(f(a,b,n),b.memoizedProps=d,b=b.child)):b=q(a,b);return b;case 6:return null===a&&J(b),a=b.pendingProps,null===a&&(a=b.memoizedProps),b.memoizedProps=a,null;case 8:b.tag=7;case 7:e=b.pendingProps;if(X.current)null===e&&(e=a&&a.memoizedProps,null===e?E("154"):void 0);else if(null===e||b.memoizedProps===e)e=b.memoizedProps;d=e.children;b.stateNode=null===a?cf(b,b.stateNode,d,c):bf(b,b.stateNode,d,c);b.memoizedProps=e;return b.stateNode;
  case 9:return null;case 4:a:{I(b,b.stateNode.containerInfo);e=b.pendingProps;if(X.current)null===e&&(e=a&&a.memoizedProps,null==e?E("154"):void 0);else if(null===e||b.memoizedProps===e){b=q(a,b);break a}null===a?b.child=bf(b,null,e,c):f(a,b,e);b.memoizedProps=e;b=b.child}return b;case 10:a:{c=b.pendingProps;if(X.current)null===c&&(c=b.memoizedProps);else if(null===c||b.memoizedProps===c){b=q(a,b);break a}f(a,b,c);b.memoizedProps=c;b=b.child}return b;default:E("156")}},beginFailedWork:function(a,b,
  c){switch(b.tag){case 2:qe(b);break;case 3:k(b);break;default:E("157")}b.effectTag|=64;null===a?b.child=null:b.child!==a.child&&(b.child=a.child);if(0===b.expirationTime||b.expirationTime>c)return v(a,b);b.firstEffect=null;b.lastEffect=null;b.child=null===a?cf(b,null,null,c):bf(b,a.child,null,c);2===b.tag&&(a=b.stateNode,b.memoizedProps=a.props,b.memoizedState=a.state);return b.child}}}
  function ef(a,b,c){function d(a){a.effectTag|=4}var e=a.createInstance,f=a.createTextInstance,g=a.appendInitialChild,h=a.finalizeInitialChildren,k=a.prepareUpdate,q=a.persistence,v=b.getRootHostContainer,y=b.popHostContext,u=b.getHostContext,z=b.popHostContainer,G=c.prepareToHydrateHostInstance,I=c.prepareToHydrateHostTextInstance,L=c.popHydrationState,N=void 0,J=void 0,w=void 0;a.mutation?(N=function(){},J=function(a,b,c){(b.updateQueue=c)&&d(b)},w=function(a,b,c,e){c!==e&&d(b)}):q?E("235"):E("236");
  return{completeWork:function(a,b,c){var m=b.pendingProps;if(null===m)m=b.memoizedProps;else if(2147483647!==b.expirationTime||2147483647===c)b.pendingProps=null;switch(b.tag){case 1:return null;case 2:return ne(b),null;case 3:z(b);V(X,b);V(ie,b);m=b.stateNode;m.pendingContext&&(m.context=m.pendingContext,m.pendingContext=null);if(null===a||null===a.child)L(b),b.effectTag&=-3;N(b);return null;case 5:y(b);c=v();var A=b.type;if(null!==a&&null!=b.stateNode){var p=a.memoizedProps,q=b.stateNode,x=u();q=
  k(q,A,p,m,c,x);J(a,b,q,A,p,m,c);a.ref!==b.ref&&(b.effectTag|=128)}else{if(!m)return null===b.stateNode?E("166"):void 0,null;a=u();if(L(b))G(b,c,a)&&d(b);else{a=e(A,m,c,a,b);a:for(p=b.child;null!==p;){if(5===p.tag||6===p.tag)g(a,p.stateNode);else if(4!==p.tag&&null!==p.child){p.child["return"]=p;p=p.child;continue}if(p===b)break;for(;null===p.sibling;){if(null===p["return"]||p["return"]===b)break a;p=p["return"]}p.sibling["return"]=p["return"];p=p.sibling}h(a,A,m,c)&&d(b);b.stateNode=a}null!==b.ref&&
  (b.effectTag|=128)}return null;case 6:if(a&&null!=b.stateNode)w(a,b,a.memoizedProps,m);else{if("string"!==typeof m)return null===b.stateNode?E("166"):void 0,null;a=v();c=u();L(b)?I(b)&&d(b):b.stateNode=f(m,a,c,b)}return null;case 7:(m=b.memoizedProps)?void 0:E("165");b.tag=8;A=[];a:for((p=b.stateNode)&&(p["return"]=b);null!==p;){if(5===p.tag||6===p.tag||4===p.tag)E("247");else if(9===p.tag)A.push(p.type);else if(null!==p.child){p.child["return"]=p;p=p.child;continue}for(;null===p.sibling;){if(null===
  p["return"]||p["return"]===b)break a;p=p["return"]}p.sibling["return"]=p["return"];p=p.sibling}p=m.handler;m=p(m.props,A);b.child=bf(b,null!==a?a.child:null,m,c);return b.child;case 8:return b.tag=7,null;case 9:return null;case 10:return null;case 4:return z(b),N(b),null;case 0:E("167");default:E("156")}}}}
  function ff(a,b){function c(a){var c=a.ref;if(null!==c)try{c(null)}catch(A){b(a,A)}}function d(a){"function"===typeof Ee&&Ee(a);switch(a.tag){case 2:c(a);var d=a.stateNode;if("function"===typeof d.componentWillUnmount)try{d.props=a.memoizedProps,d.state=a.memoizedState,d.componentWillUnmount()}catch(A){b(a,A)}break;case 5:c(a);break;case 7:e(a.stateNode);break;case 4:k&&g(a)}}function e(a){for(var b=a;;)if(d(b),null===b.child||k&&4===b.tag){if(b===a)break;for(;null===b.sibling;){if(null===b["return"]||
  b["return"]===a)return;b=b["return"]}b.sibling["return"]=b["return"];b=b.sibling}else b.child["return"]=b,b=b.child}function f(a){return 5===a.tag||3===a.tag||4===a.tag}function g(a){for(var b=a,c=!1,f=void 0,g=void 0;;){if(!c){c=b["return"];a:for(;;){null===c?E("160"):void 0;switch(c.tag){case 5:f=c.stateNode;g=!1;break a;case 3:f=c.stateNode.containerInfo;g=!0;break a;case 4:f=c.stateNode.containerInfo;g=!0;break a}c=c["return"]}c=!0}if(5===b.tag||6===b.tag)e(b),g?J(f,b.stateNode):N(f,b.stateNode);
  else if(4===b.tag?f=b.stateNode.containerInfo:d(b),null!==b.child){b.child["return"]=b;b=b.child;continue}if(b===a)break;for(;null===b.sibling;){if(null===b["return"]||b["return"]===a)return;b=b["return"];4===b.tag&&(c=!1)}b.sibling["return"]=b["return"];b=b.sibling}}var h=a.getPublicInstance,k=a.mutation;a=a.persistence;k||(a?E("235"):E("236"));var q=k.commitMount,v=k.commitUpdate,y=k.resetTextContent,u=k.commitTextUpdate,z=k.appendChild,G=k.appendChildToContainer,I=k.insertBefore,L=k.insertInContainerBefore,
  N=k.removeChild,J=k.removeChildFromContainer;return{commitResetTextContent:function(a){y(a.stateNode)},commitPlacement:function(a){a:{for(var b=a["return"];null!==b;){if(f(b)){var c=b;break a}b=b["return"]}E("160");c=void 0}var d=b=void 0;switch(c.tag){case 5:b=c.stateNode;d=!1;break;case 3:b=c.stateNode.containerInfo;d=!0;break;case 4:b=c.stateNode.containerInfo;d=!0;break;default:E("161")}c.effectTag&16&&(y(b),c.effectTag&=-17);a:b:for(c=a;;){for(;null===c.sibling;){if(null===c["return"]||f(c["return"])){c=
  null;break a}c=c["return"]}c.sibling["return"]=c["return"];for(c=c.sibling;5!==c.tag&&6!==c.tag;){if(c.effectTag&2)continue b;if(null===c.child||4===c.tag)continue b;else c.child["return"]=c,c=c.child}if(!(c.effectTag&2)){c=c.stateNode;break a}}for(var e=a;;){if(5===e.tag||6===e.tag)c?d?L(b,e.stateNode,c):I(b,e.stateNode,c):d?G(b,e.stateNode):z(b,e.stateNode);else if(4!==e.tag&&null!==e.child){e.child["return"]=e;e=e.child;continue}if(e===a)break;for(;null===e.sibling;){if(null===e["return"]||e["return"]===
  a)return;e=e["return"]}e.sibling["return"]=e["return"];e=e.sibling}},commitDeletion:function(a){g(a);a["return"]=null;a.child=null;a.alternate&&(a.alternate.child=null,a.alternate["return"]=null)},commitWork:function(a,b){switch(b.tag){case 2:break;case 5:var c=b.stateNode;if(null!=c){var d=b.memoizedProps;a=null!==a?a.memoizedProps:d;var e=b.type,f=b.updateQueue;b.updateQueue=null;null!==f&&v(c,f,e,a,d,b)}break;case 6:null===b.stateNode?E("162"):void 0;c=b.memoizedProps;u(b.stateNode,null!==a?a.memoizedProps:
  c,c);break;case 3:break;default:E("163")}},commitLifeCycles:function(a,b){switch(b.tag){case 2:var c=b.stateNode;if(b.effectTag&4)if(null===a)c.props=b.memoizedProps,c.state=b.memoizedState,c.componentDidMount();else{var d=a.memoizedProps;a=a.memoizedState;c.props=b.memoizedProps;c.state=b.memoizedState;c.componentDidUpdate(d,a)}b=b.updateQueue;null!==b&&Ke(b,c);break;case 3:c=b.updateQueue;null!==c&&Ke(c,null!==b.child?b.child.stateNode:null);break;case 5:c=b.stateNode;null===a&&b.effectTag&4&&q(c,
  b.type,b.memoizedProps,b);break;case 6:break;case 4:break;default:E("163")}},commitAttachRef:function(a){var b=a.ref;if(null!==b){var c=a.stateNode;switch(a.tag){case 5:b(h(c));break;default:b(c)}}},commitDetachRef:function(a){a=a.ref;null!==a&&a(null)}}}var gf={};
  function hf(a){function b(a){a===gf?E("174"):void 0;return a}var c=a.getChildHostContext,d=a.getRootHostContext,e={current:gf},f={current:gf},g={current:gf};return{getHostContext:function(){return b(e.current)},getRootHostContainer:function(){return b(g.current)},popHostContainer:function(a){V(e,a);V(f,a);V(g,a)},popHostContext:function(a){f.current===a&&(V(e,a),V(f,a))},pushHostContainer:function(a,b){W(g,b,a);b=d(b);W(f,a,a);W(e,b,a)},pushHostContext:function(a){var d=b(g.current),h=b(e.current);
  d=c(h,a.type,d);h!==d&&(W(f,a,a),W(e,d,a))},resetHostContainer:function(){e.current=gf;g.current=gf}}}
  function jf(a){function b(a,b){var c=new Y(5,null,0);c.type="DELETED";c.stateNode=b;c["return"]=a;c.effectTag=8;null!==a.lastEffect?(a.lastEffect.nextEffect=c,a.lastEffect=c):a.firstEffect=a.lastEffect=c}function c(a,b){switch(a.tag){case 5:return b=f(b,a.type,a.pendingProps),null!==b?(a.stateNode=b,!0):!1;case 6:return b=g(b,a.pendingProps),null!==b?(a.stateNode=b,!0):!1;default:return!1}}function d(a){for(a=a["return"];null!==a&&5!==a.tag&&3!==a.tag;)a=a["return"];y=a}var e=a.shouldSetTextContent;
  a=a.hydration;if(!a)return{enterHydrationState:function(){return!1},resetHydrationState:function(){},tryToClaimNextHydratableInstance:function(){},prepareToHydrateHostInstance:function(){E("175")},prepareToHydrateHostTextInstance:function(){E("176")},popHydrationState:function(){return!1}};var f=a.canHydrateInstance,g=a.canHydrateTextInstance,h=a.getNextHydratableSibling,k=a.getFirstHydratableChild,q=a.hydrateInstance,v=a.hydrateTextInstance,y=null,u=null,z=!1;return{enterHydrationState:function(a){u=
  k(a.stateNode.containerInfo);y=a;return z=!0},resetHydrationState:function(){u=y=null;z=!1},tryToClaimNextHydratableInstance:function(a){if(z){var d=u;if(d){if(!c(a,d)){d=h(d);if(!d||!c(a,d)){a.effectTag|=2;z=!1;y=a;return}b(y,u)}y=a;u=k(d)}else a.effectTag|=2,z=!1,y=a}},prepareToHydrateHostInstance:function(a,b,c){b=q(a.stateNode,a.type,a.memoizedProps,b,c,a);a.updateQueue=b;return null!==b?!0:!1},prepareToHydrateHostTextInstance:function(a){return v(a.stateNode,a.memoizedProps,a)},popHydrationState:function(a){if(a!==
  y)return!1;if(!z)return d(a),z=!0,!1;var c=a.type;if(5!==a.tag||"head"!==c&&"body"!==c&&!e(c,a.memoizedProps))for(c=u;c;)b(a,c),c=h(c);d(a);u=y?h(a.stateNode):null;return!0}}}
  function kf(a){function b(a){Qb=ja=!0;var b=a.stateNode;b.current===a?E("177"):void 0;b.isReadyForCommit=!1;id.current=null;if(1<a.effectTag)if(null!==a.lastEffect){a.lastEffect.nextEffect=a;var c=a.firstEffect}else c=a;else c=a.firstEffect;yg();for(t=c;null!==t;){var d=!1,e=void 0;try{for(;null!==t;){var f=t.effectTag;f&16&&zg(t);if(f&128){var g=t.alternate;null!==g&&Ag(g)}switch(f&-242){case 2:Ne(t);t.effectTag&=-3;break;case 6:Ne(t);t.effectTag&=-3;Oe(t.alternate,t);break;case 4:Oe(t.alternate,
  t);break;case 8:Sc=!0,Bg(t),Sc=!1}t=t.nextEffect}}catch(Tc){d=!0,e=Tc}d&&(null===t?E("178"):void 0,h(t,e),null!==t&&(t=t.nextEffect))}Cg();b.current=a;for(t=c;null!==t;){c=!1;d=void 0;try{for(;null!==t;){var k=t.effectTag;k&36&&Dg(t.alternate,t);k&128&&Eg(t);if(k&64)switch(e=t,f=void 0,null!==R&&(f=R.get(e),R["delete"](e),null==f&&null!==e.alternate&&(e=e.alternate,f=R.get(e),R["delete"](e))),null==f?E("184"):void 0,e.tag){case 2:e.stateNode.componentDidCatch(f.error,{componentStack:f.componentStack});
  break;case 3:null===ca&&(ca=f.error);break;default:E("157")}var Qc=t.nextEffect;t.nextEffect=null;t=Qc}}catch(Tc){c=!0,d=Tc}c&&(null===t?E("178"):void 0,h(t,d),null!==t&&(t=t.nextEffect))}ja=Qb=!1;"function"===typeof De&&De(a.stateNode);ha&&(ha.forEach(G),ha=null);null!==ca&&(a=ca,ca=null,Ob(a));b=b.current.expirationTime;0===b&&(qa=R=null);return b}function c(a){for(;;){var b=Fg(a.alternate,a,H),c=a["return"],d=a.sibling;var e=a;if(2147483647===H||2147483647!==e.expirationTime){if(2!==e.tag&&3!==
  e.tag)var f=0;else f=e.updateQueue,f=null===f?0:f.expirationTime;for(var g=e.child;null!==g;)0!==g.expirationTime&&(0===f||f>g.expirationTime)&&(f=g.expirationTime),g=g.sibling;e.expirationTime=f}if(null!==b)return b;null!==c&&(null===c.firstEffect&&(c.firstEffect=a.firstEffect),null!==a.lastEffect&&(null!==c.lastEffect&&(c.lastEffect.nextEffect=a.firstEffect),c.lastEffect=a.lastEffect),1<a.effectTag&&(null!==c.lastEffect?c.lastEffect.nextEffect=a:c.firstEffect=a,c.lastEffect=a));if(null!==d)return d;
  if(null!==c)a=c;else{a.stateNode.isReadyForCommit=!0;break}}return null}function d(a){var b=rg(a.alternate,a,H);null===b&&(b=c(a));id.current=null;return b}function e(a){var b=Gg(a.alternate,a,H);null===b&&(b=c(a));id.current=null;return b}function f(a){if(null!==R){if(!(0===H||H>a))if(H<=Uc)for(;null!==F;)F=k(F)?e(F):d(F);else for(;null!==F&&!A();)F=k(F)?e(F):d(F)}else if(!(0===H||H>a))if(H<=Uc)for(;null!==F;)F=d(F);else for(;null!==F&&!A();)F=d(F)}function g(a,b){ja?E("243"):void 0;ja=!0;a.isReadyForCommit=
  !1;if(a!==ra||b!==H||null===F){for(;-1<he;)ge[he]=null,he--;je=D;ie.current=D;X.current=!1;x();ra=a;H=b;F=se(ra.current,null,b)}var c=!1,d=null;try{f(b)}catch(Rc){c=!0,d=Rc}for(;c;){if(eb){ca=d;break}var g=F;if(null===g)eb=!0;else{var k=h(g,d);null===k?E("183"):void 0;if(!eb){try{c=k;d=b;for(k=c;null!==g;){switch(g.tag){case 2:ne(g);break;case 5:qg(g);break;case 3:p(g);break;case 4:p(g)}if(g===k||g.alternate===k)break;g=g["return"]}F=e(c);f(d)}catch(Rc){c=!0;d=Rc;continue}break}}}b=ca;eb=ja=!1;ca=
  null;null!==b&&Ob(b);return a.isReadyForCommit?a.current.alternate:null}function h(a,b){var c=id.current=null,d=!1,e=!1,f=null;if(3===a.tag)c=a,q(a)&&(eb=!0);else for(var g=a["return"];null!==g&&null===c;){2===g.tag?"function"===typeof g.stateNode.componentDidCatch&&(d=!0,f=jd(g),c=g,e=!0):3===g.tag&&(c=g);if(q(g)){if(Sc||null!==ha&&(ha.has(g)||null!==g.alternate&&ha.has(g.alternate)))return null;c=null;e=!1}g=g["return"]}if(null!==c){null===qa&&(qa=new Set);qa.add(c);var h="";g=a;do{a:switch(g.tag){case 0:case 1:case 2:case 5:var k=
  g._debugOwner,Qc=g._debugSource;var m=jd(g);var n=null;k&&(n=jd(k));k=Qc;m="\n    in "+(m||"Unknown")+(k?" (at "+k.fileName.replace(/^.*[\\\/]/,"")+":"+k.lineNumber+")":n?" (created by "+n+")":"");break a;default:m=""}h+=m;g=g["return"]}while(g);g=h;a=jd(a);null===R&&(R=new Map);b={componentName:a,componentStack:g,error:b,errorBoundary:d?c.stateNode:null,errorBoundaryFound:d,errorBoundaryName:f,willRetry:e};R.set(c,b);try{var p=b.error;p&&p.suppressReactErrorLogging||console.error(p)}catch(Vc){Vc&&
  Vc.suppressReactErrorLogging||console.error(Vc)}Qb?(null===ha&&(ha=new Set),ha.add(c)):G(c);return c}null===ca&&(ca=b);return null}function k(a){return null!==R&&(R.has(a)||null!==a.alternate&&R.has(a.alternate))}function q(a){return null!==qa&&(qa.has(a)||null!==a.alternate&&qa.has(a.alternate))}function v(){return 20*(((I()+100)/20|0)+1)}function y(a){return 0!==ka?ka:ja?Qb?1:H:!Hg||a.internalContextTag&1?v():1}function u(a,b){return z(a,b,!1)}function z(a,b){for(;null!==a;){if(0===a.expirationTime||
  a.expirationTime>b)a.expirationTime=b;null!==a.alternate&&(0===a.alternate.expirationTime||a.alternate.expirationTime>b)&&(a.alternate.expirationTime=b);if(null===a["return"])if(3===a.tag){var c=a.stateNode;!ja&&c===ra&&b<H&&(F=ra=null,H=0);var d=c,e=b;Rb>Ig&&E("185");if(null===d.nextScheduledRoot)d.remainingExpirationTime=e,null===O?(sa=O=d,d.nextScheduledRoot=d):(O=O.nextScheduledRoot=d,O.nextScheduledRoot=sa);else{var f=d.remainingExpirationTime;if(0===f||e<f)d.remainingExpirationTime=e}Fa||(la?
  Sb&&(ma=d,na=1,m(ma,na)):1===e?w(1,null):L(e));!ja&&c===ra&&b<H&&(F=ra=null,H=0)}else break;a=a["return"]}}function G(a){z(a,1,!0)}function I(){return Uc=((Wc()-Pe)/10|0)+2}function L(a){if(0!==Tb){if(a>Tb)return;Jg(Xc)}var b=Wc()-Pe;Tb=a;Xc=Kg(J,{timeout:10*(a-2)-b})}function N(){var a=0,b=null;if(null!==O)for(var c=O,d=sa;null!==d;){var e=d.remainingExpirationTime;if(0===e){null===c||null===O?E("244"):void 0;if(d===d.nextScheduledRoot){sa=O=d.nextScheduledRoot=null;break}else if(d===sa)sa=e=d.nextScheduledRoot,
  O.nextScheduledRoot=e,d.nextScheduledRoot=null;else if(d===O){O=c;O.nextScheduledRoot=sa;d.nextScheduledRoot=null;break}else c.nextScheduledRoot=d.nextScheduledRoot,d.nextScheduledRoot=null;d=c.nextScheduledRoot}else{if(0===a||e<a)a=e,b=d;if(d===O)break;c=d;d=d.nextScheduledRoot}}c=ma;null!==c&&c===b?Rb++:Rb=0;ma=b;na=a}function J(a){w(0,a)}function w(a,b){fb=b;for(N();null!==ma&&0!==na&&(0===a||na<=a)&&!Yc;)m(ma,na),N();null!==fb&&(Tb=0,Xc=-1);0!==na&&L(na);fb=null;Yc=!1;Rb=0;if(Ub)throw a=Zc,Zc=
  null,Ub=!1,a;}function m(a,c){Fa?E("245"):void 0;Fa=!0;if(c<=I()){var d=a.finishedWork;null!==d?(a.finishedWork=null,a.remainingExpirationTime=b(d)):(a.finishedWork=null,d=g(a,c),null!==d&&(a.remainingExpirationTime=b(d)))}else d=a.finishedWork,null!==d?(a.finishedWork=null,a.remainingExpirationTime=b(d)):(a.finishedWork=null,d=g(a,c),null!==d&&(A()?a.finishedWork=d:a.remainingExpirationTime=b(d)));Fa=!1}function A(){return null===fb||fb.timeRemaining()>Lg?!1:Yc=!0}function Ob(a){null===ma?E("246"):
  void 0;ma.remainingExpirationTime=0;Ub||(Ub=!0,Zc=a)}var r=hf(a),n=jf(a),p=r.popHostContainer,qg=r.popHostContext,x=r.resetHostContainer,Me=df(a,r,n,u,y),rg=Me.beginWork,Gg=Me.beginFailedWork,Fg=ef(a,r,n).completeWork;r=ff(a,h);var zg=r.commitResetTextContent,Ne=r.commitPlacement,Bg=r.commitDeletion,Oe=r.commitWork,Dg=r.commitLifeCycles,Eg=r.commitAttachRef,Ag=r.commitDetachRef,Wc=a.now,Kg=a.scheduleDeferredCallback,Jg=a.cancelDeferredCallback,Hg=a.useSyncScheduling,yg=a.prepareForCommit,Cg=a.resetAfterCommit,
  Pe=Wc(),Uc=2,ka=0,ja=!1,F=null,ra=null,H=0,t=null,R=null,qa=null,ha=null,ca=null,eb=!1,Qb=!1,Sc=!1,sa=null,O=null,Tb=0,Xc=-1,Fa=!1,ma=null,na=0,Yc=!1,Ub=!1,Zc=null,fb=null,la=!1,Sb=!1,Ig=1E3,Rb=0,Lg=1;return{computeAsyncExpiration:v,computeExpirationForFiber:y,scheduleWork:u,batchedUpdates:function(a,b){var c=la;la=!0;try{return a(b)}finally{(la=c)||Fa||w(1,null)}},unbatchedUpdates:function(a){if(la&&!Sb){Sb=!0;try{return a()}finally{Sb=!1}}return a()},flushSync:function(a){var b=la;la=!0;try{a:{var c=
  ka;ka=1;try{var d=a();break a}finally{ka=c}d=void 0}return d}finally{la=b,Fa?E("187"):void 0,w(1,null)}},deferredUpdates:function(a){var b=ka;ka=v();try{return a()}finally{ka=b}}}}
  function lf(a){function b(a){a=od(a);return null===a?null:a.stateNode}var c=a.getPublicInstance;a=kf(a);var d=a.computeAsyncExpiration,e=a.computeExpirationForFiber,f=a.scheduleWork;return{createContainer:function(a,b){var c=new Y(3,null,0);a={current:c,containerInfo:a,pendingChildren:null,remainingExpirationTime:0,isReadyForCommit:!1,finishedWork:null,context:null,pendingContext:null,hydrate:b,nextScheduledRoot:null};return c.stateNode=a},updateContainer:function(a,b,c,q){var g=b.current;if(c){c=
  c._reactInternalFiber;var h;b:{2===kd(c)&&2===c.tag?void 0:E("170");for(h=c;3!==h.tag;){if(le(h)){h=h.stateNode.__reactInternalMemoizedMergedChildContext;break b}(h=h["return"])?void 0:E("171")}h=h.stateNode.context}c=le(c)?pe(c,h):h}else c=D;null===b.context?b.context=c:b.pendingContext=c;b=q;b=void 0===b?null:b;q=null!=a&&null!=a.type&&null!=a.type.prototype&&!0===a.type.prototype.unstable_isAsyncReactComponent?d():e(g);He(g,{expirationTime:q,partialState:{element:a},callback:b,isReplace:!1,isForced:!1,
  nextCallback:null,next:null});f(g,q)},batchedUpdates:a.batchedUpdates,unbatchedUpdates:a.unbatchedUpdates,deferredUpdates:a.deferredUpdates,flushSync:a.flushSync,getPublicRootInstance:function(a){a=a.current;if(!a.child)return null;switch(a.child.tag){case 5:return c(a.child.stateNode);default:return a.child.stateNode}},findHostInstance:b,findHostInstanceWithNoPortals:function(a){a=pd(a);return null===a?null:a.stateNode},injectIntoDevTools:function(a){var c=a.findFiberByHostInstance;return Ce(B({},
  a,{findHostInstanceByFiber:function(a){return b(a)},findFiberByHostInstance:function(a){return c?c(a):null}}))}}}var mf=Object.freeze({default:lf}),nf=mf&&lf||mf,of=nf["default"]?nf["default"]:nf;function pf(a,b,c){var d=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;return{$$typeof:Ue,key:null==d?null:""+d,children:a,containerInfo:b,implementation:c}}var qf="object"===typeof performance&&"function"===typeof performance.now,rf=void 0;rf=qf?function(){return performance.now()}:function(){return Date.now()};
  var sf=void 0,tf=void 0;
  if(l.canUseDOM)if("function"!==typeof requestIdleCallback||"function"!==typeof cancelIdleCallback){var uf=null,vf=!1,wf=-1,xf=!1,yf=0,zf=33,Af=33,Bf;Bf=qf?{didTimeout:!1,timeRemaining:function(){var a=yf-performance.now();return 0<a?a:0}}:{didTimeout:!1,timeRemaining:function(){var a=yf-Date.now();return 0<a?a:0}};var Cf="__reactIdleCallback$"+Math.random().toString(36).slice(2);window.addEventListener("message",function(a){if(a.source===window&&a.data===Cf){vf=!1;a=rf();if(0>=yf-a)if(-1!==wf&&wf<=
  a)Bf.didTimeout=!0;else{xf||(xf=!0,requestAnimationFrame(Df));return}else Bf.didTimeout=!1;wf=-1;a=uf;uf=null;null!==a&&a(Bf)}},!1);var Df=function(a){xf=!1;var b=a-yf+Af;b<Af&&zf<Af?(8>b&&(b=8),Af=b<zf?zf:b):zf=b;yf=a+Af;vf||(vf=!0,window.postMessage(Cf,"*"))};sf=function(a,b){uf=a;null!=b&&"number"===typeof b.timeout&&(wf=rf()+b.timeout);xf||(xf=!0,requestAnimationFrame(Df));return 0};tf=function(){uf=null;vf=!1;wf=-1}}else sf=window.requestIdleCallback,tf=window.cancelIdleCallback;else sf=function(a){return setTimeout(function(){a({timeRemaining:function(){return Infinity}})})},
  tf=function(a){clearTimeout(a)};var Ef=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,Ff={},Gf={};
  function Hf(a){if(Gf.hasOwnProperty(a))return!0;if(Ff.hasOwnProperty(a))return!1;if(Ef.test(a))return Gf[a]=!0;Ff[a]=!0;return!1}
  function If(a,b,c){var d=wa(b);if(d&&va(b,c)){var e=d.mutationMethod;e?e(a,c):null==c||d.hasBooleanValue&&!c||d.hasNumericValue&&isNaN(c)||d.hasPositiveNumericValue&&1>c||d.hasOverloadedBooleanValue&&!1===c?Jf(a,b):d.mustUseProperty?a[d.propertyName]=c:(b=d.attributeName,(e=d.attributeNamespace)?a.setAttributeNS(e,b,""+c):d.hasBooleanValue||d.hasOverloadedBooleanValue&&!0===c?a.setAttribute(b,""):a.setAttribute(b,""+c))}else Kf(a,b,va(b,c)?c:null)}
  function Kf(a,b,c){Hf(b)&&(null==c?a.removeAttribute(b):a.setAttribute(b,""+c))}function Jf(a,b){var c=wa(b);c?(b=c.mutationMethod)?b(a,void 0):c.mustUseProperty?a[c.propertyName]=c.hasBooleanValue?!1:"":a.removeAttribute(c.attributeName):a.removeAttribute(b)}
  function Lf(a,b){var c=b.value,d=b.checked;return B({type:void 0,step:void 0,min:void 0,max:void 0},b,{defaultChecked:void 0,defaultValue:void 0,value:null!=c?c:a._wrapperState.initialValue,checked:null!=d?d:a._wrapperState.initialChecked})}function Mf(a,b){var c=b.defaultValue;a._wrapperState={initialChecked:null!=b.checked?b.checked:b.defaultChecked,initialValue:null!=b.value?b.value:c,controlled:"checkbox"===b.type||"radio"===b.type?null!=b.checked:null!=b.value}}
  function Nf(a,b){b=b.checked;null!=b&&If(a,"checked",b)}function Of(a,b){Nf(a,b);var c=b.value;if(null!=c)if(0===c&&""===a.value)a.value="0";else if("number"===b.type){if(b=parseFloat(a.value)||0,c!=b||c==b&&a.value!=c)a.value=""+c}else a.value!==""+c&&(a.value=""+c);else null==b.value&&null!=b.defaultValue&&a.defaultValue!==""+b.defaultValue&&(a.defaultValue=""+b.defaultValue),null==b.checked&&null!=b.defaultChecked&&(a.defaultChecked=!!b.defaultChecked)}
  function Pf(a,b){switch(b.type){case "submit":case "reset":break;case "color":case "date":case "datetime":case "datetime-local":case "month":case "time":case "week":a.value="";a.value=a.defaultValue;break;default:a.value=a.value}b=a.name;""!==b&&(a.name="");a.defaultChecked=!a.defaultChecked;a.defaultChecked=!a.defaultChecked;""!==b&&(a.name=b)}function Qf(a){var b="";aa.Children.forEach(a,function(a){null==a||"string"!==typeof a&&"number"!==typeof a||(b+=a)});return b}
  function Rf(a,b){a=B({children:void 0},b);if(b=Qf(b.children))a.children=b;return a}function Sf(a,b,c,d){a=a.options;if(b){b={};for(var e=0;e<c.length;e++)b["$"+c[e]]=!0;for(c=0;c<a.length;c++)e=b.hasOwnProperty("$"+a[c].value),a[c].selected!==e&&(a[c].selected=e),e&&d&&(a[c].defaultSelected=!0)}else{c=""+c;b=null;for(e=0;e<a.length;e++){if(a[e].value===c){a[e].selected=!0;d&&(a[e].defaultSelected=!0);return}null!==b||a[e].disabled||(b=a[e])}null!==b&&(b.selected=!0)}}
  function Tf(a,b){var c=b.value;a._wrapperState={initialValue:null!=c?c:b.defaultValue,wasMultiple:!!b.multiple}}function Uf(a,b){null!=b.dangerouslySetInnerHTML?E("91"):void 0;return B({},b,{value:void 0,defaultValue:void 0,children:""+a._wrapperState.initialValue})}function Vf(a,b){var c=b.value;null==c&&(c=b.defaultValue,b=b.children,null!=b&&(null!=c?E("92"):void 0,Array.isArray(b)&&(1>=b.length?void 0:E("93"),b=b[0]),c=""+b),null==c&&(c=""));a._wrapperState={initialValue:""+c}}
  function Wf(a,b){var c=b.value;null!=c&&(c=""+c,c!==a.value&&(a.value=c),null==b.defaultValue&&(a.defaultValue=c));null!=b.defaultValue&&(a.defaultValue=b.defaultValue)}function Xf(a){var b=a.textContent;b===a._wrapperState.initialValue&&(a.value=b)}var Yf={html:"http://www.w3.org/1999/xhtml",mathml:"http://www.w3.org/1998/Math/MathML",svg:"http://www.w3.org/2000/svg"};
  function Zf(a){switch(a){case "svg":return"http://www.w3.org/2000/svg";case "math":return"http://www.w3.org/1998/Math/MathML";default:return"http://www.w3.org/1999/xhtml"}}function $f(a,b){return null==a||"http://www.w3.org/1999/xhtml"===a?Zf(b):"http://www.w3.org/2000/svg"===a&&"foreignObject"===b?"http://www.w3.org/1999/xhtml":a}
  var ag=void 0,bg=function(a){return"undefined"!==typeof MSApp&&MSApp.execUnsafeLocalFunction?function(b,c,d,e){MSApp.execUnsafeLocalFunction(function(){return a(b,c,d,e)})}:a}(function(a,b){if(a.namespaceURI!==Yf.svg||"innerHTML"in a)a.innerHTML=b;else{ag=ag||document.createElement("div");ag.innerHTML="\x3csvg\x3e"+b+"\x3c/svg\x3e";for(b=ag.firstChild;a.firstChild;)a.removeChild(a.firstChild);for(;b.firstChild;)a.appendChild(b.firstChild)}});
  function cg(a,b){if(b){var c=a.firstChild;if(c&&c===a.lastChild&&3===c.nodeType){c.nodeValue=b;return}}a.textContent=b}
  var dg={animationIterationCount:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,
  stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},eg=["Webkit","ms","Moz","O"];Object.keys(dg).forEach(function(a){eg.forEach(function(b){b=b+a.charAt(0).toUpperCase()+a.substring(1);dg[b]=dg[a]})});
  function fg(a,b){a=a.style;for(var c in b)if(b.hasOwnProperty(c)){var d=0===c.indexOf("--");var e=c;var f=b[c];e=null==f||"boolean"===typeof f||""===f?"":d||"number"!==typeof f||0===f||dg.hasOwnProperty(e)&&dg[e]?(""+f).trim():f+"px";"float"===c&&(c="cssFloat");d?a.setProperty(c,e):a[c]=e}}var gg=B({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});
  function hg(a,b,c){b&&(gg[a]&&(null!=b.children||null!=b.dangerouslySetInnerHTML?E("137",a,c()):void 0),null!=b.dangerouslySetInnerHTML&&(null!=b.children?E("60"):void 0,"object"===typeof b.dangerouslySetInnerHTML&&"__html"in b.dangerouslySetInnerHTML?void 0:E("61")),null!=b.style&&"object"!==typeof b.style?E("62",c()):void 0)}
  function ig(a,b){if(-1===a.indexOf("-"))return"string"===typeof b.is;switch(a){case "annotation-xml":case "color-profile":case "font-face":case "font-face-src":case "font-face-uri":case "font-face-format":case "font-face-name":case "missing-glyph":return!1;default:return!0}}var jg=Yf.html,kg=C.thatReturns("");
  function lg(a,b){a=9===a.nodeType||11===a.nodeType?a:a.ownerDocument;var c=Hd(a);b=Sa[b];for(var d=0;d<b.length;d++){var e=b[d];c.hasOwnProperty(e)&&c[e]||("topScroll"===e?wd("topScroll","scroll",a):"topFocus"===e||"topBlur"===e?(wd("topFocus","focus",a),wd("topBlur","blur",a),c.topBlur=!0,c.topFocus=!0):"topCancel"===e?(yc("cancel",!0)&&wd("topCancel","cancel",a),c.topCancel=!0):"topClose"===e?(yc("close",!0)&&wd("topClose","close",a),c.topClose=!0):Dd.hasOwnProperty(e)&&U(e,Dd[e],a),c[e]=!0)}}
  var mg={topAbort:"abort",topCanPlay:"canplay",topCanPlayThrough:"canplaythrough",topDurationChange:"durationchange",topEmptied:"emptied",topEncrypted:"encrypted",topEnded:"ended",topError:"error",topLoadedData:"loadeddata",topLoadedMetadata:"loadedmetadata",topLoadStart:"loadstart",topPause:"pause",topPlay:"play",topPlaying:"playing",topProgress:"progress",topRateChange:"ratechange",topSeeked:"seeked",topSeeking:"seeking",topStalled:"stalled",topSuspend:"suspend",topTimeUpdate:"timeupdate",topVolumeChange:"volumechange",
  topWaiting:"waiting"};function ng(a,b,c,d){c=9===c.nodeType?c:c.ownerDocument;d===jg&&(d=Zf(a));d===jg?"script"===a?(a=c.createElement("div"),a.innerHTML="\x3cscript\x3e\x3c/script\x3e",a=a.removeChild(a.firstChild)):a="string"===typeof b.is?c.createElement(a,{is:b.is}):c.createElement(a):a=c.createElementNS(d,a);return a}function og(a,b){return(9===b.nodeType?b:b.ownerDocument).createTextNode(a)}
  function pg(a,b,c,d){var e=ig(b,c);switch(b){case "iframe":case "object":U("topLoad","load",a);var f=c;break;case "video":case "audio":for(f in mg)mg.hasOwnProperty(f)&&U(f,mg[f],a);f=c;break;case "source":U("topError","error",a);f=c;break;case "img":case "image":U("topError","error",a);U("topLoad","load",a);f=c;break;case "form":U("topReset","reset",a);U("topSubmit","submit",a);f=c;break;case "details":U("topToggle","toggle",a);f=c;break;case "input":Mf(a,c);f=Lf(a,c);U("topInvalid","invalid",a);
  lg(d,"onChange");break;case "option":f=Rf(a,c);break;case "select":Tf(a,c);f=B({},c,{value:void 0});U("topInvalid","invalid",a);lg(d,"onChange");break;case "textarea":Vf(a,c);f=Uf(a,c);U("topInvalid","invalid",a);lg(d,"onChange");break;default:f=c}hg(b,f,kg);var g=f,h;for(h in g)if(g.hasOwnProperty(h)){var k=g[h];"style"===h?fg(a,k,kg):"dangerouslySetInnerHTML"===h?(k=k?k.__html:void 0,null!=k&&bg(a,k)):"children"===h?"string"===typeof k?("textarea"!==b||""!==k)&&cg(a,k):"number"===typeof k&&cg(a,
  ""+k):"suppressContentEditableWarning"!==h&&"suppressHydrationWarning"!==h&&"autoFocus"!==h&&(Ra.hasOwnProperty(h)?null!=k&&lg(d,h):e?Kf(a,h,k):null!=k&&If(a,h,k))}switch(b){case "input":Bc(a);Pf(a,c);break;case "textarea":Bc(a);Xf(a,c);break;case "option":null!=c.value&&a.setAttribute("value",c.value);break;case "select":a.multiple=!!c.multiple;b=c.value;null!=b?Sf(a,!!c.multiple,b,!1):null!=c.defaultValue&&Sf(a,!!c.multiple,c.defaultValue,!0);break;default:"function"===typeof f.onClick&&(a.onclick=
  C)}}
  function sg(a,b,c,d,e){var f=null;switch(b){case "input":c=Lf(a,c);d=Lf(a,d);f=[];break;case "option":c=Rf(a,c);d=Rf(a,d);f=[];break;case "select":c=B({},c,{value:void 0});d=B({},d,{value:void 0});f=[];break;case "textarea":c=Uf(a,c);d=Uf(a,d);f=[];break;default:"function"!==typeof c.onClick&&"function"===typeof d.onClick&&(a.onclick=C)}hg(b,d,kg);var g,h;a=null;for(g in c)if(!d.hasOwnProperty(g)&&c.hasOwnProperty(g)&&null!=c[g])if("style"===g)for(h in b=c[g],b)b.hasOwnProperty(h)&&(a||(a={}),a[h]=
  "");else"dangerouslySetInnerHTML"!==g&&"children"!==g&&"suppressContentEditableWarning"!==g&&"suppressHydrationWarning"!==g&&"autoFocus"!==g&&(Ra.hasOwnProperty(g)?f||(f=[]):(f=f||[]).push(g,null));for(g in d){var k=d[g];b=null!=c?c[g]:void 0;if(d.hasOwnProperty(g)&&k!==b&&(null!=k||null!=b))if("style"===g)if(b){for(h in b)!b.hasOwnProperty(h)||k&&k.hasOwnProperty(h)||(a||(a={}),a[h]="");for(h in k)k.hasOwnProperty(h)&&b[h]!==k[h]&&(a||(a={}),a[h]=k[h])}else a||(f||(f=[]),f.push(g,a)),a=k;else"dangerouslySetInnerHTML"===
  g?(k=k?k.__html:void 0,b=b?b.__html:void 0,null!=k&&b!==k&&(f=f||[]).push(g,""+k)):"children"===g?b===k||"string"!==typeof k&&"number"!==typeof k||(f=f||[]).push(g,""+k):"suppressContentEditableWarning"!==g&&"suppressHydrationWarning"!==g&&(Ra.hasOwnProperty(g)?(null!=k&&lg(e,g),f||b===k||(f=[])):(f=f||[]).push(g,k))}a&&(f=f||[]).push("style",a);return f}
  function tg(a,b,c,d,e){"input"===c&&"radio"===e.type&&null!=e.name&&Nf(a,e);ig(c,d);d=ig(c,e);for(var f=0;f<b.length;f+=2){var g=b[f],h=b[f+1];"style"===g?fg(a,h,kg):"dangerouslySetInnerHTML"===g?bg(a,h):"children"===g?cg(a,h):d?null!=h?Kf(a,g,h):a.removeAttribute(g):null!=h?If(a,g,h):Jf(a,g)}switch(c){case "input":Of(a,e);break;case "textarea":Wf(a,e);break;case "select":a._wrapperState.initialValue=void 0,b=a._wrapperState.wasMultiple,a._wrapperState.wasMultiple=!!e.multiple,c=e.value,null!=c?Sf(a,
  !!e.multiple,c,!1):b!==!!e.multiple&&(null!=e.defaultValue?Sf(a,!!e.multiple,e.defaultValue,!0):Sf(a,!!e.multiple,e.multiple?[]:"",!1))}}
  function ug(a,b,c,d,e){switch(b){case "iframe":case "object":U("topLoad","load",a);break;case "video":case "audio":for(var f in mg)mg.hasOwnProperty(f)&&U(f,mg[f],a);break;case "source":U("topError","error",a);break;case "img":case "image":U("topError","error",a);U("topLoad","load",a);break;case "form":U("topReset","reset",a);U("topSubmit","submit",a);break;case "details":U("topToggle","toggle",a);break;case "input":Mf(a,c);U("topInvalid","invalid",a);lg(e,"onChange");break;case "select":Tf(a,c);
  U("topInvalid","invalid",a);lg(e,"onChange");break;case "textarea":Vf(a,c),U("topInvalid","invalid",a),lg(e,"onChange")}hg(b,c,kg);d=null;for(var g in c)c.hasOwnProperty(g)&&(f=c[g],"children"===g?"string"===typeof f?a.textContent!==f&&(d=["children",f]):"number"===typeof f&&a.textContent!==""+f&&(d=["children",""+f]):Ra.hasOwnProperty(g)&&null!=f&&lg(e,g));switch(b){case "input":Bc(a);Pf(a,c);break;case "textarea":Bc(a);Xf(a,c);break;case "select":case "option":break;default:"function"===typeof c.onClick&&
  (a.onclick=C)}return d}function vg(a,b){return a.nodeValue!==b}
  var wg=Object.freeze({createElement:ng,createTextNode:og,setInitialProperties:pg,diffProperties:sg,updateProperties:tg,diffHydratedProperties:ug,diffHydratedText:vg,warnForUnmatchedText:function(){},warnForDeletedHydratableElement:function(){},warnForDeletedHydratableText:function(){},warnForInsertedHydratedElement:function(){},warnForInsertedHydratedText:function(){},restoreControlledState:function(a,b,c){switch(b){case "input":Of(a,c);b=c.name;if("radio"===c.type&&null!=b){for(c=a;c.parentNode;)c=
  c.parentNode;c=c.querySelectorAll("input[name\x3d"+JSON.stringify(""+b)+'][type\x3d"radio"]');for(b=0;b<c.length;b++){var d=c[b];if(d!==a&&d.form===a.form){var e=rb(d);e?void 0:E("90");Cc(d);Of(d,e)}}}break;case "textarea":Wf(a,c);break;case "select":b=c.value,null!=b&&Sf(a,!!c.multiple,b,!1)}}});nc.injectFiberControlledHostComponent(wg);var xg=null,Mg=null;function Ng(a){return!(!a||1!==a.nodeType&&9!==a.nodeType&&11!==a.nodeType&&(8!==a.nodeType||" react-mount-point-unstable "!==a.nodeValue))}
  function Og(a){a=a?9===a.nodeType?a.documentElement:a.firstChild:null;return!(!a||1!==a.nodeType||!a.hasAttribute("data-reactroot"))}
  var Z=of({getRootHostContext:function(a){var b=a.nodeType;switch(b){case 9:case 11:a=(a=a.documentElement)?a.namespaceURI:$f(null,"");break;default:b=8===b?a.parentNode:a,a=b.namespaceURI||null,b=b.tagName,a=$f(a,b)}return a},getChildHostContext:function(a,b){return $f(a,b)},getPublicInstance:function(a){return a},prepareForCommit:function(){xg=td;var a=da();if(Kd(a)){if("selectionStart"in a)var b={start:a.selectionStart,end:a.selectionEnd};else a:{var c=window.getSelection&&window.getSelection();
  if(c&&0!==c.rangeCount){b=c.anchorNode;var d=c.anchorOffset,e=c.focusNode;c=c.focusOffset;try{b.nodeType,e.nodeType}catch(z){b=null;break a}var f=0,g=-1,h=-1,k=0,q=0,v=a,y=null;b:for(;;){for(var u;;){v!==b||0!==d&&3!==v.nodeType||(g=f+d);v!==e||0!==c&&3!==v.nodeType||(h=f+c);3===v.nodeType&&(f+=v.nodeValue.length);if(null===(u=v.firstChild))break;y=v;v=u}for(;;){if(v===a)break b;y===b&&++k===d&&(g=f);y===e&&++q===c&&(h=f);if(null!==(u=v.nextSibling))break;v=y;y=v.parentNode}v=u}b=-1===g||-1===h?null:
  {start:g,end:h}}else b=null}b=b||{start:0,end:0}}else b=null;Mg={focusedElem:a,selectionRange:b};ud(!1)},resetAfterCommit:function(){var a=Mg,b=da(),c=a.focusedElem,d=a.selectionRange;if(b!==c&&fa(document.documentElement,c)){if(Kd(c))if(b=d.start,a=d.end,void 0===a&&(a=b),"selectionStart"in c)c.selectionStart=b,c.selectionEnd=Math.min(a,c.value.length);else if(window.getSelection){b=window.getSelection();var e=c[Eb()].length;a=Math.min(d.start,e);d=void 0===d.end?a:Math.min(d.end,e);!b.extend&&a>
  d&&(e=d,d=a,a=e);e=Jd(c,a);var f=Jd(c,d);if(e&&f&&(1!==b.rangeCount||b.anchorNode!==e.node||b.anchorOffset!==e.offset||b.focusNode!==f.node||b.focusOffset!==f.offset)){var g=document.createRange();g.setStart(e.node,e.offset);b.removeAllRanges();a>d?(b.addRange(g),b.extend(f.node,f.offset)):(g.setEnd(f.node,f.offset),b.addRange(g))}}b=[];for(a=c;a=a.parentNode;)1===a.nodeType&&b.push({element:a,left:a.scrollLeft,top:a.scrollTop});ia(c);for(c=0;c<b.length;c++)a=b[c],a.element.scrollLeft=a.left,a.element.scrollTop=
  a.top}Mg=null;ud(xg);xg=null},createInstance:function(a,b,c,d,e){a=ng(a,b,c,d);a[Q]=e;a[ob]=b;return a},appendInitialChild:function(a,b){a.appendChild(b)},finalizeInitialChildren:function(a,b,c,d){pg(a,b,c,d);a:{switch(b){case "button":case "input":case "select":case "textarea":a=!!c.autoFocus;break a}a=!1}return a},prepareUpdate:function(a,b,c,d,e){return sg(a,b,c,d,e)},shouldSetTextContent:function(a,b){return"textarea"===a||"string"===typeof b.children||"number"===typeof b.children||"object"===
  typeof b.dangerouslySetInnerHTML&&null!==b.dangerouslySetInnerHTML&&"string"===typeof b.dangerouslySetInnerHTML.__html},shouldDeprioritizeSubtree:function(a,b){return!!b.hidden},createTextInstance:function(a,b,c,d){a=og(a,b);a[Q]=d;return a},now:rf,mutation:{commitMount:function(a){a.focus()},commitUpdate:function(a,b,c,d,e){a[ob]=e;tg(a,b,c,d,e)},resetTextContent:function(a){a.textContent=""},commitTextUpdate:function(a,b,c){a.nodeValue=c},appendChild:function(a,b){a.appendChild(b)},appendChildToContainer:function(a,
  b){8===a.nodeType?a.parentNode.insertBefore(b,a):a.appendChild(b)},insertBefore:function(a,b,c){a.insertBefore(b,c)},insertInContainerBefore:function(a,b,c){8===a.nodeType?a.parentNode.insertBefore(b,c):a.insertBefore(b,c)},removeChild:function(a,b){a.removeChild(b)},removeChildFromContainer:function(a,b){8===a.nodeType?a.parentNode.removeChild(b):a.removeChild(b)}},hydration:{canHydrateInstance:function(a,b){return 1!==a.nodeType||b.toLowerCase()!==a.nodeName.toLowerCase()?null:a},canHydrateTextInstance:function(a,
  b){return""===b||3!==a.nodeType?null:a},getNextHydratableSibling:function(a){for(a=a.nextSibling;a&&1!==a.nodeType&&3!==a.nodeType;)a=a.nextSibling;return a},getFirstHydratableChild:function(a){for(a=a.firstChild;a&&1!==a.nodeType&&3!==a.nodeType;)a=a.nextSibling;return a},hydrateInstance:function(a,b,c,d,e,f){a[Q]=f;a[ob]=c;return ug(a,b,c,e,d)},hydrateTextInstance:function(a,b,c){a[Q]=c;return vg(a,b)},didNotMatchHydratedContainerTextInstance:function(){},didNotMatchHydratedTextInstance:function(){},
  didNotHydrateContainerInstance:function(){},didNotHydrateInstance:function(){},didNotFindHydratableContainerInstance:function(){},didNotFindHydratableContainerTextInstance:function(){},didNotFindHydratableInstance:function(){},didNotFindHydratableTextInstance:function(){}},scheduleDeferredCallback:sf,cancelDeferredCallback:tf,useSyncScheduling:!0});rc=Z.batchedUpdates;
  function Pg(a,b,c,d,e){Ng(c)?void 0:E("200");var f=c._reactRootContainer;if(f)Z.updateContainer(b,f,a,e);else{d=d||Og(c);if(!d)for(f=void 0;f=c.lastChild;)c.removeChild(f);var g=Z.createContainer(c,d);f=c._reactRootContainer=g;Z.unbatchedUpdates(function(){Z.updateContainer(b,g,a,e)})}return Z.getPublicRootInstance(f)}function Qg(a,b){var c=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null;Ng(b)?void 0:E("200");return pf(a,b,null,c)}
  function Rg(a,b){this._reactRootContainer=Z.createContainer(a,b)}Rg.prototype.render=function(a,b){Z.updateContainer(a,this._reactRootContainer,null,b)};Rg.prototype.unmount=function(a){Z.updateContainer(null,this._reactRootContainer,null,a)};
  var Sg={createPortal:Qg,findDOMNode:function(a){if(null==a)return null;if(1===a.nodeType)return a;var b=a._reactInternalFiber;if(b)return Z.findHostInstance(b);"function"===typeof a.render?E("188"):E("213",Object.keys(a))},hydrate:function(a,b,c){return Pg(null,a,b,!0,c)},render:function(a,b,c){return Pg(null,a,b,!1,c)},unstable_renderSubtreeIntoContainer:function(a,b,c,d){null==a||void 0===a._reactInternalFiber?E("38"):void 0;return Pg(a,b,c,!1,d)},unmountComponentAtNode:function(a){Ng(a)?void 0:
  E("40");return a._reactRootContainer?(Z.unbatchedUpdates(function(){Pg(null,null,a,!1,function(){a._reactRootContainer=null})}),!0):!1},unstable_createPortal:Qg,unstable_batchedUpdates:tc,unstable_deferredUpdates:Z.deferredUpdates,flushSync:Z.flushSync,__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED:{EventPluginHub:mb,EventPluginRegistry:Va,EventPropagators:Cb,ReactControlledComponent:qc,ReactDOMComponentTree:sb,ReactDOMEventListener:xd}};
  Z.injectIntoDevTools({findFiberByHostInstance:pb,bundleType:0,version:"16.2.0",rendererPackageName:"react-dom"});var Tg=Object.freeze({default:Sg}),Ug=Tg&&Sg||Tg;module.exports=Ug["default"]?Ug["default"]:Ug;
  
  },{"fbjs/lib/EventListener":333,"fbjs/lib/ExecutionEnvironment":334,"fbjs/lib/containsNode":337,"fbjs/lib/emptyFunction":338,"fbjs/lib/emptyObject":339,"fbjs/lib/focusNode":340,"fbjs/lib/getActiveElement":341,"fbjs/lib/shallowEqual":347,"object-assign":480,"react":522}],497:[function(require,module,exports){
  'use strict';
  
  function checkDCE() {
    /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
    if (
      typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined' ||
      typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE !== 'function'
    ) {
      return;
    }
    if ("production" !== 'production') {
      // This branch is unreachable because this function is only called
      // in production, but the condition is true only in development.
      // Therefore if the branch is still here, dead code elimination wasn't
      // properly applied.
      // Don't change the message. React DevTools relies on it. Also make sure
      // this message doesn't occur elsewhere in this function, or it will cause
      // a false positive.
      throw new Error('^_^');
    }
    try {
      // Verify that the code above has been dead code eliminated (DCE'd).
      __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(checkDCE);
    } catch (err) {
      // DevTools shouldn't crash React, no matter what.
      // We should still report in case we break this code.
      console.error(err);
    }
  }
  
  if ("production" === 'production') {
    // DCE check should happen before ReactDOM bundle executes so that
    // DevTools can report bad minification during injection.
    checkDCE();
    module.exports = require('./cjs/react-dom.production.min.js');
  } else {
    module.exports = require('./cjs/react-dom.development.js');
  }
  
  },{"./cjs/react-dom.development.js":495,"./cjs/react-dom.production.min.js":496}],498:[function(require,module,exports){
  arguments[4][483][0].apply(exports,arguments)
  },{"./lib/ReactPropTypesSecret":499,"dup":483,"fbjs/lib/invariant":344,"fbjs/lib/warning":348}],499:[function(require,module,exports){
  arguments[4][487][0].apply(exports,arguments)
  },{"dup":487}],500:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.createProvider = createProvider;
  
  var _react = require('react');
  
  var _propTypes = require('prop-types');
  
  var _propTypes2 = _interopRequireDefault(_propTypes);
  
  var _PropTypes = require('../utils/PropTypes');
  
  var _warning = require('../utils/warning');
  
  var _warning2 = _interopRequireDefault(_warning);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  
  function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
  
  function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
  
  var didWarnAboutReceivingStore = false;
  function warnAboutReceivingStore() {
    if (didWarnAboutReceivingStore) {
      return;
    }
    didWarnAboutReceivingStore = true;
  
    (0, _warning2.default)('<Provider> does not support changing `store` on the fly. ' + 'It is most likely that you see this error because you updated to ' + 'Redux 2.x and React Redux 2.x which no longer hot reload reducers ' + 'automatically. See https://github.com/reactjs/react-redux/releases/' + 'tag/v2.0.0 for the migration instructions.');
  }
  
  function createProvider() {
    var _Provider$childContex;
  
    var storeKey = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'store';
    var subKey = arguments[1];
  
    var subscriptionKey = subKey || storeKey + 'Subscription';
  
    var Provider = function (_Component) {
      _inherits(Provider, _Component);
  
      Provider.prototype.getChildContext = function getChildContext() {
        var _ref;
  
        return _ref = {}, _ref[storeKey] = this[storeKey], _ref[subscriptionKey] = null, _ref;
      };
  
      function Provider(props, context) {
        _classCallCheck(this, Provider);
  
        var _this = _possibleConstructorReturn(this, _Component.call(this, props, context));
  
        _this[storeKey] = props.store;
        return _this;
      }
  
      Provider.prototype.render = function render() {
        return _react.Children.only(this.props.children);
      };
  
      return Provider;
    }(_react.Component);
  
    if ("production" !== 'production') {
      Provider.prototype.componentWillReceiveProps = function (nextProps) {
        if (this[storeKey] !== nextProps.store) {
          warnAboutReceivingStore();
        }
      };
    }
  
    Provider.propTypes = {
      store: _PropTypes.storeShape.isRequired,
      children: _propTypes2.default.element.isRequired
    };
    Provider.childContextTypes = (_Provider$childContex = {}, _Provider$childContex[storeKey] = _PropTypes.storeShape.isRequired, _Provider$childContex[subscriptionKey] = _PropTypes.subscriptionShape, _Provider$childContex);
  
    return Provider;
  }
  
  exports.default = createProvider();
  },{"../utils/PropTypes":510,"../utils/warning":514,"prop-types":518,"react":522}],501:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  
  var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
  
  exports.default = connectAdvanced;
  
  var _hoistNonReactStatics = require('hoist-non-react-statics');
  
  var _hoistNonReactStatics2 = _interopRequireDefault(_hoistNonReactStatics);
  
  var _invariant = require('invariant');
  
  var _invariant2 = _interopRequireDefault(_invariant);
  
  var _react = require('react');
  
  var _Subscription = require('../utils/Subscription');
  
  var _Subscription2 = _interopRequireDefault(_Subscription);
  
  var _PropTypes = require('../utils/PropTypes');
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  
  function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
  
  function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
  
  function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
  
  var hotReloadingVersion = 0;
  var dummyState = {};
  function noop() {}
  function makeSelectorStateful(sourceSelector, store) {
    // wrap the selector in an object that tracks its results between runs.
    var selector = {
      run: function runComponentSelector(props) {
        try {
          var nextProps = sourceSelector(store.getState(), props);
          if (nextProps !== selector.props || selector.error) {
            selector.shouldComponentUpdate = true;
            selector.props = nextProps;
            selector.error = null;
          }
        } catch (error) {
          selector.shouldComponentUpdate = true;
          selector.error = error;
        }
      }
    };
  
    return selector;
  }
  
  function connectAdvanced(
  /*
    selectorFactory is a func that is responsible for returning the selector function used to
    compute new props from state, props, and dispatch. For example:
       export default connectAdvanced((dispatch, options) => (state, props) => ({
        thing: state.things[props.thingId],
        saveThing: fields => dispatch(actionCreators.saveThing(props.thingId, fields)),
      }))(YourComponent)
     Access to dispatch is provided to the factory so selectorFactories can bind actionCreators
    outside of their selector as an optimization. Options passed to connectAdvanced are passed to
    the selectorFactory, along with displayName and WrappedComponent, as the second argument.
     Note that selectorFactory is responsible for all caching/memoization of inbound and outbound
    props. Do not use connectAdvanced directly without memoizing results between calls to your
    selector, otherwise the Connect component will re-render on every state or props change.
  */
  selectorFactory) {
    var _contextTypes, _childContextTypes;
  
    var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
        _ref$getDisplayName = _ref.getDisplayName,
        getDisplayName = _ref$getDisplayName === undefined ? function (name) {
      return 'ConnectAdvanced(' + name + ')';
    } : _ref$getDisplayName,
        _ref$methodName = _ref.methodName,
        methodName = _ref$methodName === undefined ? 'connectAdvanced' : _ref$methodName,
        _ref$renderCountProp = _ref.renderCountProp,
        renderCountProp = _ref$renderCountProp === undefined ? undefined : _ref$renderCountProp,
        _ref$shouldHandleStat = _ref.shouldHandleStateChanges,
        shouldHandleStateChanges = _ref$shouldHandleStat === undefined ? true : _ref$shouldHandleStat,
        _ref$storeKey = _ref.storeKey,
        storeKey = _ref$storeKey === undefined ? 'store' : _ref$storeKey,
        _ref$withRef = _ref.withRef,
        withRef = _ref$withRef === undefined ? false : _ref$withRef,
        connectOptions = _objectWithoutProperties(_ref, ['getDisplayName', 'methodName', 'renderCountProp', 'shouldHandleStateChanges', 'storeKey', 'withRef']);
  
    var subscriptionKey = storeKey + 'Subscription';
    var version = hotReloadingVersion++;
  
    var contextTypes = (_contextTypes = {}, _contextTypes[storeKey] = _PropTypes.storeShape, _contextTypes[subscriptionKey] = _PropTypes.subscriptionShape, _contextTypes);
    var childContextTypes = (_childContextTypes = {}, _childContextTypes[subscriptionKey] = _PropTypes.subscriptionShape, _childContextTypes);
  
    return function wrapWithConnect(WrappedComponent) {
      (0, _invariant2.default)(typeof WrappedComponent == 'function', 'You must pass a component to the function returned by ' + (methodName + '. Instead received ' + JSON.stringify(WrappedComponent)));
  
      var wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
  
      var displayName = getDisplayName(wrappedComponentName);
  
      var selectorFactoryOptions = _extends({}, connectOptions, {
        getDisplayName: getDisplayName,
        methodName: methodName,
        renderCountProp: renderCountProp,
        shouldHandleStateChanges: shouldHandleStateChanges,
        storeKey: storeKey,
        withRef: withRef,
        displayName: displayName,
        wrappedComponentName: wrappedComponentName,
        WrappedComponent: WrappedComponent
      });
  
      var Connect = function (_Component) {
        _inherits(Connect, _Component);
  
        function Connect(props, context) {
          _classCallCheck(this, Connect);
  
          var _this = _possibleConstructorReturn(this, _Component.call(this, props, context));
  
          _this.version = version;
          _this.state = {};
          _this.renderCount = 0;
          _this.store = props[storeKey] || context[storeKey];
          _this.propsMode = Boolean(props[storeKey]);
          _this.setWrappedInstance = _this.setWrappedInstance.bind(_this);
  
          (0, _invariant2.default)(_this.store, 'Could not find "' + storeKey + '" in either the context or props of ' + ('"' + displayName + '". Either wrap the root component in a <Provider>, ') + ('or explicitly pass "' + storeKey + '" as a prop to "' + displayName + '".'));
  
          _this.initSelector();
          _this.initSubscription();
          return _this;
        }
  
        Connect.prototype.getChildContext = function getChildContext() {
          var _ref2;
  
          // If this component received store from props, its subscription should be transparent
          // to any descendants receiving store+subscription from context; it passes along
          // subscription passed to it. Otherwise, it shadows the parent subscription, which allows
          // Connect to control ordering of notifications to flow top-down.
          var subscription = this.propsMode ? null : this.subscription;
          return _ref2 = {}, _ref2[subscriptionKey] = subscription || this.context[subscriptionKey], _ref2;
        };
  
        Connect.prototype.componentDidMount = function componentDidMount() {
          if (!shouldHandleStateChanges) return;
  
          // componentWillMount fires during server side rendering, but componentDidMount and
          // componentWillUnmount do not. Because of this, trySubscribe happens during ...didMount.
          // Otherwise, unsubscription would never take place during SSR, causing a memory leak.
          // To handle the case where a child component may have triggered a state change by
          // dispatching an action in its componentWillMount, we have to re-run the select and maybe
          // re-render.
          this.subscription.trySubscribe();
          this.selector.run(this.props);
          if (this.selector.shouldComponentUpdate) this.forceUpdate();
        };
  
        Connect.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
          this.selector.run(nextProps);
        };
  
        Connect.prototype.shouldComponentUpdate = function shouldComponentUpdate() {
          return this.selector.shouldComponentUpdate;
        };
  
        Connect.prototype.componentWillUnmount = function componentWillUnmount() {
          if (this.subscription) this.subscription.tryUnsubscribe();
          this.subscription = null;
          this.notifyNestedSubs = noop;
          this.store = null;
          this.selector.run = noop;
          this.selector.shouldComponentUpdate = false;
        };
  
        Connect.prototype.getWrappedInstance = function getWrappedInstance() {
          (0, _invariant2.default)(withRef, 'To access the wrapped instance, you need to specify ' + ('{ withRef: true } in the options argument of the ' + methodName + '() call.'));
          return this.wrappedInstance;
        };
  
        Connect.prototype.setWrappedInstance = function setWrappedInstance(ref) {
          this.wrappedInstance = ref;
        };
  
        Connect.prototype.initSelector = function initSelector() {
          var sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions);
          this.selector = makeSelectorStateful(sourceSelector, this.store);
          this.selector.run(this.props);
        };
  
        Connect.prototype.initSubscription = function initSubscription() {
          if (!shouldHandleStateChanges) return;
  
          // parentSub's source should match where store came from: props vs. context. A component
          // connected to the store via props shouldn't use subscription from context, or vice versa.
          var parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey];
          this.subscription = new _Subscription2.default(this.store, parentSub, this.onStateChange.bind(this));
  
          // `notifyNestedSubs` is duplicated to handle the case where the component is  unmounted in
          // the middle of the notification loop, where `this.subscription` will then be null. An
          // extra null check every change can be avoided by copying the method onto `this` and then
          // replacing it with a no-op on unmount. This can probably be avoided if Subscription's
          // listeners logic is changed to not call listeners that have been unsubscribed in the
          // middle of the notification loop.
          this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription);
        };
  
        Connect.prototype.onStateChange = function onStateChange() {
          this.selector.run(this.props);
  
          if (!this.selector.shouldComponentUpdate) {
            this.notifyNestedSubs();
          } else {
            this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate;
            this.setState(dummyState);
          }
        };
  
        Connect.prototype.notifyNestedSubsOnComponentDidUpdate = function notifyNestedSubsOnComponentDidUpdate() {
          // `componentDidUpdate` is conditionally implemented when `onStateChange` determines it
          // needs to notify nested subs. Once called, it unimplements itself until further state
          // changes occur. Doing it this way vs having a permanent `componentDidUpdate` that does
          // a boolean check every time avoids an extra method call most of the time, resulting
          // in some perf boost.
          this.componentDidUpdate = undefined;
          this.notifyNestedSubs();
        };
  
        Connect.prototype.isSubscribed = function isSubscribed() {
          return Boolean(this.subscription) && this.subscription.isSubscribed();
        };
  
        Connect.prototype.addExtraProps = function addExtraProps(props) {
          if (!withRef && !renderCountProp && !(this.propsMode && this.subscription)) return props;
          // make a shallow copy so that fields added don't leak to the original selector.
          // this is especially important for 'ref' since that's a reference back to the component
          // instance. a singleton memoized selector would then be holding a reference to the
          // instance, preventing the instance from being garbage collected, and that would be bad
          var withExtras = _extends({}, props);
          if (withRef) withExtras.ref = this.setWrappedInstance;
          if (renderCountProp) withExtras[renderCountProp] = this.renderCount++;
          if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription;
          return withExtras;
        };
  
        Connect.prototype.render = function render() {
          var selector = this.selector;
          selector.shouldComponentUpdate = false;
  
          if (selector.error) {
            throw selector.error;
          } else {
            return (0, _react.createElement)(WrappedComponent, this.addExtraProps(selector.props));
          }
        };
  
        return Connect;
      }(_react.Component);
  
      Connect.WrappedComponent = WrappedComponent;
      Connect.displayName = displayName;
      Connect.childContextTypes = childContextTypes;
      Connect.contextTypes = contextTypes;
      Connect.propTypes = contextTypes;
  
      if ("production" !== 'production') {
        Connect.prototype.componentWillUpdate = function componentWillUpdate() {
          var _this2 = this;
  
          // We are hot reloading!
          if (this.version !== version) {
            this.version = version;
            this.initSelector();
  
            // If any connected descendants don't hot reload (and resubscribe in the process), their
            // listeners will be lost when we unsubscribe. Unfortunately, by copying over all
            // listeners, this does mean that the old versions of connected descendants will still be
            // notified of state changes; however, their onStateChange function is a no-op so this
            // isn't a huge deal.
            var oldListeners = [];
  
            if (this.subscription) {
              oldListeners = this.subscription.listeners.get();
              this.subscription.tryUnsubscribe();
            }
            this.initSubscription();
            if (shouldHandleStateChanges) {
              this.subscription.trySubscribe();
              oldListeners.forEach(function (listener) {
                return _this2.subscription.listeners.subscribe(listener);
              });
            }
          }
        };
      }
  
      return (0, _hoistNonReactStatics2.default)(Connect, WrappedComponent);
    };
  }
  },{"../utils/PropTypes":510,"../utils/Subscription":511,"hoist-non-react-statics":369,"invariant":371,"react":522}],502:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  
  var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
  
  exports.createConnect = createConnect;
  
  var _connectAdvanced = require('../components/connectAdvanced');
  
  var _connectAdvanced2 = _interopRequireDefault(_connectAdvanced);
  
  var _shallowEqual = require('../utils/shallowEqual');
  
  var _shallowEqual2 = _interopRequireDefault(_shallowEqual);
  
  var _mapDispatchToProps = require('./mapDispatchToProps');
  
  var _mapDispatchToProps2 = _interopRequireDefault(_mapDispatchToProps);
  
  var _mapStateToProps = require('./mapStateToProps');
  
  var _mapStateToProps2 = _interopRequireDefault(_mapStateToProps);
  
  var _mergeProps = require('./mergeProps');
  
  var _mergeProps2 = _interopRequireDefault(_mergeProps);
  
  var _selectorFactory = require('./selectorFactory');
  
  var _selectorFactory2 = _interopRequireDefault(_selectorFactory);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
  
  /*
    connect is a facade over connectAdvanced. It turns its args into a compatible
    selectorFactory, which has the signature:
  
      (dispatch, options) => (nextState, nextOwnProps) => nextFinalProps
    
    connect passes its args to connectAdvanced as options, which will in turn pass them to
    selectorFactory each time a Connect component instance is instantiated or hot reloaded.
  
    selectorFactory returns a final props selector from its mapStateToProps,
    mapStateToPropsFactories, mapDispatchToProps, mapDispatchToPropsFactories, mergeProps,
    mergePropsFactories, and pure args.
  
    The resulting final props selector is called by the Connect component instance whenever
    it receives new props or store state.
   */
  
  function match(arg, factories, name) {
    for (var i = factories.length - 1; i >= 0; i--) {
      var result = factories[i](arg);
      if (result) return result;
    }
  
    return function (dispatch, options) {
      throw new Error('Invalid value of type ' + typeof arg + ' for ' + name + ' argument when connecting component ' + options.wrappedComponentName + '.');
    };
  }
  
  function strictEqual(a, b) {
    return a === b;
  }
  
  // createConnect with default args builds the 'official' connect behavior. Calling it with
  // different options opens up some testing and extensibility scenarios
  function createConnect() {
    var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
        _ref$connectHOC = _ref.connectHOC,
        connectHOC = _ref$connectHOC === undefined ? _connectAdvanced2.default : _ref$connectHOC,
        _ref$mapStateToPropsF = _ref.mapStateToPropsFactories,
        mapStateToPropsFactories = _ref$mapStateToPropsF === undefined ? _mapStateToProps2.default : _ref$mapStateToPropsF,
        _ref$mapDispatchToPro = _ref.mapDispatchToPropsFactories,
        mapDispatchToPropsFactories = _ref$mapDispatchToPro === undefined ? _mapDispatchToProps2.default : _ref$mapDispatchToPro,
        _ref$mergePropsFactor = _ref.mergePropsFactories,
        mergePropsFactories = _ref$mergePropsFactor === undefined ? _mergeProps2.default : _ref$mergePropsFactor,
        _ref$selectorFactory = _ref.selectorFactory,
        selectorFactory = _ref$selectorFactory === undefined ? _selectorFactory2.default : _ref$selectorFactory;
  
    return function connect(mapStateToProps, mapDispatchToProps, mergeProps) {
      var _ref2 = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {},
          _ref2$pure = _ref2.pure,
          pure = _ref2$pure === undefined ? true : _ref2$pure,
          _ref2$areStatesEqual = _ref2.areStatesEqual,
          areStatesEqual = _ref2$areStatesEqual === undefined ? strictEqual : _ref2$areStatesEqual,
          _ref2$areOwnPropsEqua = _ref2.areOwnPropsEqual,
          areOwnPropsEqual = _ref2$areOwnPropsEqua === undefined ? _shallowEqual2.default : _ref2$areOwnPropsEqua,
          _ref2$areStatePropsEq = _ref2.areStatePropsEqual,
          areStatePropsEqual = _ref2$areStatePropsEq === undefined ? _shallowEqual2.default : _ref2$areStatePropsEq,
          _ref2$areMergedPropsE = _ref2.areMergedPropsEqual,
          areMergedPropsEqual = _ref2$areMergedPropsE === undefined ? _shallowEqual2.default : _ref2$areMergedPropsE,
          extraOptions = _objectWithoutProperties(_ref2, ['pure', 'areStatesEqual', 'areOwnPropsEqual', 'areStatePropsEqual', 'areMergedPropsEqual']);
  
      var initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps');
      var initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps');
      var initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps');
  
      return connectHOC(selectorFactory, _extends({
        // used in error messages
        methodName: 'connect',
  
        // used to compute Connect's displayName from the wrapped component's displayName.
        getDisplayName: function getDisplayName(name) {
          return 'Connect(' + name + ')';
        },
  
        // if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes
        shouldHandleStateChanges: Boolean(mapStateToProps),
  
        // passed through to selectorFactory
        initMapStateToProps: initMapStateToProps,
        initMapDispatchToProps: initMapDispatchToProps,
        initMergeProps: initMergeProps,
        pure: pure,
        areStatesEqual: areStatesEqual,
        areOwnPropsEqual: areOwnPropsEqual,
        areStatePropsEqual: areStatePropsEqual,
        areMergedPropsEqual: areMergedPropsEqual
  
      }, extraOptions));
    };
  }
  
  exports.default = createConnect();
  },{"../components/connectAdvanced":501,"../utils/shallowEqual":512,"./mapDispatchToProps":503,"./mapStateToProps":504,"./mergeProps":505,"./selectorFactory":506}],503:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.whenMapDispatchToPropsIsFunction = whenMapDispatchToPropsIsFunction;
  exports.whenMapDispatchToPropsIsMissing = whenMapDispatchToPropsIsMissing;
  exports.whenMapDispatchToPropsIsObject = whenMapDispatchToPropsIsObject;
  
  var _redux = require('redux');
  
  var _wrapMapToProps = require('./wrapMapToProps');
  
  function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {
    return typeof mapDispatchToProps === 'function' ? (0, _wrapMapToProps.wrapMapToPropsFunc)(mapDispatchToProps, 'mapDispatchToProps') : undefined;
  }
  
  function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
    return !mapDispatchToProps ? (0, _wrapMapToProps.wrapMapToPropsConstant)(function (dispatch) {
      return { dispatch: dispatch };
    }) : undefined;
  }
  
  function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
    return mapDispatchToProps && typeof mapDispatchToProps === 'object' ? (0, _wrapMapToProps.wrapMapToPropsConstant)(function (dispatch) {
      return (0, _redux.bindActionCreators)(mapDispatchToProps, dispatch);
    }) : undefined;
  }
  
  exports.default = [whenMapDispatchToPropsIsFunction, whenMapDispatchToPropsIsMissing, whenMapDispatchToPropsIsObject];
  },{"./wrapMapToProps":508,"redux":531}],504:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.whenMapStateToPropsIsFunction = whenMapStateToPropsIsFunction;
  exports.whenMapStateToPropsIsMissing = whenMapStateToPropsIsMissing;
  
  var _wrapMapToProps = require('./wrapMapToProps');
  
  function whenMapStateToPropsIsFunction(mapStateToProps) {
    return typeof mapStateToProps === 'function' ? (0, _wrapMapToProps.wrapMapToPropsFunc)(mapStateToProps, 'mapStateToProps') : undefined;
  }
  
  function whenMapStateToPropsIsMissing(mapStateToProps) {
    return !mapStateToProps ? (0, _wrapMapToProps.wrapMapToPropsConstant)(function () {
      return {};
    }) : undefined;
  }
  
  exports.default = [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing];
  },{"./wrapMapToProps":508}],505:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  
  var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
  
  exports.defaultMergeProps = defaultMergeProps;
  exports.wrapMergePropsFunc = wrapMergePropsFunc;
  exports.whenMergePropsIsFunction = whenMergePropsIsFunction;
  exports.whenMergePropsIsOmitted = whenMergePropsIsOmitted;
  
  var _verifyPlainObject = require('../utils/verifyPlainObject');
  
  var _verifyPlainObject2 = _interopRequireDefault(_verifyPlainObject);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function defaultMergeProps(stateProps, dispatchProps, ownProps) {
    return _extends({}, ownProps, stateProps, dispatchProps);
  }
  
  function wrapMergePropsFunc(mergeProps) {
    return function initMergePropsProxy(dispatch, _ref) {
      var displayName = _ref.displayName,
          pure = _ref.pure,
          areMergedPropsEqual = _ref.areMergedPropsEqual;
  
      var hasRunOnce = false;
      var mergedProps = void 0;
  
      return function mergePropsProxy(stateProps, dispatchProps, ownProps) {
        var nextMergedProps = mergeProps(stateProps, dispatchProps, ownProps);
  
        if (hasRunOnce) {
          if (!pure || !areMergedPropsEqual(nextMergedProps, mergedProps)) mergedProps = nextMergedProps;
        } else {
          hasRunOnce = true;
          mergedProps = nextMergedProps;
  
          if ("production" !== 'production') (0, _verifyPlainObject2.default)(mergedProps, displayName, 'mergeProps');
        }
  
        return mergedProps;
      };
    };
  }
  
  function whenMergePropsIsFunction(mergeProps) {
    return typeof mergeProps === 'function' ? wrapMergePropsFunc(mergeProps) : undefined;
  }
  
  function whenMergePropsIsOmitted(mergeProps) {
    return !mergeProps ? function () {
      return defaultMergeProps;
    } : undefined;
  }
  
  exports.default = [whenMergePropsIsFunction, whenMergePropsIsOmitted];
  },{"../utils/verifyPlainObject":513}],506:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.impureFinalPropsSelectorFactory = impureFinalPropsSelectorFactory;
  exports.pureFinalPropsSelectorFactory = pureFinalPropsSelectorFactory;
  exports.default = finalPropsSelectorFactory;
  
  var _verifySubselectors = require('./verifySubselectors');
  
  var _verifySubselectors2 = _interopRequireDefault(_verifySubselectors);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
  
  function impureFinalPropsSelectorFactory(mapStateToProps, mapDispatchToProps, mergeProps, dispatch) {
    return function impureFinalPropsSelector(state, ownProps) {
      return mergeProps(mapStateToProps(state, ownProps), mapDispatchToProps(dispatch, ownProps), ownProps);
    };
  }
  
  function pureFinalPropsSelectorFactory(mapStateToProps, mapDispatchToProps, mergeProps, dispatch, _ref) {
    var areStatesEqual = _ref.areStatesEqual,
        areOwnPropsEqual = _ref.areOwnPropsEqual,
        areStatePropsEqual = _ref.areStatePropsEqual;
  
    var hasRunAtLeastOnce = false;
    var state = void 0;
    var ownProps = void 0;
    var stateProps = void 0;
    var dispatchProps = void 0;
    var mergedProps = void 0;
  
    function handleFirstCall(firstState, firstOwnProps) {
      state = firstState;
      ownProps = firstOwnProps;
      stateProps = mapStateToProps(state, ownProps);
      dispatchProps = mapDispatchToProps(dispatch, ownProps);
      mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
      hasRunAtLeastOnce = true;
      return mergedProps;
    }
  
    function handleNewPropsAndNewState() {
      stateProps = mapStateToProps(state, ownProps);
  
      if (mapDispatchToProps.dependsOnOwnProps) dispatchProps = mapDispatchToProps(dispatch, ownProps);
  
      mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
      return mergedProps;
    }
  
    function handleNewProps() {
      if (mapStateToProps.dependsOnOwnProps) stateProps = mapStateToProps(state, ownProps);
  
      if (mapDispatchToProps.dependsOnOwnProps) dispatchProps = mapDispatchToProps(dispatch, ownProps);
  
      mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
      return mergedProps;
    }
  
    function handleNewState() {
      var nextStateProps = mapStateToProps(state, ownProps);
      var statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps);
      stateProps = nextStateProps;
  
      if (statePropsChanged) mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
  
      return mergedProps;
    }
  
    function handleSubsequentCalls(nextState, nextOwnProps) {
      var propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps);
      var stateChanged = !areStatesEqual(nextState, state);
      state = nextState;
      ownProps = nextOwnProps;
  
      if (propsChanged && stateChanged) return handleNewPropsAndNewState();
      if (propsChanged) return handleNewProps();
      if (stateChanged) return handleNewState();
      return mergedProps;
    }
  
    return function pureFinalPropsSelector(nextState, nextOwnProps) {
      return hasRunAtLeastOnce ? handleSubsequentCalls(nextState, nextOwnProps) : handleFirstCall(nextState, nextOwnProps);
    };
  }
  
  // TODO: Add more comments
  
  // If pure is true, the selector returned by selectorFactory will memoize its results,
  // allowing connectAdvanced's shouldComponentUpdate to return false if final
  // props have not changed. If false, the selector will always return a new
  // object and shouldComponentUpdate will always return true.
  
  function finalPropsSelectorFactory(dispatch, _ref2) {
    var initMapStateToProps = _ref2.initMapStateToProps,
        initMapDispatchToProps = _ref2.initMapDispatchToProps,
        initMergeProps = _ref2.initMergeProps,
        options = _objectWithoutProperties(_ref2, ['initMapStateToProps', 'initMapDispatchToProps', 'initMergeProps']);
  
    var mapStateToProps = initMapStateToProps(dispatch, options);
    var mapDispatchToProps = initMapDispatchToProps(dispatch, options);
    var mergeProps = initMergeProps(dispatch, options);
  
    if ("production" !== 'production') {
      (0, _verifySubselectors2.default)(mapStateToProps, mapDispatchToProps, mergeProps, options.displayName);
    }
  
    var selectorFactory = options.pure ? pureFinalPropsSelectorFactory : impureFinalPropsSelectorFactory;
  
    return selectorFactory(mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options);
  }
  },{"./verifySubselectors":507}],507:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.default = verifySubselectors;
  
  var _warning = require('../utils/warning');
  
  var _warning2 = _interopRequireDefault(_warning);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function verify(selector, methodName, displayName) {
    if (!selector) {
      throw new Error('Unexpected value for ' + methodName + ' in ' + displayName + '.');
    } else if (methodName === 'mapStateToProps' || methodName === 'mapDispatchToProps') {
      if (!selector.hasOwnProperty('dependsOnOwnProps')) {
        (0, _warning2.default)('The selector for ' + methodName + ' of ' + displayName + ' did not specify a value for dependsOnOwnProps.');
      }
    }
  }
  
  function verifySubselectors(mapStateToProps, mapDispatchToProps, mergeProps, displayName) {
    verify(mapStateToProps, 'mapStateToProps', displayName);
    verify(mapDispatchToProps, 'mapDispatchToProps', displayName);
    verify(mergeProps, 'mergeProps', displayName);
  }
  },{"../utils/warning":514}],508:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.wrapMapToPropsConstant = wrapMapToPropsConstant;
  exports.getDependsOnOwnProps = getDependsOnOwnProps;
  exports.wrapMapToPropsFunc = wrapMapToPropsFunc;
  
  var _verifyPlainObject = require('../utils/verifyPlainObject');
  
  var _verifyPlainObject2 = _interopRequireDefault(_verifyPlainObject);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function wrapMapToPropsConstant(getConstant) {
    return function initConstantSelector(dispatch, options) {
      var constant = getConstant(dispatch, options);
  
      function constantSelector() {
        return constant;
      }
      constantSelector.dependsOnOwnProps = false;
      return constantSelector;
    };
  }
  
  // dependsOnOwnProps is used by createMapToPropsProxy to determine whether to pass props as args
  // to the mapToProps function being wrapped. It is also used by makePurePropsSelector to determine
  // whether mapToProps needs to be invoked when props have changed.
  // 
  // A length of one signals that mapToProps does not depend on props from the parent component.
  // A length of zero is assumed to mean mapToProps is getting args via arguments or ...args and
  // therefore not reporting its length accurately..
  function getDependsOnOwnProps(mapToProps) {
    return mapToProps.dependsOnOwnProps !== null && mapToProps.dependsOnOwnProps !== undefined ? Boolean(mapToProps.dependsOnOwnProps) : mapToProps.length !== 1;
  }
  
  // Used by whenMapStateToPropsIsFunction and whenMapDispatchToPropsIsFunction,
  // this function wraps mapToProps in a proxy function which does several things:
  // 
  //  * Detects whether the mapToProps function being called depends on props, which
  //    is used by selectorFactory to decide if it should reinvoke on props changes.
  //    
  //  * On first call, handles mapToProps if returns another function, and treats that
  //    new function as the true mapToProps for subsequent calls.
  //    
  //  * On first call, verifies the first result is a plain object, in order to warn
  //    the developer that their mapToProps function is not returning a valid result.
  //    
  function wrapMapToPropsFunc(mapToProps, methodName) {
    return function initProxySelector(dispatch, _ref) {
      var displayName = _ref.displayName;
  
      var proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
        return proxy.dependsOnOwnProps ? proxy.mapToProps(stateOrDispatch, ownProps) : proxy.mapToProps(stateOrDispatch);
      };
  
      // allow detectFactoryAndVerify to get ownProps
      proxy.dependsOnOwnProps = true;
  
      proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) {
        proxy.mapToProps = mapToProps;
        proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps);
        var props = proxy(stateOrDispatch, ownProps);
  
        if (typeof props === 'function') {
          proxy.mapToProps = props;
          proxy.dependsOnOwnProps = getDependsOnOwnProps(props);
          props = proxy(stateOrDispatch, ownProps);
        }
  
        if ("production" !== 'production') (0, _verifyPlainObject2.default)(props, displayName, methodName);
  
        return props;
      };
  
      return proxy;
    };
  }
  },{"../utils/verifyPlainObject":513}],509:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.connect = exports.connectAdvanced = exports.createProvider = exports.Provider = undefined;
  
  var _Provider = require('./components/Provider');
  
  var _Provider2 = _interopRequireDefault(_Provider);
  
  var _connectAdvanced = require('./components/connectAdvanced');
  
  var _connectAdvanced2 = _interopRequireDefault(_connectAdvanced);
  
  var _connect = require('./connect/connect');
  
  var _connect2 = _interopRequireDefault(_connect);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  exports.Provider = _Provider2.default;
  exports.createProvider = _Provider.createProvider;
  exports.connectAdvanced = _connectAdvanced2.default;
  exports.connect = _connect2.default;
  },{"./components/Provider":500,"./components/connectAdvanced":501,"./connect/connect":502}],510:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.storeShape = exports.subscriptionShape = undefined;
  
  var _propTypes = require('prop-types');
  
  var _propTypes2 = _interopRequireDefault(_propTypes);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  var subscriptionShape = exports.subscriptionShape = _propTypes2.default.shape({
    trySubscribe: _propTypes2.default.func.isRequired,
    tryUnsubscribe: _propTypes2.default.func.isRequired,
    notifyNestedSubs: _propTypes2.default.func.isRequired,
    isSubscribed: _propTypes2.default.func.isRequired
  });
  
  var storeShape = exports.storeShape = _propTypes2.default.shape({
    subscribe: _propTypes2.default.func.isRequired,
    dispatch: _propTypes2.default.func.isRequired,
    getState: _propTypes2.default.func.isRequired
  });
  },{"prop-types":518}],511:[function(require,module,exports){
  "use strict";
  
  exports.__esModule = true;
  
  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  
  // encapsulates the subscription logic for connecting a component to the redux store, as
  // well as nesting subscriptions of descendant components, so that we can ensure the
  // ancestor components re-render before descendants
  
  var CLEARED = null;
  var nullListeners = {
    notify: function notify() {}
  };
  
  function createListenerCollection() {
    // the current/next pattern is copied from redux's createStore code.
    // TODO: refactor+expose that code to be reusable here?
    var current = [];
    var next = [];
  
    return {
      clear: function clear() {
        next = CLEARED;
        current = CLEARED;
      },
      notify: function notify() {
        var listeners = current = next;
        for (var i = 0; i < listeners.length; i++) {
          listeners[i]();
        }
      },
      get: function get() {
        return next;
      },
      subscribe: function subscribe(listener) {
        var isSubscribed = true;
        if (next === current) next = current.slice();
        next.push(listener);
  
        return function unsubscribe() {
          if (!isSubscribed || current === CLEARED) return;
          isSubscribed = false;
  
          if (next === current) next = current.slice();
          next.splice(next.indexOf(listener), 1);
        };
      }
    };
  }
  
  var Subscription = function () {
    function Subscription(store, parentSub, onStateChange) {
      _classCallCheck(this, Subscription);
  
      this.store = store;
      this.parentSub = parentSub;
      this.onStateChange = onStateChange;
      this.unsubscribe = null;
      this.listeners = nullListeners;
    }
  
    Subscription.prototype.addNestedSub = function addNestedSub(listener) {
      this.trySubscribe();
      return this.listeners.subscribe(listener);
    };
  
    Subscription.prototype.notifyNestedSubs = function notifyNestedSubs() {
      this.listeners.notify();
    };
  
    Subscription.prototype.isSubscribed = function isSubscribed() {
      return Boolean(this.unsubscribe);
    };
  
    Subscription.prototype.trySubscribe = function trySubscribe() {
      if (!this.unsubscribe) {
        this.unsubscribe = this.parentSub ? this.parentSub.addNestedSub(this.onStateChange) : this.store.subscribe(this.onStateChange);
  
        this.listeners = createListenerCollection();
      }
    };
  
    Subscription.prototype.tryUnsubscribe = function tryUnsubscribe() {
      if (this.unsubscribe) {
        this.unsubscribe();
        this.unsubscribe = null;
        this.listeners.clear();
        this.listeners = nullListeners;
      }
    };
  
    return Subscription;
  }();
  
  exports.default = Subscription;
  },{}],512:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.default = shallowEqual;
  var hasOwn = Object.prototype.hasOwnProperty;
  
  function is(x, y) {
    if (x === y) {
      return x !== 0 || y !== 0 || 1 / x === 1 / y;
    } else {
      return x !== x && y !== y;
    }
  }
  
  function shallowEqual(objA, objB) {
    if (is(objA, objB)) return true;
  
    if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
      return false;
    }
  
    var keysA = Object.keys(objA);
    var keysB = Object.keys(objB);
  
    if (keysA.length !== keysB.length) return false;
  
    for (var i = 0; i < keysA.length; i++) {
      if (!hasOwn.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
        return false;
      }
    }
  
    return true;
  }
  },{}],513:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.default = verifyPlainObject;
  
  var _isPlainObject = require('lodash/isPlainObject');
  
  var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
  
  var _warning = require('./warning');
  
  var _warning2 = _interopRequireDefault(_warning);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function verifyPlainObject(value, displayName, methodName) {
    if (!(0, _isPlainObject2.default)(value)) {
      (0, _warning2.default)(methodName + '() in ' + displayName + ' must return a plain object. Instead received ' + value + '.');
    }
  }
  },{"./warning":514,"lodash/isPlainObject":427}],514:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.default = warning;
  /**
   * Prints a warning in the console if it exists.
   *
   * @param {String} message The warning message.
   * @returns {void}
   */
  function warning(message) {
    /* eslint-disable no-console */
    if (typeof console !== 'undefined' && typeof console.error === 'function') {
      console.error(message);
    }
    /* eslint-enable no-console */
    try {
      // This error was thrown as a convenience so that if you enable
      // "break on all exceptions" in your console,
      // it would pause the execution at this line.
      throw new Error(message);
      /* eslint-disable no-empty */
    } catch (e) {}
    /* eslint-enable no-empty */
  }
  },{}],515:[function(require,module,exports){
  arguments[4][483][0].apply(exports,arguments)
  },{"./lib/ReactPropTypesSecret":519,"dup":483,"fbjs/lib/invariant":344,"fbjs/lib/warning":348}],516:[function(require,module,exports){
  arguments[4][484][0].apply(exports,arguments)
  },{"./lib/ReactPropTypesSecret":519,"dup":484,"fbjs/lib/emptyFunction":338,"fbjs/lib/invariant":344}],517:[function(require,module,exports){
  arguments[4][485][0].apply(exports,arguments)
  },{"./checkPropTypes":515,"./lib/ReactPropTypesSecret":519,"dup":485,"fbjs/lib/emptyFunction":338,"fbjs/lib/invariant":344,"fbjs/lib/warning":348,"object-assign":480}],518:[function(require,module,exports){
  arguments[4][486][0].apply(exports,arguments)
  },{"./factoryWithThrowingShims":516,"./factoryWithTypeCheckers":517,"dup":486}],519:[function(require,module,exports){
  arguments[4][487][0].apply(exports,arguments)
  },{"dup":487}],520:[function(require,module,exports){
  /** @license React v16.2.0
   * react.development.js
   *
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   */
  
  'use strict';
  
  
  
  if ("production" !== "production") {
    (function() {
  'use strict';
  
  var _assign = require('object-assign');
  var emptyObject = require('fbjs/lib/emptyObject');
  var invariant = require('fbjs/lib/invariant');
  var warning = require('fbjs/lib/warning');
  var emptyFunction = require('fbjs/lib/emptyFunction');
  var checkPropTypes = require('prop-types/checkPropTypes');
  
  // TODO: this is special because it gets imported during build.
  
  var ReactVersion = '16.2.0';
  
  // The Symbol used to tag the ReactElement-like types. If there is no native Symbol
  // nor polyfill, then a plain number is used for performance.
  var hasSymbol = typeof Symbol === 'function' && Symbol['for'];
  
  var REACT_ELEMENT_TYPE = hasSymbol ? Symbol['for']('react.element') : 0xeac7;
  var REACT_CALL_TYPE = hasSymbol ? Symbol['for']('react.call') : 0xeac8;
  var REACT_RETURN_TYPE = hasSymbol ? Symbol['for']('react.return') : 0xeac9;
  var REACT_PORTAL_TYPE = hasSymbol ? Symbol['for']('react.portal') : 0xeaca;
  var REACT_FRAGMENT_TYPE = hasSymbol ? Symbol['for']('react.fragment') : 0xeacb;
  
  var MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
  var FAUX_ITERATOR_SYMBOL = '@@iterator';
  
  function getIteratorFn(maybeIterable) {
    if (maybeIterable === null || typeof maybeIterable === 'undefined') {
      return null;
    }
    var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
    if (typeof maybeIterator === 'function') {
      return maybeIterator;
    }
    return null;
  }
  
  /**
   * WARNING: DO NOT manually require this module.
   * This is a replacement for `invariant(...)` used by the error code system
   * and will _only_ be required by the corresponding babel pass.
   * It always throws.
   */
  
  /**
   * Forked from fbjs/warning:
   * https://github.com/facebook/fbjs/blob/e66ba20ad5be433eb54423f2b097d829324d9de6/packages/fbjs/src/__forks__/warning.js
   *
   * Only change is we use console.warn instead of console.error,
   * and do nothing when 'console' is not supported.
   * This really simplifies the code.
   * ---
   * Similar to invariant but only logs a warning if the condition is not met.
   * This can be used to log issues in development environments in critical
   * paths. Removing the logging code for production environments will keep the
   * same logic and follow the same code paths.
   */
  
  var lowPriorityWarning = function () {};
  
  {
    var printWarning = function (format) {
      for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
        args[_key - 1] = arguments[_key];
      }
  
      var argIndex = 0;
      var message = 'Warning: ' + format.replace(/%s/g, function () {
        return args[argIndex++];
      });
      if (typeof console !== 'undefined') {
        console.warn(message);
      }
      try {
        // --- Welcome to debugging React ---
        // This error was thrown as a convenience so that you can use this stack
        // to find the callsite that caused this warning to fire.
        throw new Error(message);
      } catch (x) {}
    };
  
    lowPriorityWarning = function (condition, format) {
      if (format === undefined) {
        throw new Error('`warning(condition, format, ...args)` requires a warning ' + 'message argument');
      }
      if (!condition) {
        for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
          args[_key2 - 2] = arguments[_key2];
        }
  
        printWarning.apply(undefined, [format].concat(args));
      }
    };
  }
  
  var lowPriorityWarning$1 = lowPriorityWarning;
  
  var didWarnStateUpdateForUnmountedComponent = {};
  
  function warnNoop(publicInstance, callerName) {
    {
      var constructor = publicInstance.constructor;
      var componentName = constructor && (constructor.displayName || constructor.name) || 'ReactClass';
      var warningKey = componentName + '.' + callerName;
      if (didWarnStateUpdateForUnmountedComponent[warningKey]) {
        return;
      }
      warning(false, '%s(...): Can only update a mounted or mounting component. ' + 'This usually means you called %s() on an unmounted component. ' + 'This is a no-op.\n\nPlease check the code for the %s component.', callerName, callerName, componentName);
      didWarnStateUpdateForUnmountedComponent[warningKey] = true;
    }
  }
  
  /**
   * This is the abstract API for an update queue.
   */
  var ReactNoopUpdateQueue = {
    /**
     * Checks whether or not this composite component is mounted.
     * @param {ReactClass} publicInstance The instance we want to test.
     * @return {boolean} True if mounted, false otherwise.
     * @protected
     * @final
     */
    isMounted: function (publicInstance) {
      return false;
    },
  
    /**
     * Forces an update. This should only be invoked when it is known with
     * certainty that we are **not** in a DOM transaction.
     *
     * You may want to call this when you know that some deeper aspect of the
     * component's state has changed but `setState` was not called.
     *
     * This will not invoke `shouldComponentUpdate`, but it will invoke
     * `componentWillUpdate` and `componentDidUpdate`.
     *
     * @param {ReactClass} publicInstance The instance that should rerender.
     * @param {?function} callback Called after component is updated.
     * @param {?string} callerName name of the calling function in the public API.
     * @internal
     */
    enqueueForceUpdate: function (publicInstance, callback, callerName) {
      warnNoop(publicInstance, 'forceUpdate');
    },
  
    /**
     * Replaces all of the state. Always use this or `setState` to mutate state.
     * You should treat `this.state` as immutable.
     *
     * There is no guarantee that `this.state` will be immediately updated, so
     * accessing `this.state` after calling this method may return the old value.
     *
     * @param {ReactClass} publicInstance The instance that should rerender.
     * @param {object} completeState Next state.
     * @param {?function} callback Called after component is updated.
     * @param {?string} callerName name of the calling function in the public API.
     * @internal
     */
    enqueueReplaceState: function (publicInstance, completeState, callback, callerName) {
      warnNoop(publicInstance, 'replaceState');
    },
  
    /**
     * Sets a subset of the state. This only exists because _pendingState is
     * internal. This provides a merging strategy that is not available to deep
     * properties which is confusing. TODO: Expose pendingState or don't use it
     * during the merge.
     *
     * @param {ReactClass} publicInstance The instance that should rerender.
     * @param {object} partialState Next partial state to be merged with state.
     * @param {?function} callback Called after component is updated.
     * @param {?string} Name of the calling function in the public API.
     * @internal
     */
    enqueueSetState: function (publicInstance, partialState, callback, callerName) {
      warnNoop(publicInstance, 'setState');
    }
  };
  
  /**
   * Base class helpers for the updating state of a component.
   */
  function Component(props, context, updater) {
    this.props = props;
    this.context = context;
    this.refs = emptyObject;
    // We initialize the default updater but the real one gets injected by the
    // renderer.
    this.updater = updater || ReactNoopUpdateQueue;
  }
  
  Component.prototype.isReactComponent = {};
  
  /**
   * Sets a subset of the state. Always use this to mutate
   * state. You should treat `this.state` as immutable.
   *
   * There is no guarantee that `this.state` will be immediately updated, so
   * accessing `this.state` after calling this method may return the old value.
   *
   * There is no guarantee that calls to `setState` will run synchronously,
   * as they may eventually be batched together.  You can provide an optional
   * callback that will be executed when the call to setState is actually
   * completed.
   *
   * When a function is provided to setState, it will be called at some point in
   * the future (not synchronously). It will be called with the up to date
   * component arguments (state, props, context). These values can be different
   * from this.* because your function may be called after receiveProps but before
   * shouldComponentUpdate, and this new state, props, and context will not yet be
   * assigned to this.
   *
   * @param {object|function} partialState Next partial state or function to
   *        produce next partial state to be merged with current state.
   * @param {?function} callback Called after state is updated.
   * @final
   * @protected
   */
  Component.prototype.setState = function (partialState, callback) {
    !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : void 0;
    this.updater.enqueueSetState(this, partialState, callback, 'setState');
  };
  
  /**
   * Forces an update. This should only be invoked when it is known with
   * certainty that we are **not** in a DOM transaction.
   *
   * You may want to call this when you know that some deeper aspect of the
   * component's state has changed but `setState` was not called.
   *
   * This will not invoke `shouldComponentUpdate`, but it will invoke
   * `componentWillUpdate` and `componentDidUpdate`.
   *
   * @param {?function} callback Called after update is complete.
   * @final
   * @protected
   */
  Component.prototype.forceUpdate = function (callback) {
    this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
  };
  
  /**
   * Deprecated APIs. These APIs used to exist on classic React classes but since
   * we would like to deprecate them, we're not going to move them over to this
   * modern base class. Instead, we define a getter that warns if it's accessed.
   */
  {
    var deprecatedAPIs = {
      isMounted: ['isMounted', 'Instead, make sure to clean up subscriptions and pending requests in ' + 'componentWillUnmount to prevent memory leaks.'],
      replaceState: ['replaceState', 'Refactor your code to use setState instead (see ' + 'https://github.com/facebook/react/issues/3236).']
    };
    var defineDeprecationWarning = function (methodName, info) {
      Object.defineProperty(Component.prototype, methodName, {
        get: function () {
          lowPriorityWarning$1(false, '%s(...) is deprecated in plain JavaScript React classes. %s', info[0], info[1]);
          return undefined;
        }
      });
    };
    for (var fnName in deprecatedAPIs) {
      if (deprecatedAPIs.hasOwnProperty(fnName)) {
        defineDeprecationWarning(fnName, deprecatedAPIs[fnName]);
      }
    }
  }
  
  /**
   * Base class helpers for the updating state of a component.
   */
  function PureComponent(props, context, updater) {
    // Duplicated from Component.
    this.props = props;
    this.context = context;
    this.refs = emptyObject;
    // We initialize the default updater but the real one gets injected by the
    // renderer.
    this.updater = updater || ReactNoopUpdateQueue;
  }
  
  function ComponentDummy() {}
  ComponentDummy.prototype = Component.prototype;
  var pureComponentPrototype = PureComponent.prototype = new ComponentDummy();
  pureComponentPrototype.constructor = PureComponent;
  // Avoid an extra prototype jump for these methods.
  _assign(pureComponentPrototype, Component.prototype);
  pureComponentPrototype.isPureReactComponent = true;
  
  function AsyncComponent(props, context, updater) {
    // Duplicated from Component.
    this.props = props;
    this.context = context;
    this.refs = emptyObject;
    // We initialize the default updater but the real one gets injected by the
    // renderer.
    this.updater = updater || ReactNoopUpdateQueue;
  }
  
  var asyncComponentPrototype = AsyncComponent.prototype = new ComponentDummy();
  asyncComponentPrototype.constructor = AsyncComponent;
  // Avoid an extra prototype jump for these methods.
  _assign(asyncComponentPrototype, Component.prototype);
  asyncComponentPrototype.unstable_isAsyncReactComponent = true;
  asyncComponentPrototype.render = function () {
    return this.props.children;
  };
  
  /**
   * Keeps track of the current owner.
   *
   * The current owner is the component who should own any components that are
   * currently being constructed.
   */
  var ReactCurrentOwner = {
    /**
     * @internal
     * @type {ReactComponent}
     */
    current: null
  };
  
  var hasOwnProperty = Object.prototype.hasOwnProperty;
  
  var RESERVED_PROPS = {
    key: true,
    ref: true,
    __self: true,
    __source: true
  };
  
  var specialPropKeyWarningShown;
  var specialPropRefWarningShown;
  
  function hasValidRef(config) {
    {
      if (hasOwnProperty.call(config, 'ref')) {
        var getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
        if (getter && getter.isReactWarning) {
          return false;
        }
      }
    }
    return config.ref !== undefined;
  }
  
  function hasValidKey(config) {
    {
      if (hasOwnProperty.call(config, 'key')) {
        var getter = Object.getOwnPropertyDescriptor(config, 'key').get;
        if (getter && getter.isReactWarning) {
          return false;
        }
      }
    }
    return config.key !== undefined;
  }
  
  function defineKeyPropWarningGetter(props, displayName) {
    var warnAboutAccessingKey = function () {
      if (!specialPropKeyWarningShown) {
        specialPropKeyWarningShown = true;
        warning(false, '%s: `key` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName);
      }
    };
    warnAboutAccessingKey.isReactWarning = true;
    Object.defineProperty(props, 'key', {
      get: warnAboutAccessingKey,
      configurable: true
    });
  }
  
  function defineRefPropWarningGetter(props, displayName) {
    var warnAboutAccessingRef = function () {
      if (!specialPropRefWarningShown) {
        specialPropRefWarningShown = true;
        warning(false, '%s: `ref` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName);
      }
    };
    warnAboutAccessingRef.isReactWarning = true;
    Object.defineProperty(props, 'ref', {
      get: warnAboutAccessingRef,
      configurable: true
    });
  }
  
  /**
   * Factory method to create a new React element. This no longer adheres to
   * the class pattern, so do not use new to call it. Also, no instanceof check
   * will work. Instead test $$typeof field against Symbol.for('react.element') to check
   * if something is a React Element.
   *
   * @param {*} type
   * @param {*} key
   * @param {string|object} ref
   * @param {*} self A *temporary* helper to detect places where `this` is
   * different from the `owner` when React.createElement is called, so that we
   * can warn. We want to get rid of owner and replace string `ref`s with arrow
   * functions, and as long as `this` and owner are the same, there will be no
   * change in behavior.
   * @param {*} source An annotation object (added by a transpiler or otherwise)
   * indicating filename, line number, and/or other information.
   * @param {*} owner
   * @param {*} props
   * @internal
   */
  var ReactElement = function (type, key, ref, self, source, owner, props) {
    var element = {
      // This tag allow us to uniquely identify this as a React Element
      $$typeof: REACT_ELEMENT_TYPE,
  
      // Built-in properties that belong on the element
      type: type,
      key: key,
      ref: ref,
      props: props,
  
      // Record the component responsible for creating this element.
      _owner: owner
    };
  
    {
      // The validation flag is currently mutative. We put it on
      // an external backing store so that we can freeze the whole object.
      // This can be replaced with a WeakMap once they are implemented in
      // commonly used development environments.
      element._store = {};
  
      // To make comparing ReactElements easier for testing purposes, we make
      // the validation flag non-enumerable (where possible, which should
      // include every environment we run tests in), so the test framework
      // ignores it.
      Object.defineProperty(element._store, 'validated', {
        configurable: false,
        enumerable: false,
        writable: true,
        value: false
      });
      // self and source are DEV only properties.
      Object.defineProperty(element, '_self', {
        configurable: false,
        enumerable: false,
        writable: false,
        value: self
      });
      // Two elements created in two different places should be considered
      // equal for testing purposes and therefore we hide it from enumeration.
      Object.defineProperty(element, '_source', {
        configurable: false,
        enumerable: false,
        writable: false,
        value: source
      });
      if (Object.freeze) {
        Object.freeze(element.props);
        Object.freeze(element);
      }
    }
  
    return element;
  };
  
  /**
   * Create and return a new ReactElement of the given type.
   * See https://reactjs.org/docs/react-api.html#createelement
   */
  function createElement(type, config, children) {
    var propName;
  
    // Reserved names are extracted
    var props = {};
  
    var key = null;
    var ref = null;
    var self = null;
    var source = null;
  
    if (config != null) {
      if (hasValidRef(config)) {
        ref = config.ref;
      }
      if (hasValidKey(config)) {
        key = '' + config.key;
      }
  
      self = config.__self === undefined ? null : config.__self;
      source = config.__source === undefined ? null : config.__source;
      // Remaining properties are added to a new props object
      for (propName in config) {
        if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
          props[propName] = config[propName];
        }
      }
    }
  
    // Children can be more than one argument, and those are transferred onto
    // the newly allocated props object.
    var childrenLength = arguments.length - 2;
    if (childrenLength === 1) {
      props.children = children;
    } else if (childrenLength > 1) {
      var childArray = Array(childrenLength);
      for (var i = 0; i < childrenLength; i++) {
        childArray[i] = arguments[i + 2];
      }
      {
        if (Object.freeze) {
          Object.freeze(childArray);
        }
      }
      props.children = childArray;
    }
  
    // Resolve default props
    if (type && type.defaultProps) {
      var defaultProps = type.defaultProps;
      for (propName in defaultProps) {
        if (props[propName] === undefined) {
          props[propName] = defaultProps[propName];
        }
      }
    }
    {
      if (key || ref) {
        if (typeof props.$$typeof === 'undefined' || props.$$typeof !== REACT_ELEMENT_TYPE) {
          var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type;
          if (key) {
            defineKeyPropWarningGetter(props, displayName);
          }
          if (ref) {
            defineRefPropWarningGetter(props, displayName);
          }
        }
      }
    }
    return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
  }
  
  /**
   * Return a function that produces ReactElements of a given type.
   * See https://reactjs.org/docs/react-api.html#createfactory
   */
  
  
  function cloneAndReplaceKey(oldElement, newKey) {
    var newElement = ReactElement(oldElement.type, newKey, oldElement.ref, oldElement._self, oldElement._source, oldElement._owner, oldElement.props);
  
    return newElement;
  }
  
  /**
   * Clone and return a new ReactElement using element as the starting point.
   * See https://reactjs.org/docs/react-api.html#cloneelement
   */
  function cloneElement(element, config, children) {
    var propName;
  
    // Original props are copied
    var props = _assign({}, element.props);
  
    // Reserved names are extracted
    var key = element.key;
    var ref = element.ref;
    // Self is preserved since the owner is preserved.
    var self = element._self;
    // Source is preserved since cloneElement is unlikely to be targeted by a
    // transpiler, and the original source is probably a better indicator of the
    // true owner.
    var source = element._source;
  
    // Owner will be preserved, unless ref is overridden
    var owner = element._owner;
  
    if (config != null) {
      if (hasValidRef(config)) {
        // Silently steal the ref from the parent.
        ref = config.ref;
        owner = ReactCurrentOwner.current;
      }
      if (hasValidKey(config)) {
        key = '' + config.key;
      }
  
      // Remaining properties override existing props
      var defaultProps;
      if (element.type && element.type.defaultProps) {
        defaultProps = element.type.defaultProps;
      }
      for (propName in config) {
        if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
          if (config[propName] === undefined && defaultProps !== undefined) {
            // Resolve default props
            props[propName] = defaultProps[propName];
          } else {
            props[propName] = config[propName];
          }
        }
      }
    }
  
    // Children can be more than one argument, and those are transferred onto
    // the newly allocated props object.
    var childrenLength = arguments.length - 2;
    if (childrenLength === 1) {
      props.children = children;
    } else if (childrenLength > 1) {
      var childArray = Array(childrenLength);
      for (var i = 0; i < childrenLength; i++) {
        childArray[i] = arguments[i + 2];
      }
      props.children = childArray;
    }
  
    return ReactElement(element.type, key, ref, self, source, owner, props);
  }
  
  /**
   * Verifies the object is a ReactElement.
   * See https://reactjs.org/docs/react-api.html#isvalidelement
   * @param {?object} object
   * @return {boolean} True if `object` is a valid component.
   * @final
   */
  function isValidElement(object) {
    return typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;
  }
  
  var ReactDebugCurrentFrame = {};
  
  {
    // Component that is being worked on
    ReactDebugCurrentFrame.getCurrentStack = null;
  
    ReactDebugCurrentFrame.getStackAddendum = function () {
      var impl = ReactDebugCurrentFrame.getCurrentStack;
      if (impl) {
        return impl();
      }
      return null;
    };
  }
  
  var SEPARATOR = '.';
  var SUBSEPARATOR = ':';
  
  /**
   * Escape and wrap key so it is safe to use as a reactid
   *
   * @param {string} key to be escaped.
   * @return {string} the escaped key.
   */
  function escape(key) {
    var escapeRegex = /[=:]/g;
    var escaperLookup = {
      '=': '=0',
      ':': '=2'
    };
    var escapedString = ('' + key).replace(escapeRegex, function (match) {
      return escaperLookup[match];
    });
  
    return '$' + escapedString;
  }
  
  /**
   * TODO: Test that a single child and an array with one item have the same key
   * pattern.
   */
  
  var didWarnAboutMaps = false;
  
  var userProvidedKeyEscapeRegex = /\/+/g;
  function escapeUserProvidedKey(text) {
    return ('' + text).replace(userProvidedKeyEscapeRegex, '$&/');
  }
  
  var POOL_SIZE = 10;
  var traverseContextPool = [];
  function getPooledTraverseContext(mapResult, keyPrefix, mapFunction, mapContext) {
    if (traverseContextPool.length) {
      var traverseContext = traverseContextPool.pop();
      traverseContext.result = mapResult;
      traverseContext.keyPrefix = keyPrefix;
      traverseContext.func = mapFunction;
      traverseContext.context = mapContext;
      traverseContext.count = 0;
      return traverseContext;
    } else {
      return {
        result: mapResult,
        keyPrefix: keyPrefix,
        func: mapFunction,
        context: mapContext,
        count: 0
      };
    }
  }
  
  function releaseTraverseContext(traverseContext) {
    traverseContext.result = null;
    traverseContext.keyPrefix = null;
    traverseContext.func = null;
    traverseContext.context = null;
    traverseContext.count = 0;
    if (traverseContextPool.length < POOL_SIZE) {
      traverseContextPool.push(traverseContext);
    }
  }
  
  /**
   * @param {?*} children Children tree container.
   * @param {!string} nameSoFar Name of the key path so far.
   * @param {!function} callback Callback to invoke with each child found.
   * @param {?*} traverseContext Used to pass information throughout the traversal
   * process.
   * @return {!number} The number of children in this subtree.
   */
  function traverseAllChildrenImpl(children, nameSoFar, callback, traverseContext) {
    var type = typeof children;
  
    if (type === 'undefined' || type === 'boolean') {
      // All of the above are perceived as null.
      children = null;
    }
  
    var invokeCallback = false;
  
    if (children === null) {
      invokeCallback = true;
    } else {
      switch (type) {
        case 'string':
        case 'number':
          invokeCallback = true;
          break;
        case 'object':
          switch (children.$$typeof) {
            case REACT_ELEMENT_TYPE:
            case REACT_CALL_TYPE:
            case REACT_RETURN_TYPE:
            case REACT_PORTAL_TYPE:
              invokeCallback = true;
          }
      }
    }
  
    if (invokeCallback) {
      callback(traverseContext, children,
      // If it's the only child, treat the name as if it was wrapped in an array
      // so that it's consistent if the number of children grows.
      nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar);
      return 1;
    }
  
    var child;
    var nextName;
    var subtreeCount = 0; // Count of children found in the current subtree.
    var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
  
    if (Array.isArray(children)) {
      for (var i = 0; i < children.length; i++) {
        child = children[i];
        nextName = nextNamePrefix + getComponentKey(child, i);
        subtreeCount += traverseAllChildrenImpl(child, nextName, callback, traverseContext);
      }
    } else {
      var iteratorFn = getIteratorFn(children);
      if (typeof iteratorFn === 'function') {
        {
          // Warn about using Maps as children
          if (iteratorFn === children.entries) {
            warning(didWarnAboutMaps, 'Using Maps as children is unsupported and will likely yield ' + 'unexpected results. Convert it to a sequence/iterable of keyed ' + 'ReactElements instead.%s', ReactDebugCurrentFrame.getStackAddendum());
            didWarnAboutMaps = true;
          }
        }
  
        var iterator = iteratorFn.call(children);
        var step;
        var ii = 0;
        while (!(step = iterator.next()).done) {
          child = step.value;
          nextName = nextNamePrefix + getComponentKey(child, ii++);
          subtreeCount += traverseAllChildrenImpl(child, nextName, callback, traverseContext);
        }
      } else if (type === 'object') {
        var addendum = '';
        {
          addendum = ' If you meant to render a collection of children, use an array ' + 'instead.' + ReactDebugCurrentFrame.getStackAddendum();
        }
        var childrenString = '' + children;
        invariant(false, 'Objects are not valid as a React child (found: %s).%s', childrenString === '[object Object]' ? 'object with keys {' + Object.keys(children).join(', ') + '}' : childrenString, addendum);
      }
    }
  
    return subtreeCount;
  }
  
  /**
   * Traverses children that are typically specified as `props.children`, but
   * might also be specified through attributes:
   *
   * - `traverseAllChildren(this.props.children, ...)`
   * - `traverseAllChildren(this.props.leftPanelChildren, ...)`
   *
   * The `traverseContext` is an optional argument that is passed through the
   * entire traversal. It can be used to store accumulations or anything else that
   * the callback might find relevant.
   *
   * @param {?*} children Children tree object.
   * @param {!function} callback To invoke upon traversing each child.
   * @param {?*} traverseContext Context for traversal.
   * @return {!number} The number of children in this subtree.
   */
  function traverseAllChildren(children, callback, traverseContext) {
    if (children == null) {
      return 0;
    }
  
    return traverseAllChildrenImpl(children, '', callback, traverseContext);
  }
  
  /**
   * Generate a key string that identifies a component within a set.
   *
   * @param {*} component A component that could contain a manual key.
   * @param {number} index Index that is used if a manual key is not provided.
   * @return {string}
   */
  function getComponentKey(component, index) {
    // Do some typechecking here since we call this blindly. We want to ensure
    // that we don't block potential future ES APIs.
    if (typeof component === 'object' && component !== null && component.key != null) {
      // Explicit key
      return escape(component.key);
    }
    // Implicit key determined by the index in the set
    return index.toString(36);
  }
  
  function forEachSingleChild(bookKeeping, child, name) {
    var func = bookKeeping.func,
        context = bookKeeping.context;
  
    func.call(context, child, bookKeeping.count++);
  }
  
  /**
   * Iterates through children that are typically specified as `props.children`.
   *
   * See https://reactjs.org/docs/react-api.html#react.children.foreach
   *
   * The provided forEachFunc(child, index) will be called for each
   * leaf child.
   *
   * @param {?*} children Children tree container.
   * @param {function(*, int)} forEachFunc
   * @param {*} forEachContext Context for forEachContext.
   */
  function forEachChildren(children, forEachFunc, forEachContext) {
    if (children == null) {
      return children;
    }
    var traverseContext = getPooledTraverseContext(null, null, forEachFunc, forEachContext);
    traverseAllChildren(children, forEachSingleChild, traverseContext);
    releaseTraverseContext(traverseContext);
  }
  
  function mapSingleChildIntoContext(bookKeeping, child, childKey) {
    var result = bookKeeping.result,
        keyPrefix = bookKeeping.keyPrefix,
        func = bookKeeping.func,
        context = bookKeeping.context;
  
  
    var mappedChild = func.call(context, child, bookKeeping.count++);
    if (Array.isArray(mappedChild)) {
      mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, emptyFunction.thatReturnsArgument);
    } else if (mappedChild != null) {
      if (isValidElement(mappedChild)) {
        mappedChild = cloneAndReplaceKey(mappedChild,
        // Keep both the (mapped) and old keys if they differ, just as
        // traverseAllChildren used to do for objects as children
        keyPrefix + (mappedChild.key && (!child || child.key !== mappedChild.key) ? escapeUserProvidedKey(mappedChild.key) + '/' : '') + childKey);
      }
      result.push(mappedChild);
    }
  }
  
  function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
    var escapedPrefix = '';
    if (prefix != null) {
      escapedPrefix = escapeUserProvidedKey(prefix) + '/';
    }
    var traverseContext = getPooledTraverseContext(array, escapedPrefix, func, context);
    traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
    releaseTraverseContext(traverseContext);
  }
  
  /**
   * Maps children that are typically specified as `props.children`.
   *
   * See https://reactjs.org/docs/react-api.html#react.children.map
   *
   * The provided mapFunction(child, key, index) will be called for each
   * leaf child.
   *
   * @param {?*} children Children tree container.
   * @param {function(*, int)} func The map function.
   * @param {*} context Context for mapFunction.
   * @return {object} Object containing the ordered map of results.
   */
  function mapChildren(children, func, context) {
    if (children == null) {
      return children;
    }
    var result = [];
    mapIntoWithKeyPrefixInternal(children, result, null, func, context);
    return result;
  }
  
  /**
   * Count the number of children that are typically specified as
   * `props.children`.
   *
   * See https://reactjs.org/docs/react-api.html#react.children.count
   *
   * @param {?*} children Children tree container.
   * @return {number} The number of children.
   */
  function countChildren(children, context) {
    return traverseAllChildren(children, emptyFunction.thatReturnsNull, null);
  }
  
  /**
   * Flatten a children object (typically specified as `props.children`) and
   * return an array with appropriately re-keyed children.
   *
   * See https://reactjs.org/docs/react-api.html#react.children.toarray
   */
  function toArray(children) {
    var result = [];
    mapIntoWithKeyPrefixInternal(children, result, null, emptyFunction.thatReturnsArgument);
    return result;
  }
  
  /**
   * Returns the first child in a collection of children and verifies that there
   * is only one child in the collection.
   *
   * See https://reactjs.org/docs/react-api.html#react.children.only
   *
   * The current implementation of this function assumes that a single child gets
   * passed without a wrapper, but the purpose of this helper function is to
   * abstract away the particular structure of children.
   *
   * @param {?object} children Child collection structure.
   * @return {ReactElement} The first and only `ReactElement` contained in the
   * structure.
   */
  function onlyChild(children) {
    !isValidElement(children) ? invariant(false, 'React.Children.only expected to receive a single React element child.') : void 0;
    return children;
  }
  
  var describeComponentFrame = function (name, source, ownerName) {
    return '\n    in ' + (name || 'Unknown') + (source ? ' (at ' + source.fileName.replace(/^.*[\\\/]/, '') + ':' + source.lineNumber + ')' : ownerName ? ' (created by ' + ownerName + ')' : '');
  };
  
  function getComponentName(fiber) {
    var type = fiber.type;
  
    if (typeof type === 'string') {
      return type;
    }
    if (typeof type === 'function') {
      return type.displayName || type.name;
    }
    return null;
  }
  
  /**
   * ReactElementValidator provides a wrapper around a element factory
   * which validates the props passed to the element. This is intended to be
   * used only in DEV and could be replaced by a static type checker for languages
   * that support it.
   */
  
  {
    var currentlyValidatingElement = null;
  
    var propTypesMisspellWarningShown = false;
  
    var getDisplayName = function (element) {
      if (element == null) {
        return '#empty';
      } else if (typeof element === 'string' || typeof element === 'number') {
        return '#text';
      } else if (typeof element.type === 'string') {
        return element.type;
      } else if (element.type === REACT_FRAGMENT_TYPE) {
        return 'React.Fragment';
      } else {
        return element.type.displayName || element.type.name || 'Unknown';
      }
    };
  
    var getStackAddendum = function () {
      var stack = '';
      if (currentlyValidatingElement) {
        var name = getDisplayName(currentlyValidatingElement);
        var owner = currentlyValidatingElement._owner;
        stack += describeComponentFrame(name, currentlyValidatingElement._source, owner && getComponentName(owner));
      }
      stack += ReactDebugCurrentFrame.getStackAddendum() || '';
      return stack;
    };
  
    var VALID_FRAGMENT_PROPS = new Map([['children', true], ['key', true]]);
  }
  
  function getDeclarationErrorAddendum() {
    if (ReactCurrentOwner.current) {
      var name = getComponentName(ReactCurrentOwner.current);
      if (name) {
        return '\n\nCheck the render method of `' + name + '`.';
      }
    }
    return '';
  }
  
  function getSourceInfoErrorAddendum(elementProps) {
    if (elementProps !== null && elementProps !== undefined && elementProps.__source !== undefined) {
      var source = elementProps.__source;
      var fileName = source.fileName.replace(/^.*[\\\/]/, '');
      var lineNumber = source.lineNumber;
      return '\n\nCheck your code at ' + fileName + ':' + lineNumber + '.';
    }
    return '';
  }
  
  /**
   * Warn if there's no key explicitly set on dynamic arrays of children or
   * object keys are not valid. This allows us to keep track of children between
   * updates.
   */
  var ownerHasKeyUseWarning = {};
  
  function getCurrentComponentErrorInfo(parentType) {
    var info = getDeclarationErrorAddendum();
  
    if (!info) {
      var parentName = typeof parentType === 'string' ? parentType : parentType.displayName || parentType.name;
      if (parentName) {
        info = '\n\nCheck the top-level render call using <' + parentName + '>.';
      }
    }
    return info;
  }
  
  /**
   * Warn if the element doesn't have an explicit key assigned to it.
   * This element is in an array. The array could grow and shrink or be
   * reordered. All children that haven't already been validated are required to
   * have a "key" property assigned to it. Error statuses are cached so a warning
   * will only be shown once.
   *
   * @internal
   * @param {ReactElement} element Element that requires a key.
   * @param {*} parentType element's parent's type.
   */
  function validateExplicitKey(element, parentType) {
    if (!element._store || element._store.validated || element.key != null) {
      return;
    }
    element._store.validated = true;
  
    var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);
    if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
      return;
    }
    ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
  
    // Usually the current owner is the offender, but if it accepts children as a
    // property, it may be the creator of the child that's responsible for
    // assigning it a key.
    var childOwner = '';
    if (element && element._owner && element._owner !== ReactCurrentOwner.current) {
      // Give the component that originally created this child.
      childOwner = ' It was passed a child from ' + getComponentName(element._owner) + '.';
    }
  
    currentlyValidatingElement = element;
    {
      warning(false, 'Each child in an array or iterator should have a unique "key" prop.' + '%s%s See https://fb.me/react-warning-keys for more information.%s', currentComponentErrorInfo, childOwner, getStackAddendum());
    }
    currentlyValidatingElement = null;
  }
  
  /**
   * Ensure that every element either is passed in a static location, in an
   * array with an explicit keys property defined, or in an object literal
   * with valid key property.
   *
   * @internal
   * @param {ReactNode} node Statically passed child of any type.
   * @param {*} parentType node's parent's type.
   */
  function validateChildKeys(node, parentType) {
    if (typeof node !== 'object') {
      return;
    }
    if (Array.isArray(node)) {
      for (var i = 0; i < node.length; i++) {
        var child = node[i];
        if (isValidElement(child)) {
          validateExplicitKey(child, parentType);
        }
      }
    } else if (isValidElement(node)) {
      // This element was passed in a valid location.
      if (node._store) {
        node._store.validated = true;
      }
    } else if (node) {
      var iteratorFn = getIteratorFn(node);
      if (typeof iteratorFn === 'function') {
        // Entry iterators used to provide implicit keys,
        // but now we print a separate warning for them later.
        if (iteratorFn !== node.entries) {
          var iterator = iteratorFn.call(node);
          var step;
          while (!(step = iterator.next()).done) {
            if (isValidElement(step.value)) {
              validateExplicitKey(step.value, parentType);
            }
          }
        }
      }
    }
  }
  
  /**
   * Given an element, validate that its props follow the propTypes definition,
   * provided by the type.
   *
   * @param {ReactElement} element
   */
  function validatePropTypes(element) {
    var componentClass = element.type;
    if (typeof componentClass !== 'function') {
      return;
    }
    var name = componentClass.displayName || componentClass.name;
    var propTypes = componentClass.propTypes;
    if (propTypes) {
      currentlyValidatingElement = element;
      checkPropTypes(propTypes, element.props, 'prop', name, getStackAddendum);
      currentlyValidatingElement = null;
    } else if (componentClass.PropTypes !== undefined && !propTypesMisspellWarningShown) {
      propTypesMisspellWarningShown = true;
      warning(false, 'Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?', name || 'Unknown');
    }
    if (typeof componentClass.getDefaultProps === 'function') {
      warning(componentClass.getDefaultProps.isReactClassApproved, 'getDefaultProps is only used on classic React.createClass ' + 'definitions. Use a static property named `defaultProps` instead.');
    }
  }
  
  /**
   * Given a fragment, validate that it can only be provided with fragment props
   * @param {ReactElement} fragment
   */
  function validateFragmentProps(fragment) {
    currentlyValidatingElement = fragment;
  
    var _iteratorNormalCompletion = true;
    var _didIteratorError = false;
    var _iteratorError = undefined;
  
    try {
      for (var _iterator = Object.keys(fragment.props)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
        var key = _step.value;
  
        if (!VALID_FRAGMENT_PROPS.has(key)) {
          warning(false, 'Invalid prop `%s` supplied to `React.Fragment`. ' + 'React.Fragment can only have `key` and `children` props.%s', key, getStackAddendum());
          break;
        }
      }
    } catch (err) {
      _didIteratorError = true;
      _iteratorError = err;
    } finally {
      try {
        if (!_iteratorNormalCompletion && _iterator['return']) {
          _iterator['return']();
        }
      } finally {
        if (_didIteratorError) {
          throw _iteratorError;
        }
      }
    }
  
    if (fragment.ref !== null) {
      warning(false, 'Invalid attribute `ref` supplied to `React.Fragment`.%s', getStackAddendum());
    }
  
    currentlyValidatingElement = null;
  }
  
  function createElementWithValidation(type, props, children) {
    var validType = typeof type === 'string' || typeof type === 'function' || typeof type === 'symbol' || typeof type === 'number';
    // We warn in this case but don't throw. We expect the element creation to
    // succeed and there will likely be errors in render.
    if (!validType) {
      var info = '';
      if (type === undefined || typeof type === 'object' && type !== null && Object.keys(type).length === 0) {
        info += ' You likely forgot to export your component from the file ' + "it's defined in, or you might have mixed up default and named imports.";
      }
  
      var sourceInfo = getSourceInfoErrorAddendum(props);
      if (sourceInfo) {
        info += sourceInfo;
      } else {
        info += getDeclarationErrorAddendum();
      }
  
      info += getStackAddendum() || '';
  
      warning(false, 'React.createElement: type is invalid -- expected a string (for ' + 'built-in components) or a class/function (for composite ' + 'components) but got: %s.%s', type == null ? type : typeof type, info);
    }
  
    var element = createElement.apply(this, arguments);
  
    // The result can be nullish if a mock or a custom function is used.
    // TODO: Drop this when these are no longer allowed as the type argument.
    if (element == null) {
      return element;
    }
  
    // Skip key warning if the type isn't valid since our key validation logic
    // doesn't expect a non-string/function type and can throw confusing errors.
    // We don't want exception behavior to differ between dev and prod.
    // (Rendering will throw with a helpful message and as soon as the type is
    // fixed, the key warnings will appear.)
    if (validType) {
      for (var i = 2; i < arguments.length; i++) {
        validateChildKeys(arguments[i], type);
      }
    }
  
    if (typeof type === 'symbol' && type === REACT_FRAGMENT_TYPE) {
      validateFragmentProps(element);
    } else {
      validatePropTypes(element);
    }
  
    return element;
  }
  
  function createFactoryWithValidation(type) {
    var validatedFactory = createElementWithValidation.bind(null, type);
    // Legacy hook TODO: Warn if this is accessed
    validatedFactory.type = type;
  
    {
      Object.defineProperty(validatedFactory, 'type', {
        enumerable: false,
        get: function () {
          lowPriorityWarning$1(false, 'Factory.type is deprecated. Access the class directly ' + 'before passing it to createFactory.');
          Object.defineProperty(this, 'type', {
            value: type
          });
          return type;
        }
      });
    }
  
    return validatedFactory;
  }
  
  function cloneElementWithValidation(element, props, children) {
    var newElement = cloneElement.apply(this, arguments);
    for (var i = 2; i < arguments.length; i++) {
      validateChildKeys(arguments[i], newElement.type);
    }
    validatePropTypes(newElement);
    return newElement;
  }
  
  var React = {
    Children: {
      map: mapChildren,
      forEach: forEachChildren,
      count: countChildren,
      toArray: toArray,
      only: onlyChild
    },
  
    Component: Component,
    PureComponent: PureComponent,
    unstable_AsyncComponent: AsyncComponent,
  
    Fragment: REACT_FRAGMENT_TYPE,
  
    createElement: createElementWithValidation,
    cloneElement: cloneElementWithValidation,
    createFactory: createFactoryWithValidation,
    isValidElement: isValidElement,
  
    version: ReactVersion,
  
    __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
      ReactCurrentOwner: ReactCurrentOwner,
      // Used by renderers to avoid bundling object-assign twice in UMD bundles:
      assign: _assign
    }
  };
  
  {
    _assign(React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, {
      // These should not be included in production.
      ReactDebugCurrentFrame: ReactDebugCurrentFrame,
      // Shim for React DOM 16.0.0 which still destructured (but not used) this.
      // TODO: remove in React 17.0.
      ReactComponentTreeHook: {}
    });
  }
  
  
  
  var React$2 = Object.freeze({
    default: React
  });
  
  var React$3 = ( React$2 && React ) || React$2;
  
  // TODO: decide on the top-level export form.
  // This is hacky but makes it work with both Rollup and Jest.
  var react = React$3['default'] ? React$3['default'] : React$3;
  
  module.exports = react;
    })();
  }
  
  },{"fbjs/lib/emptyFunction":338,"fbjs/lib/emptyObject":339,"fbjs/lib/invariant":344,"fbjs/lib/warning":348,"object-assign":480,"prop-types/checkPropTypes":523}],521:[function(require,module,exports){
  /** @license React v16.2.0
   * react.production.min.js
   *
   * Copyright (c) 2013-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   */
  
  'use strict';var m=require("object-assign"),n=require("fbjs/lib/emptyObject"),p=require("fbjs/lib/emptyFunction"),q="function"===typeof Symbol&&Symbol["for"],r=q?Symbol["for"]("react.element"):60103,t=q?Symbol["for"]("react.call"):60104,u=q?Symbol["for"]("react.return"):60105,v=q?Symbol["for"]("react.portal"):60106,w=q?Symbol["for"]("react.fragment"):60107,x="function"===typeof Symbol&&Symbol.iterator;
  function y(a){for(var b=arguments.length-1,e="Minified React error #"+a+"; visit http://facebook.github.io/react/docs/error-decoder.html?invariant\x3d"+a,c=0;c<b;c++)e+="\x26args[]\x3d"+encodeURIComponent(arguments[c+1]);b=Error(e+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings.");b.name="Invariant Violation";b.framesToPop=1;throw b;}
  var z={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}};function A(a,b,e){this.props=a;this.context=b;this.refs=n;this.updater=e||z}A.prototype.isReactComponent={};A.prototype.setState=function(a,b){"object"!==typeof a&&"function"!==typeof a&&null!=a?y("85"):void 0;this.updater.enqueueSetState(this,a,b,"setState")};A.prototype.forceUpdate=function(a){this.updater.enqueueForceUpdate(this,a,"forceUpdate")};
  function B(a,b,e){this.props=a;this.context=b;this.refs=n;this.updater=e||z}function C(){}C.prototype=A.prototype;var D=B.prototype=new C;D.constructor=B;m(D,A.prototype);D.isPureReactComponent=!0;function E(a,b,e){this.props=a;this.context=b;this.refs=n;this.updater=e||z}var F=E.prototype=new C;F.constructor=E;m(F,A.prototype);F.unstable_isAsyncReactComponent=!0;F.render=function(){return this.props.children};var G={current:null},H=Object.prototype.hasOwnProperty,I={key:!0,ref:!0,__self:!0,__source:!0};
  function J(a,b,e){var c,d={},g=null,k=null;if(null!=b)for(c in void 0!==b.ref&&(k=b.ref),void 0!==b.key&&(g=""+b.key),b)H.call(b,c)&&!I.hasOwnProperty(c)&&(d[c]=b[c]);var f=arguments.length-2;if(1===f)d.children=e;else if(1<f){for(var h=Array(f),l=0;l<f;l++)h[l]=arguments[l+2];d.children=h}if(a&&a.defaultProps)for(c in f=a.defaultProps,f)void 0===d[c]&&(d[c]=f[c]);return{$$typeof:r,type:a,key:g,ref:k,props:d,_owner:G.current}}function K(a){return"object"===typeof a&&null!==a&&a.$$typeof===r}
  function escape(a){var b={"\x3d":"\x3d0",":":"\x3d2"};return"$"+(""+a).replace(/[=:]/g,function(a){return b[a]})}var L=/\/+/g,M=[];function N(a,b,e,c){if(M.length){var d=M.pop();d.result=a;d.keyPrefix=b;d.func=e;d.context=c;d.count=0;return d}return{result:a,keyPrefix:b,func:e,context:c,count:0}}function O(a){a.result=null;a.keyPrefix=null;a.func=null;a.context=null;a.count=0;10>M.length&&M.push(a)}
  function P(a,b,e,c){var d=typeof a;if("undefined"===d||"boolean"===d)a=null;var g=!1;if(null===a)g=!0;else switch(d){case "string":case "number":g=!0;break;case "object":switch(a.$$typeof){case r:case t:case u:case v:g=!0}}if(g)return e(c,a,""===b?"."+Q(a,0):b),1;g=0;b=""===b?".":b+":";if(Array.isArray(a))for(var k=0;k<a.length;k++){d=a[k];var f=b+Q(d,k);g+=P(d,f,e,c)}else if(null===a||"undefined"===typeof a?f=null:(f=x&&a[x]||a["@@iterator"],f="function"===typeof f?f:null),"function"===typeof f)for(a=
  f.call(a),k=0;!(d=a.next()).done;)d=d.value,f=b+Q(d,k++),g+=P(d,f,e,c);else"object"===d&&(e=""+a,y("31","[object Object]"===e?"object with keys {"+Object.keys(a).join(", ")+"}":e,""));return g}function Q(a,b){return"object"===typeof a&&null!==a&&null!=a.key?escape(a.key):b.toString(36)}function R(a,b){a.func.call(a.context,b,a.count++)}
  function S(a,b,e){var c=a.result,d=a.keyPrefix;a=a.func.call(a.context,b,a.count++);Array.isArray(a)?T(a,c,e,p.thatReturnsArgument):null!=a&&(K(a)&&(b=d+(!a.key||b&&b.key===a.key?"":(""+a.key).replace(L,"$\x26/")+"/")+e,a={$$typeof:r,type:a.type,key:b,ref:a.ref,props:a.props,_owner:a._owner}),c.push(a))}function T(a,b,e,c,d){var g="";null!=e&&(g=(""+e).replace(L,"$\x26/")+"/");b=N(b,g,c,d);null==a||P(a,"",S,b);O(b)}
  var U={Children:{map:function(a,b,e){if(null==a)return a;var c=[];T(a,c,null,b,e);return c},forEach:function(a,b,e){if(null==a)return a;b=N(null,null,b,e);null==a||P(a,"",R,b);O(b)},count:function(a){return null==a?0:P(a,"",p.thatReturnsNull,null)},toArray:function(a){var b=[];T(a,b,null,p.thatReturnsArgument);return b},only:function(a){K(a)?void 0:y("143");return a}},Component:A,PureComponent:B,unstable_AsyncComponent:E,Fragment:w,createElement:J,cloneElement:function(a,b,e){var c=m({},a.props),
  d=a.key,g=a.ref,k=a._owner;if(null!=b){void 0!==b.ref&&(g=b.ref,k=G.current);void 0!==b.key&&(d=""+b.key);if(a.type&&a.type.defaultProps)var f=a.type.defaultProps;for(h in b)H.call(b,h)&&!I.hasOwnProperty(h)&&(c[h]=void 0===b[h]&&void 0!==f?f[h]:b[h])}var h=arguments.length-2;if(1===h)c.children=e;else if(1<h){f=Array(h);for(var l=0;l<h;l++)f[l]=arguments[l+2];c.children=f}return{$$typeof:r,type:a.type,key:d,ref:g,props:c,_owner:k}},createFactory:function(a){var b=J.bind(null,a);b.type=a;return b},
  isValidElement:K,version:"16.2.0",__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED:{ReactCurrentOwner:G,assign:m}},V=Object.freeze({default:U}),W=V&&U||V;module.exports=W["default"]?W["default"]:W;
  
  },{"fbjs/lib/emptyFunction":338,"fbjs/lib/emptyObject":339,"object-assign":480}],522:[function(require,module,exports){
  'use strict';
  
  if ("production" === 'production') {
    module.exports = require('./cjs/react.production.min.js');
  } else {
    module.exports = require('./cjs/react.development.js');
  }
  
  },{"./cjs/react.development.js":520,"./cjs/react.production.min.js":521}],523:[function(require,module,exports){
  arguments[4][483][0].apply(exports,arguments)
  },{"./lib/ReactPropTypesSecret":524,"dup":483,"fbjs/lib/invariant":344,"fbjs/lib/warning":348}],524:[function(require,module,exports){
  arguments[4][487][0].apply(exports,arguments)
  },{"dup":487}],525:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  function createThunkMiddleware(extraArgument) {
    return function (_ref) {
      var dispatch = _ref.dispatch,
          getState = _ref.getState;
      return function (next) {
        return function (action) {
          if (typeof action === 'function') {
            return action(dispatch, getState, extraArgument);
          }
  
          return next(action);
        };
      };
    };
  }
  
  var thunk = createThunkMiddleware();
  thunk.withExtraArgument = createThunkMiddleware;
  
  exports['default'] = thunk;
  },{}],526:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  
  var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
  
  exports['default'] = applyMiddleware;
  
  var _compose = require('./compose');
  
  var _compose2 = _interopRequireDefault(_compose);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  
  /**
   * Creates a store enhancer that applies middleware to the dispatch method
   * of the Redux store. This is handy for a variety of tasks, such as expressing
   * asynchronous actions in a concise manner, or logging every action payload.
   *
   * See `redux-thunk` package as an example of the Redux middleware.
   *
   * Because middleware is potentially asynchronous, this should be the first
   * store enhancer in the composition chain.
   *
   * Note that each middleware will be given the `dispatch` and `getState` functions
   * as named arguments.
   *
   * @param {...Function} middlewares The middleware chain to be applied.
   * @returns {Function} A store enhancer applying the middleware.
   */
  function applyMiddleware() {
    for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
      middlewares[_key] = arguments[_key];
    }
  
    return function (createStore) {
      return function (reducer, preloadedState, enhancer) {
        var store = createStore(reducer, preloadedState, enhancer);
        var _dispatch = store.dispatch;
        var chain = [];
  
        var middlewareAPI = {
          getState: store.getState,
          dispatch: function dispatch(action) {
            return _dispatch(action);
          }
        };
        chain = middlewares.map(function (middleware) {
          return middleware(middlewareAPI);
        });
        _dispatch = _compose2['default'].apply(undefined, chain)(store.dispatch);
  
        return _extends({}, store, {
          dispatch: _dispatch
        });
      };
    };
  }
  },{"./compose":529}],527:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports['default'] = bindActionCreators;
  function bindActionCreator(actionCreator, dispatch) {
    return function () {
      return dispatch(actionCreator.apply(undefined, arguments));
    };
  }
  
  /**
   * Turns an object whose values are action creators, into an object with the
   * same keys, but with every function wrapped into a `dispatch` call so they
   * may be invoked directly. This is just a convenience method, as you can call
   * `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
   *
   * For convenience, you can also pass a single function as the first argument,
   * and get a function in return.
   *
   * @param {Function|Object} actionCreators An object whose values are action
   * creator functions. One handy way to obtain it is to use ES6 `import * as`
   * syntax. You may also pass a single function.
   *
   * @param {Function} dispatch The `dispatch` function available on your Redux
   * store.
   *
   * @returns {Function|Object} The object mimicking the original object, but with
   * every action creator wrapped into the `dispatch` call. If you passed a
   * function as `actionCreators`, the return value will also be a single
   * function.
   */
  function bindActionCreators(actionCreators, dispatch) {
    if (typeof actionCreators === 'function') {
      return bindActionCreator(actionCreators, dispatch);
    }
  
    if (typeof actionCreators !== 'object' || actionCreators === null) {
      throw new Error('bindActionCreators expected an object or a function, instead received ' + (actionCreators === null ? 'null' : typeof actionCreators) + '. ' + 'Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?');
    }
  
    var keys = Object.keys(actionCreators);
    var boundActionCreators = {};
    for (var i = 0; i < keys.length; i++) {
      var key = keys[i];
      var actionCreator = actionCreators[key];
      if (typeof actionCreator === 'function') {
        boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
      }
    }
    return boundActionCreators;
  }
  },{}],528:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports['default'] = combineReducers;
  
  var _createStore = require('./createStore');
  
  var _isPlainObject = require('lodash/isPlainObject');
  
  var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
  
  var _warning = require('./utils/warning');
  
  var _warning2 = _interopRequireDefault(_warning);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  
  function getUndefinedStateErrorMessage(key, action) {
    var actionType = action && action.type;
    var actionName = actionType && '"' + actionType.toString() + '"' || 'an action';
  
    return 'Given action ' + actionName + ', reducer "' + key + '" returned undefined. ' + 'To ignore an action, you must explicitly return the previous state. ' + 'If you want this reducer to hold no value, you can return null instead of undefined.';
  }
  
  function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
    var reducerKeys = Object.keys(reducers);
    var argumentName = action && action.type === _createStore.ActionTypes.INIT ? 'preloadedState argument passed to createStore' : 'previous state received by the reducer';
  
    if (reducerKeys.length === 0) {
      return 'Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.';
    }
  
    if (!(0, _isPlainObject2['default'])(inputState)) {
      return 'The ' + argumentName + ' has unexpected type of "' + {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + '". Expected argument to be an object with the following ' + ('keys: "' + reducerKeys.join('", "') + '"');
    }
  
    var unexpectedKeys = Object.keys(inputState).filter(function (key) {
      return !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key];
    });
  
    unexpectedKeys.forEach(function (key) {
      unexpectedKeyCache[key] = true;
    });
  
    if (unexpectedKeys.length > 0) {
      return 'Unexpected ' + (unexpectedKeys.length > 1 ? 'keys' : 'key') + ' ' + ('"' + unexpectedKeys.join('", "') + '" found in ' + argumentName + '. ') + 'Expected to find one of the known reducer keys instead: ' + ('"' + reducerKeys.join('", "') + '". Unexpected keys will be ignored.');
    }
  }
  
  function assertReducerShape(reducers) {
    Object.keys(reducers).forEach(function (key) {
      var reducer = reducers[key];
      var initialState = reducer(undefined, { type: _createStore.ActionTypes.INIT });
  
      if (typeof initialState === 'undefined') {
        throw new Error('Reducer "' + key + '" returned undefined during initialization. ' + 'If the state passed to the reducer is undefined, you must ' + 'explicitly return the initial state. The initial state may ' + 'not be undefined. If you don\'t want to set a value for this reducer, ' + 'you can use null instead of undefined.');
      }
  
      var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.');
      if (typeof reducer(undefined, { type: type }) === 'undefined') {
        throw new Error('Reducer "' + key + '" returned undefined when probed with a random type. ' + ('Don\'t try to handle ' + _createStore.ActionTypes.INIT + ' or other actions in "redux/*" ') + 'namespace. They are considered private. Instead, you must return the ' + 'current state for any unknown actions, unless it is undefined, ' + 'in which case you must return the initial state, regardless of the ' + 'action type. The initial state may not be undefined, but can be null.');
      }
    });
  }
  
  /**
   * Turns an object whose values are different reducer functions, into a single
   * reducer function. It will call every child reducer, and gather their results
   * into a single state object, whose keys correspond to the keys of the passed
   * reducer functions.
   *
   * @param {Object} reducers An object whose values correspond to different
   * reducer functions that need to be combined into one. One handy way to obtain
   * it is to use ES6 `import * as reducers` syntax. The reducers may never return
   * undefined for any action. Instead, they should return their initial state
   * if the state passed to them was undefined, and the current state for any
   * unrecognized action.
   *
   * @returns {Function} A reducer function that invokes every reducer inside the
   * passed object, and builds a state object with the same shape.
   */
  function combineReducers(reducers) {
    var reducerKeys = Object.keys(reducers);
    var finalReducers = {};
    for (var i = 0; i < reducerKeys.length; i++) {
      var key = reducerKeys[i];
  
      if ("production" !== 'production') {
        if (typeof reducers[key] === 'undefined') {
          (0, _warning2['default'])('No reducer provided for key "' + key + '"');
        }
      }
  
      if (typeof reducers[key] === 'function') {
        finalReducers[key] = reducers[key];
      }
    }
    var finalReducerKeys = Object.keys(finalReducers);
  
    var unexpectedKeyCache = void 0;
    if ("production" !== 'production') {
      unexpectedKeyCache = {};
    }
  
    var shapeAssertionError = void 0;
    try {
      assertReducerShape(finalReducers);
    } catch (e) {
      shapeAssertionError = e;
    }
  
    return function combination() {
      var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      var action = arguments[1];
  
      if (shapeAssertionError) {
        throw shapeAssertionError;
      }
  
      if ("production" !== 'production') {
        var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);
        if (warningMessage) {
          (0, _warning2['default'])(warningMessage);
        }
      }
  
      var hasChanged = false;
      var nextState = {};
      for (var _i = 0; _i < finalReducerKeys.length; _i++) {
        var _key = finalReducerKeys[_i];
        var reducer = finalReducers[_key];
        var previousStateForKey = state[_key];
        var nextStateForKey = reducer(previousStateForKey, action);
        if (typeof nextStateForKey === 'undefined') {
          var errorMessage = getUndefinedStateErrorMessage(_key, action);
          throw new Error(errorMessage);
        }
        nextState[_key] = nextStateForKey;
        hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
      }
      return hasChanged ? nextState : state;
    };
  }
  },{"./createStore":530,"./utils/warning":532,"lodash/isPlainObject":427}],529:[function(require,module,exports){
  "use strict";
  
  exports.__esModule = true;
  exports["default"] = compose;
  /**
   * Composes single-argument functions from right to left. The rightmost
   * function can take multiple arguments as it provides the signature for
   * the resulting composite function.
   *
   * @param {...Function} funcs The functions to compose.
   * @returns {Function} A function obtained by composing the argument functions
   * from right to left. For example, compose(f, g, h) is identical to doing
   * (...args) => f(g(h(...args))).
   */
  
  function compose() {
    for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
      funcs[_key] = arguments[_key];
    }
  
    if (funcs.length === 0) {
      return function (arg) {
        return arg;
      };
    }
  
    if (funcs.length === 1) {
      return funcs[0];
    }
  
    return funcs.reduce(function (a, b) {
      return function () {
        return a(b.apply(undefined, arguments));
      };
    });
  }
  },{}],530:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.ActionTypes = undefined;
  exports['default'] = createStore;
  
  var _isPlainObject = require('lodash/isPlainObject');
  
  var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
  
  var _symbolObservable = require('symbol-observable');
  
  var _symbolObservable2 = _interopRequireDefault(_symbolObservable);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  
  /**
   * These are private action types reserved by Redux.
   * For any unknown actions, you must return the current state.
   * If the current state is undefined, you must return the initial state.
   * Do not reference these action types directly in your code.
   */
  var ActionTypes = exports.ActionTypes = {
    INIT: '@@redux/INIT'
  
    /**
     * Creates a Redux store that holds the state tree.
     * The only way to change the data in the store is to call `dispatch()` on it.
     *
     * There should only be a single store in your app. To specify how different
     * parts of the state tree respond to actions, you may combine several reducers
     * into a single reducer function by using `combineReducers`.
     *
     * @param {Function} reducer A function that returns the next state tree, given
     * the current state tree and the action to handle.
     *
     * @param {any} [preloadedState] The initial state. You may optionally specify it
     * to hydrate the state from the server in universal apps, or to restore a
     * previously serialized user session.
     * If you use `combineReducers` to produce the root reducer function, this must be
     * an object with the same shape as `combineReducers` keys.
     *
     * @param {Function} [enhancer] The store enhancer. You may optionally specify it
     * to enhance the store with third-party capabilities such as middleware,
     * time travel, persistence, etc. The only store enhancer that ships with Redux
     * is `applyMiddleware()`.
     *
     * @returns {Store} A Redux store that lets you read the state, dispatch actions
     * and subscribe to changes.
     */
  };function createStore(reducer, preloadedState, enhancer) {
    var _ref2;
  
    if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
      enhancer = preloadedState;
      preloadedState = undefined;
    }
  
    if (typeof enhancer !== 'undefined') {
      if (typeof enhancer !== 'function') {
        throw new Error('Expected the enhancer to be a function.');
      }
  
      return enhancer(createStore)(reducer, preloadedState);
    }
  
    if (typeof reducer !== 'function') {
      throw new Error('Expected the reducer to be a function.');
    }
  
    var currentReducer = reducer;
    var currentState = preloadedState;
    var currentListeners = [];
    var nextListeners = currentListeners;
    var isDispatching = false;
  
    function ensureCanMutateNextListeners() {
      if (nextListeners === currentListeners) {
        nextListeners = currentListeners.slice();
      }
    }
  
    /**
     * Reads the state tree managed by the store.
     *
     * @returns {any} The current state tree of your application.
     */
    function getState() {
      return currentState;
    }
  
    /**
     * Adds a change listener. It will be called any time an action is dispatched,
     * and some part of the state tree may potentially have changed. You may then
     * call `getState()` to read the current state tree inside the callback.
     *
     * You may call `dispatch()` from a change listener, with the following
     * caveats:
     *
     * 1. The subscriptions are snapshotted just before every `dispatch()` call.
     * If you subscribe or unsubscribe while the listeners are being invoked, this
     * will not have any effect on the `dispatch()` that is currently in progress.
     * However, the next `dispatch()` call, whether nested or not, will use a more
     * recent snapshot of the subscription list.
     *
     * 2. The listener should not expect to see all state changes, as the state
     * might have been updated multiple times during a nested `dispatch()` before
     * the listener is called. It is, however, guaranteed that all subscribers
     * registered before the `dispatch()` started will be called with the latest
     * state by the time it exits.
     *
     * @param {Function} listener A callback to be invoked on every dispatch.
     * @returns {Function} A function to remove this change listener.
     */
    function subscribe(listener) {
      if (typeof listener !== 'function') {
        throw new Error('Expected listener to be a function.');
      }
  
      var isSubscribed = true;
  
      ensureCanMutateNextListeners();
      nextListeners.push(listener);
  
      return function unsubscribe() {
        if (!isSubscribed) {
          return;
        }
  
        isSubscribed = false;
  
        ensureCanMutateNextListeners();
        var index = nextListeners.indexOf(listener);
        nextListeners.splice(index, 1);
      };
    }
  
    /**
     * Dispatches an action. It is the only way to trigger a state change.
     *
     * The `reducer` function, used to create the store, will be called with the
     * current state tree and the given `action`. Its return value will
     * be considered the **next** state of the tree, and the change listeners
     * will be notified.
     *
     * The base implementation only supports plain object actions. If you want to
     * dispatch a Promise, an Observable, a thunk, or something else, you need to
     * wrap your store creating function into the corresponding middleware. For
     * example, see the documentation for the `redux-thunk` package. Even the
     * middleware will eventually dispatch plain object actions using this method.
     *
     * @param {Object} action A plain object representing “what changed”. It is
     * a good idea to keep actions serializable so you can record and replay user
     * sessions, or use the time travelling `redux-devtools`. An action must have
     * a `type` property which may not be `undefined`. It is a good idea to use
     * string constants for action types.
     *
     * @returns {Object} For convenience, the same action object you dispatched.
     *
     * Note that, if you use a custom middleware, it may wrap `dispatch()` to
     * return something else (for example, a Promise you can await).
     */
    function dispatch(action) {
      if (!(0, _isPlainObject2['default'])(action)) {
        throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.');
      }
  
      if (typeof action.type === 'undefined') {
        throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?');
      }
  
      if (isDispatching) {
        throw new Error('Reducers may not dispatch actions.');
      }
  
      try {
        isDispatching = true;
        currentState = currentReducer(currentState, action);
      } finally {
        isDispatching = false;
      }
  
      var listeners = currentListeners = nextListeners;
      for (var i = 0; i < listeners.length; i++) {
        var listener = listeners[i];
        listener();
      }
  
      return action;
    }
  
    /**
     * Replaces the reducer currently used by the store to calculate the state.
     *
     * You might need this if your app implements code splitting and you want to
     * load some of the reducers dynamically. You might also need this if you
     * implement a hot reloading mechanism for Redux.
     *
     * @param {Function} nextReducer The reducer for the store to use instead.
     * @returns {void}
     */
    function replaceReducer(nextReducer) {
      if (typeof nextReducer !== 'function') {
        throw new Error('Expected the nextReducer to be a function.');
      }
  
      currentReducer = nextReducer;
      dispatch({ type: ActionTypes.INIT });
    }
  
    /**
     * Interoperability point for observable/reactive libraries.
     * @returns {observable} A minimal observable of state changes.
     * For more information, see the observable proposal:
     * https://github.com/tc39/proposal-observable
     */
    function observable() {
      var _ref;
  
      var outerSubscribe = subscribe;
      return _ref = {
        /**
         * The minimal observable subscription method.
         * @param {Object} observer Any object that can be used as an observer.
         * The observer object should have a `next` method.
         * @returns {subscription} An object with an `unsubscribe` method that can
         * be used to unsubscribe the observable from the store, and prevent further
         * emission of values from the observable.
         */
        subscribe: function subscribe(observer) {
          if (typeof observer !== 'object') {
            throw new TypeError('Expected the observer to be an object.');
          }
  
          function observeState() {
            if (observer.next) {
              observer.next(getState());
            }
          }
  
          observeState();
          var unsubscribe = outerSubscribe(observeState);
          return { unsubscribe: unsubscribe };
        }
      }, _ref[_symbolObservable2['default']] = function () {
        return this;
      }, _ref;
    }
  
    // When a store is created, an "INIT" action is dispatched so that every
    // reducer returns their initial state. This effectively populates
    // the initial state tree.
    dispatch({ type: ActionTypes.INIT });
  
    return _ref2 = {
      dispatch: dispatch,
      subscribe: subscribe,
      getState: getState,
      replaceReducer: replaceReducer
    }, _ref2[_symbolObservable2['default']] = observable, _ref2;
  }
  },{"lodash/isPlainObject":427,"symbol-observable":567}],531:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.compose = exports.applyMiddleware = exports.bindActionCreators = exports.combineReducers = exports.createStore = undefined;
  
  var _createStore = require('./createStore');
  
  var _createStore2 = _interopRequireDefault(_createStore);
  
  var _combineReducers = require('./combineReducers');
  
  var _combineReducers2 = _interopRequireDefault(_combineReducers);
  
  var _bindActionCreators = require('./bindActionCreators');
  
  var _bindActionCreators2 = _interopRequireDefault(_bindActionCreators);
  
  var _applyMiddleware = require('./applyMiddleware');
  
  var _applyMiddleware2 = _interopRequireDefault(_applyMiddleware);
  
  var _compose = require('./compose');
  
  var _compose2 = _interopRequireDefault(_compose);
  
  var _warning = require('./utils/warning');
  
  var _warning2 = _interopRequireDefault(_warning);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  
  /*
  * This is a dummy function to check if the function name has been altered by minification.
  * If the function has been minified and NODE_ENV !== 'production', warn the user.
  */
  function isCrushed() {}
  
  if ("production" !== 'production' && typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed') {
    (0, _warning2['default'])('You are currently using minified code outside of NODE_ENV === \'production\'. ' + 'This means that you are running a slower development build of Redux. ' + 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' + 'to ensure you have the correct code for your production build.');
  }
  
  exports.createStore = _createStore2['default'];
  exports.combineReducers = _combineReducers2['default'];
  exports.bindActionCreators = _bindActionCreators2['default'];
  exports.applyMiddleware = _applyMiddleware2['default'];
  exports.compose = _compose2['default'];
  },{"./applyMiddleware":526,"./bindActionCreators":527,"./combineReducers":528,"./compose":529,"./createStore":530,"./utils/warning":532}],532:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports['default'] = warning;
  /**
   * Prints a warning in the console if it exists.
   *
   * @param {String} message The warning message.
   * @returns {void}
   */
  function warning(message) {
    /* eslint-disable no-console */
    if (typeof console !== 'undefined' && typeof console.error === 'function') {
      console.error(message);
    }
    /* eslint-enable no-console */
    try {
      // This error was thrown as a convenience so that if you enable
      // "break on all exceptions" in your console,
      // it would pause the execution at this line.
      throw new Error(message);
      /* eslint-disable no-empty */
    } catch (e) {}
    /* eslint-enable no-empty */
  }
  },{}],533:[function(require,module,exports){
  'use strict';
  
  exports.__esModule = true;
  exports.defaultMemoize = defaultMemoize;
  exports.createSelectorCreator = createSelectorCreator;
  exports.createStructuredSelector = createStructuredSelector;
  
  function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
  
  function defaultEqualityCheck(a, b) {
    return a === b;
  }
  
  function defaultMemoize(func) {
    var equalityCheck = arguments.length <= 1 || arguments[1] === undefined ? defaultEqualityCheck : arguments[1];
  
    var lastArgs = null;
    var lastResult = null;
    return function () {
      for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
  
      if (lastArgs === null || lastArgs.length !== args.length || !args.every(function (value, index) {
        return equalityCheck(value, lastArgs[index]);
      })) {
        lastResult = func.apply(undefined, args);
      }
      lastArgs = args;
      return lastResult;
    };
  }
  
  function getDependencies(funcs) {
    var dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs;
  
    if (!dependencies.every(function (dep) {
      return typeof dep === 'function';
    })) {
      var dependencyTypes = dependencies.map(function (dep) {
        return typeof dep;
      }).join(', ');
      throw new Error('Selector creators expect all input-selectors to be functions, ' + ('instead received the following types: [' + dependencyTypes + ']'));
    }
  
    return dependencies;
  }
  
  function createSelectorCreator(memoize) {
    for (var _len2 = arguments.length, memoizeOptions = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
      memoizeOptions[_key2 - 1] = arguments[_key2];
    }
  
    return function () {
      for (var _len3 = arguments.length, funcs = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
        funcs[_key3] = arguments[_key3];
      }
  
      var recomputations = 0;
      var resultFunc = funcs.pop();
      var dependencies = getDependencies(funcs);
  
      var memoizedResultFunc = memoize.apply(undefined, [function () {
        recomputations++;
        return resultFunc.apply(undefined, arguments);
      }].concat(memoizeOptions));
  
      var selector = function selector(state, props) {
        for (var _len4 = arguments.length, args = Array(_len4 > 2 ? _len4 - 2 : 0), _key4 = 2; _key4 < _len4; _key4++) {
          args[_key4 - 2] = arguments[_key4];
        }
  
        var params = dependencies.map(function (dependency) {
          return dependency.apply(undefined, [state, props].concat(args));
        });
        return memoizedResultFunc.apply(undefined, _toConsumableArray(params));
      };
  
      selector.resultFunc = resultFunc;
      selector.recomputations = function () {
        return recomputations;
      };
      selector.resetRecomputations = function () {
        return recomputations = 0;
      };
      return selector;
    };
  }
  
  var createSelector = exports.createSelector = createSelectorCreator(defaultMemoize);
  
  function createStructuredSelector(selectors) {
    var selectorCreator = arguments.length <= 1 || arguments[1] === undefined ? createSelector : arguments[1];
  
    if (typeof selectors !== 'object') {
      throw new Error('createStructuredSelector expects first argument to be an object ' + ('where each property is a selector, instead received a ' + typeof selectors));
    }
    var objectKeys = Object.keys(selectors);
    return selectorCreator(objectKeys.map(function (key) {
      return selectors[key];
    }), function () {
      for (var _len5 = arguments.length, values = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
        values[_key5] = arguments[_key5];
      }
  
      return values.reduce(function (composition, value, index) {
        composition[objectKeys[index]] = value;
        return composition;
      }, {});
    });
  }
  },{}],534:[function(require,module,exports){
  /*
  Slick Finder
  */"use strict"
  
  // Notable changes from Slick.Finder 1.0.x
  
  // faster bottom -> up expression matching
  // prefers mental sanity over *obsessive compulsive* milliseconds savings
  // uses prototypes instead of objects
  // tries to use matchesSelector smartly, whenever available
  // can populate objects as well as arrays
  // lots of stuff is broken or not implemented
  
  var parse = require("./parser")
  
  // utilities
  
  var index = 0,
      counter = document.__counter = (parseInt(document.__counter || -1, 36) + 1).toString(36),
      key = "uid:" + counter
  
  var uniqueID = function(n, xml){
      if (n === window) return "window"
      if (n === document) return "document"
      if (n === document.documentElement) return "html"
  
      if (xml) {
          var uid = n.getAttribute(key)
          if (!uid) {
              uid = (index++).toString(36)
              n.setAttribute(key, uid)
          }
          return uid
      } else {
          return n[key] || (n[key] = (index++).toString(36))
      }
  }
  
  var uniqueIDXML = function(n) {
      return uniqueID(n, true)
  }
  
  var isArray = Array.isArray || function(object){
      return Object.prototype.toString.call(object) === "[object Array]"
  }
  
  // tests
  
  var uniqueIndex = 0;
  
  var HAS = {
  
      GET_ELEMENT_BY_ID: function(test, id){
          id = "slick_" + (uniqueIndex++);
          // checks if the document has getElementById, and it works
          test.innerHTML = '<a id="' + id + '"></a>'
          return !!this.getElementById(id)
      },
  
      QUERY_SELECTOR: function(test){
          // this supposedly fixes a webkit bug with matchesSelector / querySelector & nth-child
          test.innerHTML = '_<style>:nth-child(2){}</style>'
  
          // checks if the document has querySelectorAll, and it works
          test.innerHTML = '<a class="MiX"></a>'
  
          return test.querySelectorAll('.MiX').length === 1
      },
  
      EXPANDOS: function(test, id){
          id = "slick_" + (uniqueIndex++);
          // checks if the document has elements that support expandos
          test._custom_property_ = id
          return test._custom_property_ === id
      },
  
      // TODO: use this ?
  
      // CHECKED_QUERY_SELECTOR: function(test){
      //
      //     // checks if the document supports the checked query selector
      //     test.innerHTML = '<select><option selected="selected">a</option></select>'
      //     return test.querySelectorAll(':checked').length === 1
      // },
  
      // TODO: use this ?
  
      // EMPTY_ATTRIBUTE_QUERY_SELECTOR: function(test){
      //
      //     // checks if the document supports the empty attribute query selector
      //     test.innerHTML = '<a class=""></a>'
      //     return test.querySelectorAll('[class*=""]').length === 1
      // },
  
      MATCHES_SELECTOR: function(test){
  
          test.className = "MiX"
  
          // checks if the document has matchesSelector, and we can use it.
  
          var matches = test.matchesSelector || test.mozMatchesSelector || test.webkitMatchesSelector
  
          // if matchesSelector trows errors on incorrect syntax we can use it
          if (matches) try {
              matches.call(test, ':slick')
          } catch(e){
              // just as a safety precaution, also test if it works on mixedcase (like querySelectorAll)
              return matches.call(test, ".MiX") ? matches : false
          }
  
          return false
      },
  
      GET_ELEMENTS_BY_CLASS_NAME: function(test){
          test.innerHTML = '<a class="f"></a><a class="b"></a>'
          if (test.getElementsByClassName('b').length !== 1) return false
  
          test.firstChild.className = 'b'
          if (test.getElementsByClassName('b').length !== 2) return false
  
          // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one
          test.innerHTML = '<a class="a"></a><a class="f b a"></a>'
          if (test.getElementsByClassName('a').length !== 2) return false
  
          // tests passed
          return true
      },
  
      // no need to know
  
      // GET_ELEMENT_BY_ID_NOT_NAME: function(test, id){
      //     test.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>'
      //     return this.getElementById(id) !== test.firstChild
      // },
  
      // this is always checked for and fixed
  
      // STAR_GET_ELEMENTS_BY_TAG_NAME: function(test){
      //
      //     // IE returns comment nodes for getElementsByTagName('*') for some documents
      //     test.appendChild(this.createComment(''))
      //     if (test.getElementsByTagName('*').length > 0) return false
      //
      //     // IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents
      //     test.innerHTML = 'foo</foo>'
      //     if (test.getElementsByTagName('*').length) return false
      //
      //     // tests passed
      //     return true
      // },
  
      // this is always checked for and fixed
  
      // STAR_QUERY_SELECTOR: function(test){
      //
      //     // returns closed nodes (EG:"</foo>") for querySelector('*') for some documents
      //     test.innerHTML = 'foo</foo>'
      //     return !!(test.querySelectorAll('*').length)
      // },
  
      GET_ATTRIBUTE: function(test){
          // tests for working getAttribute implementation
          var shout = "fus ro dah"
          test.innerHTML = '<a class="' + shout + '"></a>'
          return test.firstChild.getAttribute('class') === shout
      }
  
  }
  
  // Finder
  
  var Finder = function Finder(document){
  
      this.document        = document
      var root = this.root = document.documentElement
      this.tested          = {}
  
      // uniqueID
  
      this.uniqueID = this.has("EXPANDOS") ? uniqueID : uniqueIDXML
  
      // getAttribute
  
      this.getAttribute = (this.has("GET_ATTRIBUTE")) ? function(node, name){
  
          return node.getAttribute(name)
  
      } : function(node, name){
  
          node = node.getAttributeNode(name)
          return (node && node.specified) ? node.value : null
  
      }
  
      // hasAttribute
  
      this.hasAttribute = (root.hasAttribute) ? function(node, attribute){
  
          return node.hasAttribute(attribute)
  
      } : function(node, attribute) {
  
          node = node.getAttributeNode(attribute)
          return !!(node && node.specified)
  
      }
  
      // contains
  
      this.contains = (document.contains && root.contains) ? function(context, node){
  
          return context.contains(node)
  
      } : (root.compareDocumentPosition) ? function(context, node){
  
          return context === node || !!(context.compareDocumentPosition(node) & 16)
  
      } : function(context, node){
  
          do {
              if (node === context) return true
          } while ((node = node.parentNode))
  
          return false
      }
  
      // sort
      // credits to Sizzle (http://sizzlejs.com/)
  
      this.sorter = (root.compareDocumentPosition) ? function(a, b){
  
          if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0
          return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1
  
      } : ('sourceIndex' in root) ? function(a, b){
  
          if (!a.sourceIndex || !b.sourceIndex) return 0
          return a.sourceIndex - b.sourceIndex
  
      } : (document.createRange) ? function(a, b){
  
          if (!a.ownerDocument || !b.ownerDocument) return 0
          var aRange = a.ownerDocument.createRange(),
              bRange = b.ownerDocument.createRange()
  
          aRange.setStart(a, 0)
          aRange.setEnd(a, 0)
          bRange.setStart(b, 0)
          bRange.setEnd(b, 0)
          return aRange.compareBoundaryPoints(Range.START_TO_END, bRange)
  
      } : null
  
      this.failed = {}
  
      var nativeMatches = this.has("MATCHES_SELECTOR")
  
      if (nativeMatches) this.matchesSelector = function(node, expression){
  
          if (this.failed[expression]) return null
  
          try {
              return nativeMatches.call(node, expression)
          } catch(e){
              if (slick.debug) console.warn("matchesSelector failed on " + expression)
              this.failed[expression] = true
              return null
          }
  
      }
  
      if (this.has("QUERY_SELECTOR")){
  
          this.querySelectorAll = function(node, expression){
  
              if (this.failed[expression]) return true
  
              var result, _id, _expression, _combinator, _node
  
  
              // non-document rooted QSA
              // credits to Andrew Dupont
  
              if (node !== this.document){
  
                  _combinator = expression[0].combinator
  
                  _id         = node.getAttribute("id")
                  _expression = expression
  
                  if (!_id){
                      _node = node
                      _id = "__slick__"
                      _node.setAttribute("id", _id)
                  }
  
                  expression = "#" + _id + " " + _expression
  
  
                  // these combinators need a parentNode due to how querySelectorAll works, which is:
                  // finding all the elements that match the given selector
                  // then filtering by the ones that have the specified element as an ancestor
                  if (_combinator.indexOf("~") > -1 || _combinator.indexOf("+") > -1){
  
                      node = node.parentNode
                      if (!node) result = true
                      // if node has no parentNode, we return "true" as if it failed, without polluting the failed cache
  
                  }
  
              }
  
              if (!result) try {
                  result = node.querySelectorAll(expression.toString())
              } catch(e){
                  if (slick.debug) console.warn("querySelectorAll failed on " + (_expression || expression))
                  result = this.failed[_expression || expression] = true
              }
  
              if (_node) _node.removeAttribute("id")
  
              return result
  
          }
  
      }
  
  }
  
  Finder.prototype.has = function(FEATURE){
  
      var tested        = this.tested,
          testedFEATURE = tested[FEATURE]
  
      if (testedFEATURE != null) return testedFEATURE
  
      var root     = this.root,
          document = this.document,
          testNode = document.createElement("div")
  
      testNode.setAttribute("style", "display: none;")
  
      root.appendChild(testNode)
  
      var TEST = HAS[FEATURE], result = false
  
      if (TEST) try {
          result = TEST.call(document, testNode)
      } catch(e){}
  
      if (slick.debug && !result) console.warn("document has no " + FEATURE)
  
      root.removeChild(testNode)
  
      return tested[FEATURE] = result
  
  }
  
  var combinators = {
  
      " ": function(node, part, push){
  
          var item, items
  
          var noId = !part.id, noTag = !part.tag, noClass = !part.classes
  
          if (part.id && node.getElementById && this.has("GET_ELEMENT_BY_ID")){
              item = node.getElementById(part.id)
  
              // return only if id is found, else keep checking
              // might be a tad slower on non-existing ids, but less insane
  
              if (item && item.getAttribute('id') === part.id){
                  items = [item]
                  noId = true
                  // if tag is star, no need to check it in match()
                  if (part.tag === "*") noTag = true
              }
          }
  
          if (!items){
  
              if (part.classes && node.getElementsByClassName && this.has("GET_ELEMENTS_BY_CLASS_NAME")){
                  items = node.getElementsByClassName(part.classList)
                  noClass = true
                  // if tag is star, no need to check it in match()
                  if (part.tag === "*") noTag = true
              } else {
                  items = node.getElementsByTagName(part.tag)
                  // if tag is star, need to check it in match because it could select junk, boho
                  if (part.tag !== "*") noTag = true
              }
  
              if (!items || !items.length) return false
  
          }
  
          for (var i = 0; item = items[i++];)
              if ((noTag && noId && noClass && !part.attributes && !part.pseudos) || this.match(item, part, noTag, noId, noClass))
                  push(item)
  
          return true
  
      },
  
      ">": function(node, part, push){ // direct children
          if ((node = node.firstChild)) do {
              if (node.nodeType == 1 && this.match(node, part)) push(node)
          } while ((node = node.nextSibling))
      },
  
      "+": function(node, part, push){ // next sibling
          while ((node = node.nextSibling)) if (node.nodeType == 1){
              if (this.match(node, part)) push(node)
              break
          }
      },
  
      "^": function(node, part, push){ // first child
          node = node.firstChild
          if (node){
              if (node.nodeType === 1){
                  if (this.match(node, part)) push(node)
              } else {
                  combinators['+'].call(this, node, part, push)
              }
          }
      },
  
      "~": function(node, part, push){ // next siblings
          while ((node = node.nextSibling)){
              if (node.nodeType === 1 && this.match(node, part)) push(node)
          }
      },
  
      "++": function(node, part, push){ // next sibling and previous sibling
          combinators['+'].call(this, node, part, push)
          combinators['!+'].call(this, node, part, push)
      },
  
      "~~": function(node, part, push){ // next siblings and previous siblings
          combinators['~'].call(this, node, part, push)
          combinators['!~'].call(this, node, part, push)
      },
  
      "!": function(node, part, push){ // all parent nodes up to document
          while ((node = node.parentNode)) if (node !== this.document && this.match(node, part)) push(node)
      },
  
      "!>": function(node, part, push){ // direct parent (one level)
          node = node.parentNode
          if (node !== this.document && this.match(node, part)) push(node)
      },
  
      "!+": function(node, part, push){ // previous sibling
          while ((node = node.previousSibling)) if (node.nodeType == 1){
              if (this.match(node, part)) push(node)
              break
          }
      },
  
      "!^": function(node, part, push){ // last child
          node = node.lastChild
          if (node){
              if (node.nodeType == 1){
                  if (this.match(node, part)) push(node)
              } else {
                  combinators['!+'].call(this, node, part, push)
              }
          }
      },
  
      "!~": function(node, part, push){ // previous siblings
          while ((node = node.previousSibling)){
              if (node.nodeType === 1 && this.match(node, part)) push(node)
          }
      }
  
  }
  
  Finder.prototype.search = function(context, expression, found){
  
      if (!context) context = this.document
      else if (!context.nodeType && context.document) context = context.document
  
      var expressions = parse(expression)
  
      // no expressions were parsed. todo: is this really necessary?
      if (!expressions || !expressions.length) throw new Error("invalid expression")
  
      if (!found) found = []
  
      var uniques, push = isArray(found) ? function(node){
          found[found.length] = node
      } : function(node){
          found[found.length++] = node
      }
  
      // if there is more than one expression we need to check for duplicates when we push to found
      // this simply saves the old push and wraps it around an uid dupe check.
      if (expressions.length > 1){
          uniques = {}
          var plush = push
          push = function(node){
              var uid = uniqueID(node)
              if (!uniques[uid]){
                  uniques[uid] = true
                  plush(node)
              }
          }
      }
  
      // walker
  
      var node, nodes, part
  
      main: for (var i = 0; expression = expressions[i++];){
  
          // querySelector
  
          // TODO: more functional tests
  
          // if there is querySelectorAll (and the expression does not fail) use it.
          if (!slick.noQSA && this.querySelectorAll){
  
              nodes = this.querySelectorAll(context, expression)
              if (nodes !== true){
                  if (nodes && nodes.length) for (var j = 0; node = nodes[j++];) if (node.nodeName > '@'){
                      push(node)
                  }
                  continue main
              }
          }
  
          // if there is only one part in the expression we don't need to check each part for duplicates.
          // todo: this might be too naive. while solid, there can be expression sequences that do not
          // produce duplicates. "body div" for instance, can never give you each div more than once.
          // "body div a" on the other hand might.
          if (expression.length === 1){
  
              part = expression[0]
              combinators[part.combinator].call(this, context, part, push)
  
          } else {
  
              var cs = [context], c, f, u, p = function(node){
                  var uid = uniqueID(node)
                  if (!u[uid]){
                      u[uid] = true
                      f[f.length] = node
                  }
              }
  
              // loop the expression parts
              for (var j = 0; part = expression[j++];){
                  f = []; u = {}
                  // loop the contexts
                  for (var k = 0; c = cs[k++];) combinators[part.combinator].call(this, c, part, p)
                  // nothing was found, the expression failed, continue to the next expression.
                  if (!f.length) continue main
                  cs = f // set the contexts for future parts (if any)
              }
  
              if (i === 0) found = f // first expression. directly set found.
              else for (var l = 0; l < f.length; l++) push(f[l]) // any other expression needs to push to found.
          }
  
      }
  
      if (uniques && found && found.length > 1) this.sort(found)
  
      return found
  
  }
  
  Finder.prototype.sort = function(nodes){
      return this.sorter ? Array.prototype.sort.call(nodes, this.sorter) : nodes
  }
  
  // TODO: most of these pseudo selectors include <html> and qsa doesnt. fixme.
  
  var pseudos = {
  
  
      // TODO: returns different results than qsa empty.
  
      'empty': function(){
          return !(this && this.nodeType === 1) && !(this.innerText || this.textContent || '').length
      },
  
      'not': function(expression){
          return !slick.matches(this, expression)
      },
  
      'contains': function(text){
          return (this.innerText || this.textContent || '').indexOf(text) > -1
      },
  
      'first-child': function(){
          var node = this
          while ((node = node.previousSibling)) if (node.nodeType == 1) return false
          return true
      },
  
      'last-child': function(){
          var node = this
          while ((node = node.nextSibling)) if (node.nodeType == 1) return false
          return true
      },
  
      'only-child': function(){
          var prev = this
          while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false
  
          var next = this
          while ((next = next.nextSibling)) if (next.nodeType == 1) return false
  
          return true
      },
  
      'first-of-type': function(){
          var node = this, nodeName = node.nodeName
          while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false
          return true
      },
  
      'last-of-type': function(){
          var node = this, nodeName = node.nodeName
          while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false
          return true
      },
  
      'only-of-type': function(){
          var prev = this, nodeName = this.nodeName
          while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false
          var next = this
          while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false
          return true
      },
  
      'enabled': function(){
          return !this.disabled
      },
  
      'disabled': function(){
          return this.disabled
      },
  
      'checked': function(){
          return this.checked || this.selected
      },
  
      'selected': function(){
          return this.selected
      },
  
      'focus': function(){
          var doc = this.ownerDocument
          return doc.activeElement === this && (this.href || this.type || slick.hasAttribute(this, 'tabindex'))
      },
  
      'root': function(){
          return (this === this.ownerDocument.documentElement)
      }
  
  }
  
  Finder.prototype.match = function(node, bit, noTag, noId, noClass){
  
      // TODO: more functional tests ?
  
      if (!slick.noQSA && this.matchesSelector){
          var matches = this.matchesSelector(node, bit)
          if (matches !== null) return matches
      }
  
      // normal matching
  
      if (!noTag && bit.tag){
  
          var nodeName = node.nodeName.toLowerCase()
          if (bit.tag === "*"){
              if (nodeName < "@") return false
          } else if (nodeName != bit.tag){
              return false
          }
  
      }
  
      if (!noId && bit.id && node.getAttribute('id') !== bit.id) return false
  
      var i, part
  
      if (!noClass && bit.classes){
  
          var className = this.getAttribute(node, "class")
          if (!className) return false
  
          for (part in bit.classes) if (!RegExp('(^|\\s)' + bit.classes[part] + '(\\s|$)').test(className)) return false
      }
  
      var name, value
  
      if (bit.attributes) for (i = 0; part = bit.attributes[i++];){
  
          var operator  = part.operator,
              escaped   = part.escapedValue
  
          name  = part.name
          value = part.value
  
          if (!operator){
  
              if (!this.hasAttribute(node, name)) return false
  
          } else {
  
              var actual = this.getAttribute(node, name)
              if (actual == null) return false
  
              switch (operator){
                  case '^=' : if (!RegExp(      '^' + escaped            ).test(actual)) return false; break
                  case '$=' : if (!RegExp(            escaped + '$'      ).test(actual)) return false; break
                  case '~=' : if (!RegExp('(^|\\s)' + escaped + '(\\s|$)').test(actual)) return false; break
                  case '|=' : if (!RegExp(      '^' + escaped + '(-|$)'  ).test(actual)) return false; break
  
                  case '='  : if (actual !== value) return false; break
                  case '*=' : if (actual.indexOf(value) === -1) return false; break
                  default   : return false
              }
  
          }
      }
  
      if (bit.pseudos) for (i = 0; part = bit.pseudos[i++];){
  
          name  = part.name
          value = part.value
  
          if (pseudos[name]) return pseudos[name].call(node, value)
  
          if (value != null){
              if (this.getAttribute(node, name) !== value) return false
          } else {
              if (!this.hasAttribute(node, name)) return false
          }
  
      }
  
      return true
  
  }
  
  Finder.prototype.matches = function(node, expression){
  
      var expressions = parse(expression)
  
      if (expressions.length === 1 && expressions[0].length === 1){ // simplest match
          return this.match(node, expressions[0][0])
      }
  
      // TODO: more functional tests ?
  
      if (!slick.noQSA && this.matchesSelector){
          var matches = this.matchesSelector(node, expressions)
          if (matches !== null) return matches
      }
  
      var nodes = this.search(this.document, expression, {length: 0})
  
      for (var i = 0, res; res = nodes[i++];) if (node === res) return true
      return false
  
  }
  
  var finders = {}
  
  var finder = function(context){
      var doc = context || document
      if (doc.ownerDocument) doc = doc.ownerDocument
      else if (doc.document) doc = doc.document
  
      if (doc.nodeType !== 9) throw new TypeError("invalid document")
  
      var uid = uniqueID(doc)
      return finders[uid] || (finders[uid] = new Finder(doc))
  }
  
  // ... API ...
  
  var slick = function(expression, context){
      return slick.search(expression, context)
  }
  
  slick.search = function(expression, context, found){
      return finder(context).search(context, expression, found)
  }
  
  slick.find = function(expression, context){
      return finder(context).search(context, expression)[0] || null
  }
  
  slick.getAttribute = function(node, name){
      return finder(node).getAttribute(node, name)
  }
  
  slick.hasAttribute = function(node, name){
      return finder(node).hasAttribute(node, name)
  }
  
  slick.contains = function(context, node){
      return finder(context).contains(context, node)
  }
  
  slick.matches = function(node, expression){
      return finder(node).matches(node, expression)
  }
  
  slick.sort = function(nodes){
      if (nodes && nodes.length > 1) finder(nodes[0]).sort(nodes)
      return nodes
  }
  
  slick.parse = parse;
  
  // slick.debug = true
  // slick.noQSA  = true
  
  module.exports = slick
  
  },{"./parser":536}],535:[function(require,module,exports){
  (function (global){
  /*
  slick
  */"use strict"
  
  module.exports = "document" in global ? require("./finder") : { parse: require("./parser") }
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"./finder":534,"./parser":536}],536:[function(require,module,exports){
  /*
  Slick Parser
   - originally created by the almighty Thomas Aylott <@subtlegradient> (http://subtlegradient.com)
  */"use strict"
  
  // Notable changes from Slick.Parser 1.0.x
  
  // The parser now uses 2 classes: Expressions and Expression
  // `new Expressions` produces an array-like object containing a list of Expression objects
  // - Expressions::toString() produces a cleaned up expressions string
  // `new Expression` produces an array-like object
  // - Expression::toString() produces a cleaned up expression string
  // The only exposed method is parse, which produces a (cached) `new Expressions` instance
  // parsed.raw is no longer present, use .toString()
  // parsed.expression is now useless, just use the indices
  // parsed.reverse() has been removed for now, due to its apparent uselessness
  // Other changes in the Expressions object:
  // - classNames are now unique, and save both escaped and unescaped values
  // - attributes now save both escaped and unescaped values
  // - pseudos now save both escaped and unescaped values
  
  var escapeRe   = /([-.*+?^${}()|[\]\/\\])/g,
      unescapeRe = /\\/g
  
  var escape = function(string){
      // XRegExp v2.0.0-beta-3
      // « https://github.com/slevithan/XRegExp/blob/master/src/xregexp.js
      return (string + "").replace(escapeRe, '\\$1')
  }
  
  var unescape = function(string){
      return (string + "").replace(unescapeRe, '')
  }
  
  var slickRe = RegExp(
  /*
  #!/usr/bin/env ruby
  puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'')
  __END__
      "(?x)^(?:\
        \\s* ( , ) \\s*               # Separator          \n\
      | \\s* ( <combinator>+ ) \\s*   # Combinator         \n\
      |      ( \\s+ )                 # CombinatorChildren \n\
      |      ( <unicode>+ | \\* )     # Tag                \n\
      | \\#  ( <unicode>+       )     # ID                 \n\
      | \\.  ( <unicode>+       )     # ClassName          \n\
      |                               # Attribute          \n\
      \\[  \
          \\s* (<unicode1>+)  (?:  \
              \\s* ([*^$!~|]?=)  (?:  \
                  \\s* (?:\
                      ([\"']?)(.*?)\\9 \
                  )\
              )  \
          )?  \\s*  \
      \\](?!\\]) \n\
      |   :+ ( <unicode>+ )(?:\
      \\( (?:\
          (?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\
      ) \\)\
      )?\
      )"
  */
  "^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)"
      .replace(/<combinator>/, '[' + escape(">+~`!@$%^&={}\\;</") + ']')
      .replace(/<unicode>/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
      .replace(/<unicode1>/g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
  )
  
  // Part
  
  var Part = function Part(combinator){
      this.combinator = combinator || " "
      this.tag = "*"
  }
  
  Part.prototype.toString = function(){
  
      if (!this.raw){
  
          var xpr = "", k, part
  
          xpr += this.tag || "*"
          if (this.id) xpr += "#" + this.id
          if (this.classes) xpr += "." + this.classList.join(".")
          if (this.attributes) for (k = 0; part = this.attributes[k++];){
              xpr += "[" + part.name + (part.operator ? part.operator + '"' + part.value + '"' : '') + "]"
          }
          if (this.pseudos) for (k = 0; part = this.pseudos[k++];){
              xpr += ":" + part.name
              if (part.value) xpr += "(" + part.value + ")"
          }
  
          this.raw = xpr
  
      }
  
      return this.raw
  }
  
  // Expression
  
  var Expression = function Expression(){
      this.length = 0
  }
  
  Expression.prototype.toString = function(){
  
      if (!this.raw){
  
          var xpr = ""
  
          for (var j = 0, bit; bit = this[j++];){
              if (j !== 1) xpr += " "
              if (bit.combinator !== " ") xpr += bit.combinator + " "
              xpr += bit
          }
  
          this.raw = xpr
  
      }
  
      return this.raw
  }
  
  var replacer = function(
      rawMatch,
  
      separator,
      combinator,
      combinatorChildren,
  
      tagName,
      id,
      className,
  
      attributeKey,
      attributeOperator,
      attributeQuote,
      attributeValue,
  
      pseudoMarker,
      pseudoClass,
      pseudoQuote,
      pseudoClassQuotedValue,
      pseudoClassValue
  ){
  
      var expression, current
  
      if (separator || !this.length){
          expression = this[this.length++] = new Expression
          if (separator) return ''
      }
  
      if (!expression) expression = this[this.length - 1]
  
      if (combinator || combinatorChildren || !expression.length){
          current = expression[expression.length++] = new Part(combinator)
      }
  
      if (!current) current = expression[expression.length - 1]
  
      if (tagName){
  
          current.tag = unescape(tagName)
  
      } else if (id){
  
          current.id = unescape(id)
  
      } else if (className){
  
          var unescaped = unescape(className)
  
          var classes = current.classes || (current.classes = {})
          if (!classes[unescaped]){
              classes[unescaped] = escape(className)
              var classList = current.classList || (current.classList = [])
              classList.push(unescaped)
              classList.sort()
          }
  
      } else if (pseudoClass){
  
          pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue
  
          ;(current.pseudos || (current.pseudos = [])).push({
              type         : pseudoMarker.length == 1 ? 'class' : 'element',
              name         : unescape(pseudoClass),
              escapedName  : escape(pseudoClass),
              value        : pseudoClassValue ? unescape(pseudoClassValue) : null,
              escapedValue : pseudoClassValue ? escape(pseudoClassValue) : null
          })
  
      } else if (attributeKey){
  
          attributeValue = attributeValue ? escape(attributeValue) : null
  
          ;(current.attributes || (current.attributes = [])).push({
              operator     : attributeOperator,
              name         : unescape(attributeKey),
              escapedName  : escape(attributeKey),
              value        : attributeValue ? unescape(attributeValue) : null,
              escapedValue : attributeValue ? escape(attributeValue) : null
          })
  
      }
  
      return ''
  
  }
  
  // Expressions
  
  var Expressions = function Expressions(expression){
      this.length = 0
  
      var self = this
  
      var original = expression, replaced
  
      while (expression){
          replaced = expression.replace(slickRe, function(){
              return replacer.apply(self, arguments)
          })
          if (replaced === expression) throw new Error(original + ' is an invalid expression')
          expression = replaced
      }
  }
  
  Expressions.prototype.toString = function(){
      if (!this.raw){
          var expressions = []
          for (var i = 0, expression; expression = this[i++];) expressions.push(expression)
          this.raw = expressions.join(", ")
      }
  
      return this.raw
  }
  
  var cache = {}
  
  var parse = function(expression){
      if (expression == null) return null
      expression = ('' + expression).replace(/^\s+|\s+$/g, '')
      return cache[expression] || (cache[expression] = new Expressions(expression))
  }
  
  module.exports = parse
  
  },{}],537:[function(require,module,exports){
  
  /**
   * `Batch' constructor
   *
   * @api public
   * @param {Number} max - optional (Default: Infinity)
   */
  
  module.exports = Batch;
  function Batch (max) {
    if (!(this instanceof Batch)) {
      return new Batch(max);
    }
  
    var self = this;
  
    this.m = 0; // max
    this.c = 0; // concurrency
    this.jobs = [];
  
    this.concurrency(Infinity);
    this.max(max);
  
    Object.defineProperty(this, 'length', {
      get: function () {
        return self.jobs.length;
      }
    });
  }
  
  /**
   * Set batch concurrency
   *
   * @api public
   * @param {Number} n
   */
  
  Batch.prototype.concurrency = function (n) {
    if (n > 0) {
      this.c = n;
    }
    return this;
  };
  
  /**
   * Set max job limit
   *
   * @api publc
   * @param {Number} n
   */
  
  Batch.prototype.max = function (n) {
    this.m = 'number' == typeof n && n > 0 ? n : Infinity;
    return this;
  };
  
  /**
   * Push job onto queue
   *
   * @api publc
   * @param {Function} fn
   */
  
  Batch.prototype.push = function (fn) {
    if ('function' != typeof fn) {
      throw new TypeError("expecting a function");
    } else if (this.length < this.m) {
      this.jobs.push(fn);
    }
    return this;
  };
  
  /**
   * Shift job off of queue
   *
   * @api public
   */
  
  Batch.prototype.shift = function () {
    return this.jobs.shift();
  };
  
  /**
   * Dequeues all queued jobs
   *
   * @api public
   * @param {Function} done - optional
   */
  
  Batch.prototype.run = function (done) {
    done = 'function' == typeof done ? done : Function();
    var self = this;
    var c = this.c;
    var i = 0;
  
    // empty queue
    if (0 == this.length) {
      return done(null);
    }
  
    function dequeue  () {
      var job = null;
      if (0 == self.length) {
        return false;
      }
  
      job = self.shift();
  
      if ('function' != typeof job) {
        return;
      }
  
      try { job(next); }
      catch (err) {
        done(err)
        return false;
      }
  
      function next (err) {
        if (err) {
          done(err);
        } else if (self.length) {
          dequeue();
        } else {
          done();
        }
      }
    }
  
    while (i++ < c) {
      if (false === dequeue()) {
        break;
      }
    }
  
    return this;
  };
  
  },{}],538:[function(require,module,exports){
  /*jslint node: true */
  
  'use strict';
  
  var ClientRequest = require('./request').ClientRequest;
  
  /**
   * An autoincremented id that is used to distinguish Resolver instances.
   *
   * @type {number}
   * @private
   */
  var resolverUID = 0;
  
  /**
   * A resolver takes a request for a resources and sends it through the
   * transport.
   *
   * @constructs Resolver
   */
  function Resolver() {
    if (!(this instanceof Resolver))
      return new Resolver();
  
    /**
     * The resolver's id.
     *
     * @type {number}
     * @protected
     */
    this._id = resolverUID++;
  
    /**
     * An incrementing ID used for tracking requests.
     *
     * @type {number}
     * @protected
     */
    this._requestID = 0;
  
    /**
     * Storage for the sent request handlers waiting for a response.
     *
     * @type {Object.<string, function>}
     * @protected
     */
    this._handlers = {};
  }
  exports.Resolver = Resolver;
  
  /**
   * Adds a handler to the internal storage.
   *
   * @param {number} requestID The identifier for the request.
   * @param {ClientRequest} handler The handler for the request.
   * @protected
   */
  Resolver.prototype._addHandler = function(requestID, handler) {
    this._handlers[requestID] = handler;
    return this;
  };
  
  /**
   * Removes a handler from the internal storage.
   *
   * @param {number} requestID The identifier for the request.
   * @protected
   */
  Resolver.prototype._removeHandler = function(requestID) {
    this._handlers[requestID] = null;
    return this;
  };
  
  /**
   * Sends a request through the transport.
   *
   * Subclasses of this class need to implement this method.
   *
   * @param {number} requestID The id of the request.
   * @param {Object} data The payload data for the request.
   * @protected
   */
  Resolver.prototype._sendRequest = function(requestID, data) {
    throw new Error('Resolver _sendRequest not implemented.');
  };
  
  /**
   * Handles a response from the transport.
   *
   * @param {Object} response The response from the transport.
   * @protected
   */
  Resolver.prototype._handleResponse = function(response) {
    throw new Error('Resolver _handleResponse not implemented.');
  };
  
  /**
   * Dispatches a request handler with some data.
   *
   * @param {number} requestID The request handler to dispatch.
   * @param {Object} data The response data to send back.
   * @protected
   */
  Resolver.prototype._dispatchResponse = function(requestID, requestType, data) {
    var handler = this._handlers[requestID];
    if (!handler) return;
    handler._handleResponse(requestType, data);
  };
  
  /**
   * Resolves a request (represented by the data) through the transport.
   *
   * @param {Object} data The data representing the request.
   * @param {function} onsuccess The success handler for the request.
   * @param {function} onerror The error handler for the request.
   */
  Resolver.prototype._resolve = function(data, onsuccess, onerror) {
    if (!data || !onsuccess || !onerror ||
        typeof onsuccess != 'function' || typeof onerror != 'function')
      throw new TypeError('Invalid argument length for `resolve`.');
  
    var requestID = ++this._requestID;
    var request = new ClientRequest(this, requestID, data, onsuccess, onerror);
  
    this._addHandler(requestID, request);
  
    request.onClose = this._removeHandler.bind(this);
    request.open();
  
    return request;
  };
  
  /**
   * Resolves a single request (represented by the data) through the transport.
   *
   * @param {Object} data The data representing the request.
   * @param {function} onsuccess The success handler for the request.
   * @param {function} onerror The error handler for the request.
   */
  Resolver.prototype.resolve = function(data, onsuccess, onerror) {
    throw new Error('Resolver resolve not implemented.');
  };
  
  /**
   * Resolves a subscription request (represented by the data) through the transport.
   *
   * @param {Object} data The data representing the request.
   * @param {function} onsuccess The success handler for the request.
   * @param {function} onerror The error handler for the request.
   */
  Resolver.prototype.subscribe = function(data, onsuccess, onerror) {
    throw new Error('Resolver subscribe not implemented.');
  };
  
  },{"./request":542}],539:[function(require,module,exports){
  (function (global){
  /*jslint node: true */
  
  'use strict';
  
  var defer = require('spotify-deferred');
  var Resolver = require('./bootstrap').Resolver;
  
  /**
   * A mock resolver for a nodejs environment.
   *
   * This resolver uses regular callbacks as a transport.
   *
   * @constructs Cosmos.MockResolver
   * @extends Cosmos.Resolver
   */
  function MockResolver() {
    if (!(this instanceof MockResolver))
      return new MockResolver();
    Resolver.call(this);
  
    // rebind the _handleResponse method so that we can reuse it for both
    // addEventListener and removeEventListener
    this._handleResponse = this._handleResponse.bind(this);
  
    /**
     * The identifier for request messages.
     *
     * @type {string}
     * @protected
     */
    this._requestMessageType = 'cosmos-request';
  
    /**
     * The identifier for response messages.
     *
     * @type {string}
     * @protected
     */
    this._responseMessageType = 'cosmos-response';
  
    /**
     * Prefix for the requests ids to prevent clashes
     * with bridge requests in webplayer
     *
     * @type {string}
     * @private
     */
    this._requestIdPrefix = 'cosmos_';
  
    this._handlersMap = {};
  
    // attach the handler
    this.attach();
  }
  MockResolver.prototype = new Resolver();
  MockResolver.prototype.constructor = MockResolver;
  exports.MockResolver = MockResolver;
  
  /**
   * @inheritDoc
   */
  MockResolver.prototype._sendRequest = function(requestName, requestID, data) {
    var self = this;
    console.log(requestName, requestID, data)
    var message = {
      type: this._requestMessageType,
      resolver: this._id,
      id: this._requestIdPrefix + requestID,
      name: requestName,
      payload: data.serialize ? data.serialize() : data
    };
  
    if (!this._handlersMap[data._action]) {
      return;
    }
  
    if (!this._handlersMap[data._action][data._uri]) {
      return;
    }
  
    this._handlersMap[data._action][data._uri](data, function (status, resp) {
      message.payload = {
        body: typeof resp !== 'undefined' ? resp : status,
        uri: data._uri,
        status: typeof resp !== 'undefined' ? status : 200
      };
      message.type = self._responseMessageType;
  
      var response = {
        data: message
      };
      self._handleResponse(response);
    });
  };
  
  /**
   * @inheritDoc
   */
  MockResolver.prototype._handleResponse = function(response) {
    var data = response.data;
    if (typeof data == 'string') {
      try {
        data = JSON.parse(response.data);
      } catch (e) {
        return;
      }
    }
    if (data.type != this._responseMessageType ||
        data.resolver != this._id ||
        !data.payload) return;
    var id = data.id || '';
    var requestID = parseInt(id.replace(this._requestIdPrefix, ''), 10);
    var requestName = data.name || '';
    if (!requestID || !requestName) return;
    this._dispatchResponse(requestID, requestName, data.payload);
  };
  
  /**
   * Attaches the resolver so that it could process calls from the window object.
   */
  MockResolver.prototype.attach = function() {
    var win = global.window;
    if (win) {
      win._cosmosRequest = this.resolve.bind(this);
    }
  };
  
  /**
   * Detaches the resolver so that it doesn't process calls from the window object.
   */
  MockResolver.prototype.detach = function() {
    var win = global.window;
    if (win) {
      delete win._cosmosRequest;
    }
  };
  
  /**
   * Specific method for the mock resolver to add request handlers
   *
   * @param {string}   method       Type of method (GET, POST, PUT, SUB)
   * @param {string}   uri          Request to handle
   * @param {Function} fn           Function that handles the request
   */
  MockResolver.prototype.addHandler = function(method, uri, fn) {
    if (!this._handlersMap[method]) {
      this._handlersMap[method] = {};
    }
  
    this._handlersMap[method][uri] = fn;
  };
  
  /**
   * Specific method for the mock resolver to remove a specific request handler
   *
   * @param {string}   method       Type of method (GET, POST, PUT, SUB)
   * @param {string}   uri          Request to handle
   */
  MockResolver.prototype.removeHandler = function(method, uri) {
    if (!this._handlersMap[method]) {
      return;
    }
  
    if (this._handlersMap[method][uri]) {
      delete this._handlersMap[method][uri];
    }
  };
  
  /**
   * Specific method for the mock resolver to remove all handlers
   */
  MockResolver.prototype.clearHandlers = function() {
    this._handlersMap = {};
  };
  
  /**
   * Resolves a single request (represented by the data) through the transport.
   * Single requests need to be closed immediately after the response is
   * received.
   *
   * @param {Object} data The data representing the request.
   * @param {function} onsuccess The success handler for the request.
   * @param {function} onerror The error handler for the request.
   */
  MockResolver.prototype.resolve = function(data, onsuccess, onerror) {
    function onResult(callback, response) {
      defer(callback.bind(this, response));
      request.close();
    }
  
    var request = this._resolve(data, onResult.bind(this, onsuccess), onResult.bind(this, onerror));
    return request;
  };
  
  /**
   * Resolves a  subscription request (represented by the data) through the transport.
   * Subscriptions stay open until they're explicitly closed.
   * Every time the request returns some data pull for next
   * batch is sent.
   *
   * @param {Object} data The data representing the request.
   * @param {function} onsuccess The success handler for the request.
   * @param {function} onerror The error handler for the request.
   */
  MockResolver.prototype.subscribe = function(data, onsuccess, onerror) {
    return this._resolve(data, onsuccess, onerror);
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"./bootstrap":538,"spotify-deferred":544}],540:[function(require,module,exports){
  /*jslint node: true */
  
  'use strict';
  
  var Resolver = require('./bootstrap').Resolver;
  var defer = require('spotify-deferred');
  
  /**
   * A resolver for a native environment.
   *
   * This resolver uses the Stitch bridge as a transport
   *
   * @constructs Cosmos.NativeResolver
   * @extends Cosmos.Resolver
   * @param {Object} spBridge Spotify CPP/JS bridge
   */
  function NativeResolver(spBridge) {
    if (!(this instanceof NativeResolver))
      return new NativeResolver();
    if (!spBridge) {
      throw new TypeError('Missing `spBridge` parameter');
    }
    Resolver.call(this);
  
    this._bridge = spBridge;
  }
  NativeResolver.prototype = new Resolver();
  NativeResolver.prototype.constructor = NativeResolver;
  exports.NativeResolver = NativeResolver;
  
  /**
   * Defer the function call.
   * TODO: Move to the separate module
   */
  NativeResolver.prototype._defer = function(context, callback) {
    defer(callback.bind(context));
  };
  
  /**
   * Construct and send Cosmos message.
   */
  NativeResolver.prototype._sendRequest = function(requestName, requestId, data) {
    var self = this;
    data = (data.serialize ? data.serialize() : data);
  
    var args = [requestId, data];
    var caller = { self: this, id: requestId, type: requestName };
  
    this._sendCosmosRequest(requestName, args, caller, this._handleResponse, this._handleError);
  };
  
  
  /**
   * Talk to bridge directly from Cosmos
   * TODO: Move to the separate module
   */
  NativeResolver.prototype._sendCosmosRequest = function(requestName, args, caller, onSuccess, onError) {
    var message = JSON.stringify({
      name: requestName,
      args: args
    });
  
    this._sendBridgeRequest(message, {
      onSuccess: function(data) {
        onSuccess.call(caller, JSON.parse(data));
      },
      onFailure: function(data) {
        data = JSON.parse(data);
        onError.call(caller, data);
      }
    });
  };
  
  /**
   * Send message to the bridge
   * @param {string} message The message to send to the bridge.
   * @param {Object.<string, function>} callbackMap The `onSuccess`
   * and `onFailure` functions to be executed after request is completed.
   * TODO: Use proxy that will handle Cosmos/Stitch calls for the resolver.
   */
  NativeResolver.prototype._sendBridgeRequest = function(message, callbackMap) {
    this._bridge.executeRequest(message, callbackMap || {});
  };
  
  /**
   * Handles successful responses from the bridge
   * @param {Object} data The response data.
   */
  NativeResolver.prototype._handleResponse = function(data) {
    this.self._dispatchResponse(this.id, this.type, data.responses && data.responses[0] || data);
  };
  
  /**
   * Handles failed responses from the bridge
   * @param {Object} error The error data.
   */
  NativeResolver.prototype._handleError = function(error) {
    this.self._dispatchResponse(this.id, this.type, error);
  };
  
  /**
   * Resolves a single request (represented by the data) through the transport.
   * Single requests need to be closed immediately after the response is
   * received.
   *
   * @param {Object} data The data representing the request.
   * @param {function} onsuccess The success handler for the request.
   * @param {function} onerror The error handler for the request.
   */
  NativeResolver.prototype.resolve = function(data, onsuccess, onerror) {
    function onResult(callback, response) {
      this._defer(this, callback.bind(this, response));
      request.close();
    }
  
    var request = this._resolve(data, onResult.bind(this, onsuccess), onResult.bind(this, onerror));
    return request;
  };
  
  /**
   * Resolves a  subscription request (represented by the data) through the transport.
   * Subscriptions stay open until they're explicitly closed.
   * Every time the request returns some data pull for next
   * batch is sent.
   *
   * @param {Object} data The data representing the request.
   * @param {function} onsuccess The success handler for the request.
   * @param {function} onerror The error handler for the request.
   */
  NativeResolver.prototype.subscribe = function(data, onsuccess, onerror) {
    function onResult(callback, response) {
      callback.call(this, response);
      request.pull();
    }
  
    var request = this._resolve(data, onResult.bind(this, onsuccess), onResult.bind(this, onerror));
    return request;
  };
  
  },{"./bootstrap":538,"spotify-deferred":544}],541:[function(require,module,exports){
  (function (global){
  /*jslint node: true */
  
  'use strict';
  
  var defer = require('spotify-deferred');
  var Resolver = require('./bootstrap').Resolver;
  
  /**
   * A resolver for a web-based environment.
   *
   * This resolver uses postMessage as a transport.
   *
   * @constructs Cosmos.WebResolver
   * @extends Cosmos.Resolver
   * @type {string=} opt_target The optional target for the postMessage calls.
   */
  function WebResolver(opt_target) {
    if (!(this instanceof WebResolver))
      return new WebResolver(opt_target);
    Resolver.call(this);
  
    /**
     * The target for postMessage calls.
     *
     * @type {string}
     * @protected
     */
    this._target = opt_target || '*';
  
    // rebind the _handleResponse method so that we can reuse it for both
    // addEventListener and removeEventListener
    this._handleResponse = this._handleResponse.bind(this);
  
    /**
     * The identifier for request messages.
     *
     * @type {string}
     * @protected
     */
    this._requestMessageType = 'cosmos-request';
  
    /**
     * The identifier for response messages.
     *
     * @type {string}
     * @protected
     */
    this._responseMessageType = 'cosmos-response';
  
    /**
     * Prefix for the requests ids to prevent clashes
     * with bridge requests in webplayer
     *
     * @type {string}
     * @private
     */
    this._requestIdPrefix = 'cosmos_';
  
    // attach the handler
    this.attach();
  }
  WebResolver.prototype = new Resolver();
  WebResolver.prototype.constructor = WebResolver;
  exports.WebResolver = WebResolver;
  
  /**
   * @inheritDoc
   */
  WebResolver.prototype._sendRequest = function(requestName, requestID, data) {
    var top = global.window.top;
  
    var message = {
      type: this._requestMessageType,
      resolver: this._id,
      id: this._requestIdPrefix + requestID,
      name: requestName,
      payload: data.serialize ? data.serialize() : data
    };
    top.postMessage(JSON.stringify(message), this._target);
  };
  
  /**
   * @inheritDoc
   */
  WebResolver.prototype._handleResponse = function(response) {
    var data = response.data;
    if (typeof data == 'string') {
      try {
        data = JSON.parse(response.data);
      } catch (e) {
        return;
      }
    }
    if (data.type != this._responseMessageType ||
        data.resolver != this._id ||
        !data.payload) return;
    var id = data.id || '';
    var requestID = parseInt(id.replace(this._requestIdPrefix, ''), 10);
    var requestName = data.name || '';
    if (!requestID || !requestName) return;
    this._dispatchResponse(requestID, requestName, data.payload);
  };
  
  /**
   * Attaches the resolver so that it could process postMessage calls.
   */
  WebResolver.prototype.attach = function() {
    var win = global.window;
    if (win.addEvent && !win.addEventListener) {
      win.addEvent('onmessage', this._handleResponse);
    } else {
      win.addEventListener('message', this._handleResponse, false);
    }
  };
  
  /**
   * Detaches the resolver so that it doesn't process postMessage calls.
   */
  WebResolver.prototype.detach = function() {
    var win = global.window;
    if (win.removeEvent && !win.removeEventListener) {
      win.removeEvent('onmessage', this._handleResponse);
    } else {
      win.removeEventListener('message', this._handleResponse, false);
    }
  };
  
  /**
   * Resolves a single request (represented by the data) through the transport.
   * Single requests need to be closed immediately after the response is
   * received.
   *
   * @param {Object} data The data representing the request.
   * @param {function} onsuccess The success handler for the request.
   * @param {function} onerror The error handler for the request.
   */
  WebResolver.prototype.resolve = function(data, onsuccess, onerror) {
    function onResult(callback, response) {
      defer(callback.bind(this, response));
      request.close();
    }
  
    var request = this._resolve(data, onResult.bind(this, onsuccess), onResult.bind(this, onerror));
    return request;
  };
  
  /**
   * Resolves a  subscription request (represented by the data) through the transport.
   * Subscriptions stay open until they're explicitly closed.
   * Every time the request returns some data pull for next
   * batch is sent.
   *
   * @param {Object} data The data representing the request.
   * @param {function} onsuccess The success handler for the request.
   * @param {function} onerror The error handler for the request.
   */
  WebResolver.prototype.subscribe = function(data, onsuccess, onerror) {
    return this._resolve(data, onsuccess, onerror);
  };
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"./bootstrap":538,"spotify-deferred":544}],542:[function(require,module,exports){
  /*jslint node: true */
  
  'use strict';
  
  var defer = require('spotify-deferred');
  
  /**
   * The representation of the connection to the client.
   * With introduction of Cosmos subscription the model of making
   * client requests changed.
   * Each Cosmos requests now needs to be explicitly cancelled to
   * close the connection.
   * Simple requests that only send or retrieve data (e.g POST and GET)
   * need to send 'cosmos_request_cancel' message immediately after the response
   * is received.
   * Subscription requests need to send a 'cosmos_request_pull' message every time
   * they receive data. The consumer of the subscription needs to cancel the subscription
   * when no more data should be sent from the provider.
   */
  function ClientRequest(resolver, requestId, data, onsuccess, onerror) {
  
    /**
     * Request identifier.
     * @type {number}
     */
    this._requestId = requestId;
  
    /**
     * Either web or native request resolver.
     * @type {Cosmos.Resolver}
     */
    this._resolver = resolver;
  
    /**
     * Data that should be passed with every request.
     * @type {*}
     */
    this._requestData = data;
  
    /**
     * Triggered on success
     * @type {function}
     */
    this._successCallback = onsuccess;
  
    /**
     * Triggered on error
     * @type {function}
     */
    this._errorCallback = onerror;
  
    /**
     * Current state of the request.
     * @type {ClientRequest.status}
     */
    this._status = ClientRequest.status.INITIALIZED;
  }
  exports.ClientRequest = ClientRequest;
  
  /**
   * Possible state of the ClientRequest instance.
   */
  ClientRequest.status = {
    INITIALIZED: 'INITIALIZED',
    CLOSED: 'CLOSED',
    OPEN: 'OPEN'
  };
  
  /**
   * Possible desktop bridge messages.
   */
  ClientRequest.messages = {
    OPEN: 'cosmos_request_create',
    PULL: 'cosmos_request_pull',
    CLOSE: 'cosmos_request_cancel'
  };
  
  /**
   * Opens the connection with the client.
   */
  ClientRequest.prototype.open = function() {
    if (this._status === ClientRequest.status.INITIALIZED) {
      this._status = ClientRequest.status.OPEN;
      this._sendRequest(ClientRequest.messages.OPEN, this._requestData);
    }
  };
  
  /**
   * Send pull request for the open connection.
   * For subscriptions pull should resolve to a
   * piece of data.
   */
  ClientRequest.prototype.pull = function() {
    if (this._status === ClientRequest.status.OPEN) {
      this._sendRequest(ClientRequest.messages.PULL, this._requestData);
    }
    return this._status;
  };
  
  /**
   * Closes the connection with the client.
   */
  ClientRequest.prototype.close = function() {
    if (this._status === ClientRequest.status.OPEN) {
      this._status = ClientRequest.status.CLOSE;
      this._sendRequest(ClientRequest.messages.CLOSE);
    }
  };
  
  ClientRequest.prototype.onClose = function() {};
  
  /**
   * Sends the request to the platform specific resolver
   * @param {string} requestName The message type. One of the {ClientRequest.messages}.
   * @param {object?} data The data to send with the request.
   */
  ClientRequest.prototype._sendRequest = function(requestName, data) {
    this._resolver._sendRequest(requestName, this._requestId, data || {});
  };
  
  /**
   * Handles the response for the given request
   * @param {String} requestName The message type. One of the {ClientRequest.messages}.
   * @param {Object} data The response data.
   */
  ClientRequest.prototype._handleResponse = function(requestName, data) {
    var self = this;
    var status = data && data.status;
    var callback;
  
    if (requestName === ClientRequest.messages.CLOSE) {
      this._successCallback = null;
      this._errorCallback = null;
      this._requestData = null;
      this.onClose(this._requestId);
      return;
    }
  
    callback = this._successCallback;
    callback = typeof callback === 'function' ? callback : function() {};
    defer(callback.bind(this, data));
  };
  
  },{"spotify-deferred":544}],543:[function(require,module,exports){
  (function (global){
  'use strict';
  
  var window = global.window || {};
  var process = global.process;
  process = "cmd.exe";
  var common = require('cosmos-common-js');
  var Resolver = require('./scripts/resolver').Resolver;
  
  var SPResolver = null;
  var spResolver = null;
  
  var hasNativeBridge = window._getSpotifyModule &&
      typeof window._getSpotifyModule === 'function' &&
      window._getSpotifyModule('bridge');
  
  var nodeRegex = /(node)|(grunt)|(iojs)(\.exe)*$/;
  var isNodeJs = process && nodeRegex.test(process.argv0);
  
  if (!isNodeJs) {
    if (hasNativeBridge) {
      SPResolver = require('./env/bootstrap.native.js').NativeResolver;
      spResolver = new SPResolver(hasNativeBridge);
    } else {
      SPResolver = require('./env/bootstrap.web.js').WebResolver;
      spResolver = new SPResolver();
    }
  } else {
    SPResolver = require('./env/bootstrap.mock.js').MockResolver;
    spResolver = new SPResolver();
    console.log(spResolver)
    exports.mockResolver = {
      addHandler: spResolver.addHandler.bind(spResolver),
      removeHandler: spResolver.removeHandler.bind(spResolver),
      clearHandlers: spResolver.clearHandlers.bind(spResolver)
    };
  }
  
  exports.Resolver = Resolver;
  exports.Action = common.request.Action;
  exports.Request = common.request.Request;
  exports.Response = common.response.Response;
  exports.resolver = spResolver ? new Resolver(spResolver) : null;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"./env/bootstrap.mock.js":539,"./env/bootstrap.native.js":540,"./env/bootstrap.web.js":541,"./scripts/resolver":545,"cosmos-common-js":325}],544:[function(require,module,exports){
  (function() {
    /**
     * @file
     * Introduces a function called "defer" that allows functions to be
     * executed in the next available tick.
     *
     * Unlike "setTimeout", "defer" executes the function at the nearest
     * possible time without clamping.
     *
     * @see Spotify.defer
     */
    'use strict';
  
    var hasWindow = typeof window != 'undefined';
    var hasDefineProperty = typeof Object.defineProperty == 'function';
  
    if (hasWindow && window.__modDefFn) {
      // If deferred has been attached to the global scope
      module.exports = window.__modDefFn;
      return;
    }
  
    /**
     * Storage for deferred functions to be executed.
     *
     * @type {Array.<function()>}
     * @private
     */
    var deferred = [];
  
  
    /**
     * A bound version of the postMessage routine used to trigger deferred
     * execution.
     *
     * @type {function()}
     * @private
     */
    var send;
    var origin;
  
    if (hasWindow && window.postMessage) {
      origin = (window.location.origin ||
            window.location.protocol + '//' + window.location.hostname);
      send = window.postMessage.bind(window, '@execute_deferreds', origin);
      if (!window.__hasDeferredHandler) {
        if (hasDefineProperty) {
          Object.defineProperty(window, '__hasDeferredHandler', {value: 1});
        } else {
          window.__hasDeferredHandler = 1;
        }
        var handler = function(e) {
          if (e.origin != origin && e.data != '@execute_deferreds') {
            return;
          }
          executeDeferreds();
        };
        if (window.addEventListener) {
          window.addEventListener('message', handler);
        } else {
          window.attachEvent('onmessage', handler);
        }
      }
    } else if (typeof setImmediate != 'undefined') {
      send = setImmediate.bind(null, executeDeferreds);
    } else {
      send = setTimeout.bind(null, executeDeferreds, 10);
    }
  
  
    /**
     * Executes the deferred functions when the window
     * receives an 'execute_deferreds' message.
     *
     * @private
     */
    function executeDeferreds() {
      var fns = deferred.splice(0);
      if (!fns.length) return;
      for (var i = 0, l = fns.length; i < l; i++) {
        try {
          fns[i]();
        } finally {
          // Do nothing.
          null;
        }
      }
    }
  
  
    /**
     * Executes the function applied at the nearest possible time without
     * clamping.
     *
     * @param {function()} fn The function to execute.
     */
    var defer = function(fn) {
      var trigger = !deferred.length;
      deferred.push(fn);
      if (trigger) send();
    };
  
    if (hasWindow && !window.__modDefFn) {
      if (hasDefineProperty) {
        Object.defineProperty(window, '__modDefFn', {value: defer});
      } else {
        window.__modDefFn = defer;
      }
    }
  
    /**
     * Export public interface
     */
    module.exports = defer;
  
  })();
  
  },{}],545:[function(require,module,exports){
  var common = require('cosmos-common-js');
  
  var Request = common.request.Request;
  var Action = common.request.Action;
  var Response = common.response.Response;
  
  /**
   * Checks whether a status is successful.
   *
   * We define a successful status to be something within the 200 to 299 range.
   *
   * @param {number} status The status to check.
   */
  function _isSuccessStatus(status) {
    // This constitutes a successfull status.
    return (status >= 200 && status <= 299);
  };
  
  
  function Resolver(spResolver) {
    if (!spResolver || typeof spResolver.resolve !== 'function') {
      throw TypeError('Incorrect resolver argument');
    }
  
    this._resolver = spResolver;
  }
  
  /**
   * The basic, generic method of sending the requests.
   *
   * For params description:
   * @borrows Resolver#_resolve as Resolver#resolve
   */
  Resolver.prototype.resolve = function(request, callback) {
    return this._resolve(request, callback);
  };
  
  /**
   * Convenience method for doing GET requests.
   * resolver.get('sp://player') is an equivalent of
   * resolver.resolve(new Request('GET', 'sp://player')).
   *
   * @param {string|Object.<string, object>} options If is a string
   * will be parsed as url. If more data is needed, object notation
   * should be used.
   *  param options.url {string} The url of the request.
   *  param options.body {object=} The request body.
   *  param options.headers {object=} The request headers.
   * @param {function(error=, object?)} callback The function
   * executed after the request has been completed.
   *
   * @return {RequestHandler} The cancellable request handler.
   */
  Resolver.prototype.get = function(options, callback) {
    return this._resolveFromParams(Action.GET, options, callback);
  };
  
  /**
   * Convenience method for doing POST requests.
   * resolver.post('sp://player') is an equivalent of
   * resolver.resolve(new Request('POST', 'sp://player'))
   *
   * @see Resolver#get for params description and returned value.
   */
  Resolver.prototype.post = function(options, callback) {
    return this._resolveFromParams(Action.POST, options, callback);
  };
  
  /**
   * Convenience method for doing SUB requests.
   * resolver.subscribe('sp://player') is an equivalent of
   * resolver.resolve(new Request('SUB', 'sp://player'))
   *
   * @see Resolver#get for params description and returned value.
   */
  Resolver.prototype.subscribe = function(options, callback) {
    return this._resolveFromParams(Action.SUB, options, callback);
  };
  
  /**
   * Convenience method for doing PUT requests.
   * resolver.put('sp://ads/v1/settings/session') is an equivalent of
   * resolver.resolve(new Request('PUT', 'sp://ads/v1/settings/session'))
   *
   * @see Resolver#get for params description and returned value.
   */
  Resolver.prototype.put = function(options, callback) {
    return this._resolveFromParams(Action.PUT, options, callback);
  };
  
  /**
   * Convenience method for doing PATCH requests.
   * resolver.patch('sp://ads/v1/settings/session') is an equivalent of
   * resolver.resolve(new Request('PATCH', 'sp://ads/v1/settings/session'))
   *
   * @see Resolver#get for params description and returned value.
   */
  Resolver.prototype.patch = function(options, callback) {
    return this._resolveFromParams(Action.PATCH, options, callback);
  };
  
  /**
   * Convenience method for doing DELETE requests.
   * resolver.delete('sp://player') is an equivalent of
   * resolver.resolve(new Request('DELETE', 'sp://player'))
   *
   * @see Resolver#get for params description and returned value.
   */
  Resolver.prototype.delete = function(options, callback) {
    return this._resolveFromParams(Action.DELETE, options, callback);
  };
  
  /**
   * @private
   * Sends the request to the platform specific request resolver.
   * If the request action is 'SUB' it will send subscribe request
   * In any other case it will send simple resolve request.
   *
   * @param {Cosmos.Request} request A request object.
   * @param {function(error=, object?)} callback The function
   * executed after the request has been completed.
   *
   * @return {RequestHandler} The cancellable request handler.
   */
  Resolver.prototype._resolve = function(request, callback) {
    if (!callback || typeof callback !== 'function') {
      callback = function() {};
    }
  
    var requestHandler;
  
    function onSuccess(serverResponse) {
      if (!requestHandler._request) {
        return;
      }
  
      var response = Response.fromObject(serverResponse);
      if (!response) {
        var error = new Error(
          'Failed to parse response: ' + JSON.stringify(serverResponse));
        return callback(error);
      }
  
      if (_isSuccessStatus(response.getStatusCode())) {
        return callback(null, response);
      } else {
        // Extract just the initial part of the request uri. It's good to have something
        // but it is also good to avoid having everything, since that can hurt dashboards
        // that group error messages.
        var requestEndpoint = request.toJSON().uri.match(/[^\:]*(\:\/\/)?[^\/]*/)[0];
        var errorMessage = (
          response.getHeader("error") ||
          "Request to " + requestEndpoint + " failed with status code " + response.getStatusCode());
        var error = new Error(errorMessage);
        error.response = response;
        return callback(error, response);
      }
    }
  
    function onError(serverResponse) {
      return callback(serverResponse instanceof Error ?
        serverResponse :
        new Error('Request failed: ' + JSON.stringify(serverResponse)));
    }
  
    var resolveFn = request.getAction() === Action.SUB ?
        this._resolver.subscribe : this._resolver.resolve;
  
    var clientRequest = resolveFn.call(this._resolver, request, onSuccess, onError);
  
    requestHandler = new RequestHandler(clientRequest);
    return requestHandler;
  };
  
  /**
   * @private
   * Creates Request object from supplied params.
   * @param {string} method Request method. One of the Request.Action.
   * @param {string|Object.<string, object>} options If is a string
   * will be parsed as url. If more data is needed, object notation
   * should be used.
   *  param options.url {string} The url of the request.
   *  param options.body {object=} The request body.
   *  param options.headers {object=} The request headers.
   * @param {function(error=, object?)} callback The function
   * executed after the request has been completed.
   *
   * @return {RequestHandler} The cancellable request handler.
   */
  Resolver.prototype._resolveFromParams = function(method, options, callback) {
    options = options || {};
  
    var url = typeof options === 'string' ? options : options.url;
    var headers = options.headers;
    var body = options.body;
  
    var request = new Request(method, url, headers, body);
  
    return this._resolve(request, callback);
  };
  
  /**
   * The object that wraps the clientRequest
   * in a very simple interface.
   * Separates the implementation of the ClientRequest
   * from the request handler returned by Cosmos API.
   *
   * @param {Cosmos.ClientRequest} request The object
   * representing the newly opened request to the client.
   * Usually a request will be a subscription that
   * needs a close handler.
   */
  function RequestHandler(request) {
    if (!request || typeof request.close !== 'function')
      throw new TypeError('Invalid `request` argument.');
  
    this._request = request;
  }
  
  /**
   * Closes the request and removes the object.
   */
  RequestHandler.prototype.cancel = function() {
    if (this._request) {
      this._request.close();
      this._request = null;
    }
  };
  
  exports.Resolver = Resolver;
  
  },{"cosmos-common-js":325}],546:[function(require,module,exports){
  (function() {
    /**
     * @file
     * Introduces a function called "defer" that allows functions to be
     * executed in the next available tick.
     *
     * Unlike "setTimeout", "defer" executes the function at the nearest
     * possible time without clamping.
     *
     * @see Spotify.defer
     */
    'use strict';
  
    var hasWindow = typeof window != 'undefined';
    var hasDefineProperty = typeof Object.defineProperty == 'function';
  
    if (hasWindow && window.__modDefFn) {
      // If deferred has been attached to the global scope
      module.exports = window.__modDefFn;
      return;
    }
  
    /**
     * Storage for deferred functions to be executed.
     *
     * @type {Array.<function()>}
     * @private
     */
    var deferred = [];
  
  
    /**
     * A bound version of the postMessage routine used to trigger deferred
     * execution.
     *
     * @type {function()}
     * @private
     */
    var send;
    var origin;
  
    function bindSendDom() {
      origin = (window.location.origin ||
            window.location.protocol + '//' + window.location.hostname);
      send = window.postMessage.bind(window, '@execute_deferreds', origin);
      if (!window.__hasDeferredHandler) {
        if (hasDefineProperty) {
          Object.defineProperty(window, '__hasDeferredHandler', {value: 1});
        } else {
          window.__hasDeferredHandler = 1;
        }
        var handler = function(e) {
          if (e.origin != origin && e.data != '@execute_deferreds') {
            return;
          }
          executeDeferreds();
        };
        if (window.addEventListener) {
          window.addEventListener('message', handler);
        } else {
          window.attachEvent('onmessage', handler);
        }
      }
    }
  
    function bindSendImmediate() {
      send = setImmediate.bind(null, executeDeferreds);
    }
  
    function bindSendTimeout() {
      send = setTimeout.bind(null, executeDeferreds, 10);
    }
  
    function bindSendAuto() {
      if (hasWindow && window.postMessage) {
        bindSendDom();
      } else if (typeof setImmediate != 'undefined') {
        bindSendImmediate();
      } else {
        bindSendTimeout();
      }
    }
  
    bindSendAuto();
  
    /**
     * Executes the deferred functions when the window
     * receives an 'execute_deferreds' message.
     *
     * @private
     */
    function executeDeferreds() {
      var fns = deferred.splice(0);
      if (!fns.length) return;
      for (var i = 0, l = fns.length; i < l; i++) {
        try {
          fns[i]();
        } finally {
          // Do nothing.
          null;
        }
      }
    }
  
  
    /**
     * Executes the function applied at the nearest possible time without
     * clamping.
     *
     * @param {function()} fn The function to execute.
     */
    var defer = function(fn) {
      var trigger = !deferred.length;
      deferred.push(fn);
      if (trigger) send();
    };
  
    if (hasWindow && !window.__modDefFn) {
      if (hasDefineProperty) {
        Object.defineProperty(window, '__modDefFn', {value: defer});
      } else {
        window.__modDefFn = defer;
      }
    }
  
    /**
     * Export interface for binding send method to a particular implementation,
     * which is intended primarily for use by integration tests where the send method
     * can be explicitly set to immediate/timeout, even if mock DOM globals exist.
     */
    defer.use = {
      auto: bindSendAuto,
      dom: bindSendDom,
      immediate: bindSendImmediate,
      timeout: bindSendTimeout,
    };
  
    /**
     * Export public interface
     */
    module.exports = defer;
  
  })();
  
  },{}],547:[function(require,module,exports){
  arguments[4][544][0].apply(exports,arguments)
  },{"dup":544}],548:[function(require,module,exports){
  /**
   * @module spotify-eventemitter
   */
  'use strict';
  
  /**
   * @private
   */
  var _defer = require('spotify-deferred');
  
  /**
   * The event handlers.
   *
   * @typedef {Array.<function>}
   * @private
   */
  var EventHandlers;
  
  /**
   * Represents an Event.
   *
   * **NOTE**: The Event class is an internal class: you cannot instantiate it
   * directly. Instead, you should use the provided
   * {@link module:spotify-eventemitter.createEvent} function.
   *
   * @constructor
   * @param {string} type The type name of the event object.
   * @param {Object} props An object that will be added as properties of the
   *     event object.
   * @see {@link module:spotify-eventemitter.createEvent}
   */
  function Event(type, props) {
    /**
     * The type of the event.
     *
     * @type {string}
     */
    this.type = type;
  
    /**
     * A flag for whether preventDefault was called.
     *
     * @type {boolean}
     * @private
     */
    this._prevented = false;
  
    /**
     * A flag for whether stopPropagation was called
     *
     * @type {boolean}
     * @private
     */
    this._stopped = false;
  
    /**
     * A flag for whether stopImmediatePropagation was called.
     *
     * @type {boolean}
     * @private
     */
    this._immediateStopped = false;
  
    if (props) {
      for (var key in props) {
        if (key == 'type') {
          continue;
        }
        this[key] = props[key];
      }
    }
  }
  
  /**
   * Prevents the default operation for the event.
   */
  Event.prototype.preventDefault = function() {
    this._prevented = true;
  };
  
  /**
   * Returns whether preventDefault was called on the event.
   *
   * @return {boolean} True if preventDefault was called, false otherwise.
   */
  Event.prototype.isDefaultPrevented = function() {
    return this._prevented;
  };
  
  /**
   * Stops the propagation of the event.
   */
  Event.prototype.stopPropagation = function() {
    this._stopped = true;
  };
  
  /**
   * Returns whether stopPropagation was called on the event.
   *
   * @return {boolean} True if stopPropagation was called, false otherwise.
   */
  Event.prototype.isPropagationStopped = function() {
    return this._stopped;
  };
  
  /**
   * Stops the immediate propagation of the event.
   *
   * Handlers added after any event handler calling this method will not receive
   * the event.
   */
  Event.prototype.stopImmediatePropagation = function() {
    this._immediateStopped = true;
  };
  
  /**
   * Returns whether stopImmediatePropagation was called on the event.
   *
   * @return {boolean} True if stopImmediatePropagation was called, false
   *     otherwise.
   */
  Event.prototype.isImmediatePropagationStopped = function() {
    return this._immediateStopped;
  };
  
  /**
   * An EventEmitter is an object that can be listened to for events.
   *
   * Instances of this class are not usually used directly; instead, a class that
   * needs EventEmitter functionality would inherit from the EventEmitter class
   * so that it's instances can use events.
   *
   * @constructor
   * @alias module:spotify-eventemitter
   *
   * @example <caption>Direct usage</caption>
   * var EventEmitter = require('spotify-eventemitter');
   * var emitter = new EventEmitter();
   * emitter.addListener('someEvent', function() {
   *     console.log('someEvent fired!');
   * });
   * emitter.emit('someEvent');
   * @example <caption>Inheritance</caption>
   * var EventEmitter = require('spotify-eventemitter');
   * var inherit = require('spotify-inherit/inherit');
   *
   * function MyClass() {
   *   EventEmitter.call(this);
   * }
   * inherit(MyClass, EventEmitter);
   *
   * var instance = new MyClass();
   * instance.addListener('someEvent', function() {
   *     console.log('someEvent fired!');
   * });
   * instance.emit('someEvent');
   */
  function EventEmitter() {
    /**
     * A map of event names to event handlers.
     *
     * @type {Object.<string, module:spotify-eventemitter~EventHandlers>}
     * @private
     */
    this._listenerMap = {};
  }
  
  /**
   * Creates a new Event object.
   *
   * @param {string} type The type name of the event.
   * @param {Object=} opt_params An object containing properties for the new event
   *     object.
   * @return {module:spotify-eventemitter~Event} The new event object.
   */
  EventEmitter.createEvent = function(type, opt_params) {
    return new Event(type, opt_params);
  };
  
  /**
   * Adds an event listener to the emitter.
   *
   * This method is idempotent: calling it multiple times using the same type
   * and listener arguments will only set the listener once. This behaviour is
   * done to prevent accidental additions of the same event listener.
   *
   * @param {string} type The type of event to listen to.
   * @param {function} listener A function to be called when the emitter fires
   *     the event specified in the type.
   * @return {module:spotify-eventemitter} The event emitter.
   */
  EventEmitter.prototype.addListener = function(type, listener) {
    var _listenerMap = this._listenerMap || (this._listenerMap = {});
    var listeners = _listenerMap[type] || (_listenerMap[type] = []);
    if (listeners.indexOf(listener) != -1) {
      // Handler already added, return quickly.
      return this;
    }
    listeners.push(listener);
    return this;
  };
  
  /**
   * Adds multiple event listeners to the emitter.
   *
   * @param {Object.<string, function>} eventListeners An object, the keys of
   *     which correspond to the name of events to listen to, and the value of
   *     each of these keys should be a function that would be added as the
   *     listener for that event.
   * @return {module:spotify-eventemitter} The event emitter.
   */
  EventEmitter.prototype.addListeners = function(eventListeners) {
    for (var type in eventListeners) {
      this.addListener(type, eventListeners[type]);
    }
    return this;
  };
  
  /**
   * Adds a "once" event listener to the emitter, which will be removed right
   * after it has been fired.
   *
   * In order to achieve the "once" behaviour, the listener argument passed to
   * this method is wrapped in a function, which is then returned by the method.
   *
   * Because of this wrapping, this method is not idempotent: calling it multiple
   * times with the same type and listener arguments will result to multiple
   * event listeners attached.
   *
   * @param {string} type The type of event to listen to.
   * @param {function} listener A function to be called when the emitter fires
   *     the event specified in the type and then automatically removed.
   * @return {function} The function used to wrap the listener function argument.
   *     This function can be used as an argument to removeEvent.
   */
  EventEmitter.prototype.addOnceListener = function(type, listener) {
    var wrapper = function() {
      this.removeListener(type, wrapper);
      return listener.apply(this, arguments);
    };
    this.addListener(type, wrapper);
    return wrapper;
  };
  
  /**
   * Removes an event listener from the emitter.
   *
   * @param {string} type The type of event to remove.
   * @param {function} listener The listener function to remove. This must be a
   *     function that was added previously using addEvent.
   * @return {module:spotify-eventemitter} The event emitter.
   */
  EventEmitter.prototype.removeListener = function(type, listener) {
    var _listenerMap = this._listenerMap;
    var listeners = _listenerMap && _listenerMap[type];
    if (!listeners) {
      return this;
    }
    var index = listeners.indexOf(listener);
    if (index == -1) {
      return this;
    }
    listeners.splice(index, 1);
    if (!listeners.length) {
      _listenerMap[type] = null;
    }
    return this;
  };
  
  /**
   * Removes all event listeners from the emitter for a particular type.
   * If no event type is given, all event listeners will be removed.
   * 
   * @param {string=} opt_type The event type to remove.
   * @return {module:spotify-eventemitter} The event emitter.
   */
  EventEmitter.prototype.removeAllListeners = function(opt_type) {
    var _listenerMap = this._listenerMap;
    if (!_listenerMap) {
      return this;
    }
  
    if (opt_type === undefined) {
      this._listenerMap = {};
      return this;
    }
  
    _listenerMap[opt_type] = null;
    return this;
  };
  
  /**
   * Removes multiple event listeners from the emitter.
   *
   * @param {Object.<string, function>} eventListeners An object, the keys of
   *     which correspond to the name of events to listen to, and the value of
   *     each of these keys should be a function that would be added as the
   *     listener for that event.
   * @return {module:spotify-eventemitter} The event emitter.
   */
  EventEmitter.prototype.removeListeners = function(eventListeners) {
    for (var type in eventListeners) {
      this.removeListener(type, eventListeners[type]);
    }
    return this;
  };
  
  /**
   * Creates and emits an event on the emitter.
   *
   * This method invokes all the added event listeners of the `type` provided in
   * the order they were added, passing in a new Event object created using the
   * provided `type` and `opt_params` arguments.
   *
   * This method emits events asynchronously: the listeners are not called until
   * the next run loop.
   *
   * @param {string} type The type name of the event to emit.
   * @param {Object=} opt_params An object containing parameters for the Event
   *     object.
   * @return {module:spotify-eventemitter~Event} The Event object that was
   *     created.
   */
  EventEmitter.prototype.emit = function(type, opt_params) {
    var event = new Event(type, opt_params);
    _defer(function() {
      this.emitEventSync(event);
    }.bind(this));
    return event;
  };
  
  /**
   * Emits an event on the emitter.
   *
   * This method invokes all the added event listeners of the `type` of the 
   * `event` provided in the order they were added, passing in the `event` as an
   * argument.
   *
   * This method emits events asynchronously: the listeners are not called until
   * the next run loop.
   *
   * @param {module:spotify-eventemitter~Event} event The Event object.
   * @return {module:spotify-eventemitter~Event} The Event object that was passed.
   */
  EventEmitter.prototype.emitEvent = function(event) {
    _defer(function() {
      this.emitEventSync(event);
    }.bind(this));
    return event;
  };
  
  /**
   * Creates and synchronously emits an event on the emitter.
   *
   * This method invokes all the added event listeners of the `type` provided in
   * the order they were added, passing in a new Event object created using the
   * provided `type` and `opt_params` arguments.
   *
   * @param {string} type The type name of event to emit.
   * @param {Object=} opt_params An object containing parameters for the event
   *     object.
   * @return {module:spotify-eventemitter~Event} The Event object that was
   *     created.
   */
  EventEmitter.prototype.emitSync = function(type, opt_params) {
    var event = new Event(type, opt_params);
    this.emitEventSync(event);
    return event;
  };
  
  /**
   * Synchronously emits an event on the emitter.
   *
   * This method invokes all the added event listeners of the `type` of the
   * `event` provided in the order they were added, passing in the `event` as an
   * argument.
   *
   * @param {module:spotify-eventemitter~Event} event The Event object.
   * @return {module:spotify-eventemitter~Event} The Event object that was passed.
   */
  EventEmitter.prototype.emitEventSync = function(event) {
    var type = event.type;
    var _listenerMap = this._listenerMap;
    var listeners = _listenerMap && _listenerMap[type];
    if (!listeners || !listeners.length) {
      return event;
    }
    listeners = listeners.slice(0);
    for (var i = 0, l = listeners.length; i < l; i++) {
      listeners[i].call(this, event);
      if (event.isImmediatePropagationStopped()) {
        break;
      }
    }
    return event;
  };
  
  /**
   * Adds an event listener to the emitter.
   *
   * **NOTE**: This method is an alias of
   * {@link module:spotify-eventemitter#addListener}.
   *
   * This method is idempotent: calling it multiple times using the same type
   * and listener arguments will only set the listener once. This behaviour is
   * done to prevent accidental additions of the same event listener.
   *
   * @param {string} type The type of event to listen to.
   * @param {function} listener A function to be called when the emitter fires
   *     the event specified in the type.
   * @return {module:spotify-eventemitter} The event emitter.
   * @see {@link module:spotify-eventemitter#addListener}
   */
  EventEmitter.prototype.on = function(type, listener) {
    return this.addListener(type, listener);
  };
  
  /**
   * Adds a "once" event listener to the emitter, which will be removed right
   * after it has been fired.
   *
   * **NOTE**: This method is an alias of
   * {@link module:spotify-eventemitter#addOnceListener}.
   *
   * In order to achieve the "once" behaviour, the listener argument passed to
   * this method is wrapped in a function, which is then returned by the method.
   *
   * Because of this wrapping, this method is not idempotent: calling it multiple
   * times with the same type and listener arguments will result to multiple
   * event listeners attached.
   *
   * @param {string} type The type of event to listen to.
   * @param {function} listener A function to be called when the emitter fires
   *     the event specified in the type and then automatically removed.
   * @return {function} The function used to wrap the listener function argument.
   *     This function can be used as an argument to removeEvent.
   * @see {@link module:spotify-eventemitter#addOnceListener}
   */
  EventEmitter.prototype.once = function(type, listener) {
    return this.addOnceListener(type, listener);
  };
  
  // DEPRECATED METHODS:
  
  /**
   * Adds an event listener to the emitter.
   *
   * **NOTE**: This method has been deprecated in favor of
   * {@link module:spotify-eventemitter#addListener}.
   *
   * This method is idempotent: calling it multiple times using the same type
   * and listener arguments will only set the listener once. This behaviour is
   * done to prevent accidental additions of the same event listener.
   *
   * @deprecated since v2.0.0.
   * @param {string} type The type of event to listen to.
   * @param {function} listener A function to be called when the emitter fires
   *     the event specified in the type.
   * @return {module:spotify-eventemitter} The event emitter.
   * @see {@link module:spotify-eventemitter#addListener}
   */
  EventEmitter.prototype.addEvent = function(type, listener) {
    return this.addListener(type, listener);
  };
  
  /**
   * Adds multiple event listeners to the emitter.
   *
   * **NOTE**: This method has been deprecated in favor of
   * {@link module:spotify-eventemitter#addListeners}.
   *
   * @deprecated since v2.0.0.
   * @param {Object.<string, function>} eventListeners An object, the keys of
   *     which correspond to the name of events to listen to, and the value of
   *     each of these keys should be a function that would be added as the
   *     listener for that event.
   * @return {module:spotify-eventemitter} The event emitter.
   * @see {@link module:spotify-eventemitter#addListeners}
   */
  EventEmitter.prototype.addEvents = function(eventListeners) {
    return this.addListeners(eventListeners);
  };
  
  /**
   * Adds a "once" event listener to the emitter, which will be removed right
   * after it has been fired.
   *
   * **NOTE**: This method has been deprecated in favor of
   * {@link module:spotify-eventemitter#addOnceListener}.
   *
   * In order to achieve the "once" behaviour, the listener argument passed to
   * this method is wrapped in a function, which is then returned by the method.
   *
   * Because of this wrapping, this method is not idempotent: calling it multiple
   * times with the same type and listener arguments will result to multiple
   * event listeners attached.
   *
   * @deprecated since v2.0.0.
   * @param {string} type The type of event to listen to.
   * @param {function} listener A function to be called when the emitter fires
   *     the event specified in the type and then automatically removed.
   * @return {function} The function used to wrap the listener function argument.
   *     This function can be used as an argument to removeEvent.
   * @see {@link module:spotify-eventemitter#addOnceListener}
   */
  EventEmitter.prototype.addOnceEvent = function(type, listener) {
    return this.addOnceListener(type, listener);
  };
  
  /**
   * Removes an event listener from the emitter.
   *
   * **NOTE**: This method has been deprecated in favor of
   * {@link module:spotify-eventemitter#removeListener}.
   *
   * @deprecated since v2.0.0.
   * @param {string} type The type of event to remove.
   * @param {function} listener The listener function to remove. This must be a
   *     function that was added previously using addEvent.
   * @return {module:spotify-eventemitter} The event emitter.
   * @see {@link module:spotify-eventemitter#removeListener}
   */
  EventEmitter.prototype.removeEvent = function(type, listener) {
    return this.removeListener(type, listener);
  };
  
  /**
   * Removes multiple event listeners from the emitter.
   *
   * **NOTE**: This method has been deprecated in favor of
   * {@link module:spotify-eventemitter#removeListeners}.
   *
   * @deprecated since v2.0.0.
   * @param {Object.<string, function>} events An object, the keys of which
   *     correspond to the name of events to remove, and the value of each of
   *     these keys should be a function that would be removed as a listener.
   * @return {module:spotify-eventemitter} The event emitter.
   * @see {@link module:spotify-eventemitter#removeListeners}
   */
  EventEmitter.prototype.removeEvents = function(eventListeners) {
    return this.removeListeners(eventListeners);
  };
  
  /**
   * Fires an event on the emitter, optionally passing arguments to the listeners.
   *
   * **NOTE**: This method has been deprecated in favor of
   * {@link module:spotify-eventemitter#emit}.
   *
   * This method fire events asynchronously: the listeners are not called until
   * the next run loop. A third boolean parameter can be passed to change this
   * behaviour.
   *
   * @deprecated since v2.0.0.
   * @param {string} type The type of event to fire.
   * @param {Array.<*>=} opt_args A set of objects that would be passed to the
   *     event listeners as arguments.
   * @param {boolean=} opt_priority Passing true will fire the event synchronously.
   * @return {module:spotify-eventemitter} The event emitter.
   * @see {@link module:spotify-eventemitter#emit}
   */
  EventEmitter.prototype.fireEvent = function(type, opt_args, opt_priority) {
    if (opt_priority) {
      this.fireEventSync(type, opt_args);
    } else {
      var self = this;
      _defer(function() { self.fireEventSync(type, opt_args); });
    }
    return this;
  };
  
  /**
   * Fires an event on the emitter synchronously, optionally passing arguments to
   * the listeners.
   *
   * **NOTE**: This method has been deprecated in favor of
   * {@link module:spotify-eventemitter#emitSync}.
   *
   * @deprecated since v2.0.0.
   * @param {string} type The type of event to fire.
   * @param {Array.<*>=} opt_args A set of objects that would be passed to the
   *     event listeners as arguments.
   * @return {module:spotify-eventemitter} The event emitter.
   * @see {@link module:spotify-eventemitter#emitSync}
   */
  EventEmitter.prototype.fireEventSync = function(type, opt_args) {
    var self = this;
    var events = this._listenerMap && this._listenerMap[type];
    if (!events || !events.length) return this;
    events = events.slice(0);
    var i, l;
    if (!opt_args) {
      for (i = 0, l = events.length; i < l; i++) {
        events[i].call(self);
      }
    } else {
      if (!Array.isArray(opt_args)) {
        opt_args = [opt_args];
      }
      for (i = 0, l = events.length; i < l; i++) {
        events[i].apply(self, opt_args);
      }
    }
    return this;
  };
  
  /**
   * Removes an event listener or all event listeners from the emitter.
   *
   * **NOTE**: This method has been deprecated in favor of
   * {@link module:spotify-eventemitter#removeListener}.
   *
   * @deprecated since v2.0.0.
   * @param {string} type The type of event to remove.
   * @param {function=} opt_listener The listener function to remove. This must be
   *     a function that was added previously using addEvent. If this parameter is
   *     not given, all event listeners of the corresponding `type` argument will
   *     be removed.
   * @return {module:spotify-eventemitter} The event emitter.
   * @see {@link module:spotify-eventemitter#removeListener}
   * @see {@link module:spotify-eventemitter#removeListeners}
   */
  EventEmitter.prototype.off = function(type, opt_listener) {
    if (typeof opt_listener === 'function') {
      return this.removeEvent(type, opt_listener);
    }
    // if no listener set, remove all the listeners from the event
    this._listenerMap[type] = null;
    return this;
  };
  
  /**
   * Exporting
   */
  module.exports = EventEmitter;
  
  },{"spotify-deferred":547}],549:[function(require,module,exports){
  'use strict';
  
  /**
   * Function to add properties to an object
   * @param {Object} obj The input object
   * @param {Object} args The objects which are going to be injected
   * @return {Object} The extended object
   */
  var extend = function(obj, args) {
    var source;
  
    for (var i = 1; i < arguments.length; i++) {
      source = arguments[i];
      if (source) {
        for (var prop in source) {
          if (source.hasOwnProperty(prop)) {
            obj[prop] = source[prop];
          }
        }
      }
    }
    return obj;
  };
  
  
  /**
   * Export public interface
   */
  module.exports = extend;
  
  },{}],550:[function(require,module,exports){
  'use strict';
  
  module.exports = {
    inherit: require('./inherit'),
    extend: require('./extend')
  };
  
  },{"./extend":549,"./inherit":551}],551:[function(require,module,exports){
  'use strict';
  
  /**
   * Makes a class inherit from a superclass' prototype indirectly.
   *
   * @param {Spotify.ClassLike} Sub The class that will inherit.
   * @param {Spotify.ClassLike} Super The class to inherit from.
   */
  var inherit = function(Sub, Super) {
    var superProto = Super.prototype;
    function Superclass() {}
    Superclass.prototype = Sub._super = superProto;
    Superclass.prototype.constructor = Super;
    Sub.prototype = new Superclass();
  };
  
  
  /**
   * Export public interface
   */
  module.exports = inherit;
  
  },{}],552:[function(require,module,exports){
  /**
   * Copyright (c) 2017 Spotify AB
   *
   * Fast base62 encoder/decoder.
   *
   * Usage:
   *
   *   Base62.toHex('1C0pasJ0dS2Z46GKh2puYo') // -> '34ff970885ca8fa02c0d6e459377d5d0'
   *                         ^^^
   *                          |
   *               Length-22 base62-encoded ID.
   *         Lengths other than 22 or invalid base62 IDs
   *                  are not supported.
   *
   *   Base62.fromHex('34ff970885ca8fa02c0d6e459377d5d0') // -> '1C0pasJ0dS2Z46GKh2puYo'
   *                         ^^^
   *                          |
   *               Length-32 hex-encoded ID.
   *         Lengths other than 32 are not supported.
   *
   * Written by @ludde, programatically tested and documented by @felipec.
   */
  (function() {
    // Alphabets
    var HEX16 = '0123456789abcdef';
    var BASE62 =
      '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  
    // Hexadecimal fragments
    var HEX256 = [];
    HEX256.length = 256;
    for (var i = 0; i < 256; i++) {
      HEX256[i] = HEX16[i >> 4] + HEX16[i & 0xf];
    }
  
    // Look-up tables
    var ID62 = [];
    ID62.length = 128;
    for (var i = 0; i < BASE62.length; ++i) {
      ID62[BASE62.charCodeAt(i)] = i;
    }
    var ID16 = [];
    for (var i = 0; i < 16; i++) {
      ID16[HEX16.charCodeAt(i)] = i;
    }
    for (var i = 0; i < 6; i++) {
      ID16['ABCDEF'.charCodeAt(i)] = 10 + i;
    }
  
    var Base62 = {
      toHex: function(s) {
        if (s.length !== 22) {
          // Can only parse base62 ids with length == 22.
          // Invalid base62 ids will lead to garbage in the output.
          return null;
        }
  
        // 1 / (2^32)
        var MAX_INT_INV = 2.3283064365386963e-10;
        // 2^32
        var MAX_INT = 0x100000000;
        // 62^3
        var P62_3 = 238328;
  
        var p0, p1, p2, p3;
        var v;
        // First 7 characters fit in 2^53
        // prettier-ignore
        p0 =
          ID62[s.charCodeAt(0)] * 56800235584 +  // * 62^6
          ID62[s.charCodeAt(1)] * 916132832 +    // * 62^5
          ID62[s.charCodeAt(2)] * 14776336 +     // * 62^4
          ID62[s.charCodeAt(3)] * 238328 +       // * 62^3
          ID62[s.charCodeAt(4)] * 3844 +         // * 62^2
          ID62[s.charCodeAt(5)] * 62 +           // * 62^1
          ID62[s.charCodeAt(6)];                 // * 62^0
        p1 = (p0 * MAX_INT_INV) | 0;
        p0 -= p1 * MAX_INT;
        // 62^10 < 2^64
        v =
          ID62[s.charCodeAt(7)] * 3844 +
          ID62[s.charCodeAt(8)] * 62 +
          ID62[s.charCodeAt(9)];
        (p0 = p0 * P62_3 + v), (p0 = p0 - (v = (p0 * MAX_INT_INV) | 0) * MAX_INT);
        p1 = p1 * P62_3 + v;
        // 62^13 < 2^96
        v =
          ID62[s.charCodeAt(10)] * 3844 +
          ID62[s.charCodeAt(11)] * 62 +
          ID62[s.charCodeAt(12)];
        (p0 = p0 * P62_3 + v), (p0 = p0 - (v = (p0 * MAX_INT_INV) | 0) * MAX_INT);
        (p1 = p1 * P62_3 + v), (p1 = p1 - (v = (p1 * MAX_INT_INV) | 0) * MAX_INT);
        p2 = v;
        // 62^16 < 2^96
        v =
          ID62[s.charCodeAt(13)] * 3844 +
          ID62[s.charCodeAt(14)] * 62 +
          ID62[s.charCodeAt(15)];
        (p0 = p0 * P62_3 + v), (p0 = p0 - (v = (p0 * MAX_INT_INV) | 0) * MAX_INT);
        (p1 = p1 * P62_3 + v), (p1 = p1 - (v = (p1 * MAX_INT_INV) | 0) * MAX_INT);
        p2 = p2 * P62_3 + v;
        // 62^19 < 2^128
        v =
          ID62[s.charCodeAt(16)] * 3844 +
          ID62[s.charCodeAt(17)] * 62 +
          ID62[s.charCodeAt(18)];
        (p0 = p0 * P62_3 + v), (p0 = p0 - (v = (p0 * MAX_INT_INV) | 0) * MAX_INT);
        (p1 = p1 * P62_3 + v), (p1 = p1 - (v = (p1 * MAX_INT_INV) | 0) * MAX_INT);
        (p2 = p2 * P62_3 + v), (p2 = p2 - (v = (p2 * MAX_INT_INV) | 0) * MAX_INT);
        p3 = v;
        v =
          ID62[s.charCodeAt(19)] * 3844 +
          ID62[s.charCodeAt(20)] * 62 +
          ID62[s.charCodeAt(21)];
        (p0 = p0 * P62_3 + v), (p0 = p0 - (v = (p0 * MAX_INT_INV) | 0) * MAX_INT);
        (p1 = p1 * P62_3 + v), (p1 = p1 - (v = (p1 * MAX_INT_INV) | 0) * MAX_INT);
        (p2 = p2 * P62_3 + v), (p2 = p2 - (v = (p2 * MAX_INT_INV) | 0) * MAX_INT);
        (p3 = p3 * P62_3 + v), (p3 = p3 - (v = (p3 * MAX_INT_INV) | 0) * MAX_INT);
        if (v) {
          // carry not allowed
          return null;
        }
        // prettier-ignore
        return HEX256[p3>>>24]+HEX256[(p3>>>16)&0xFF]+HEX256[(p3>>>8)&0xFF]+HEX256[(p3)&0xFF] +
               HEX256[p2>>>24]+HEX256[(p2>>>16)&0xFF]+HEX256[(p2>>>8)&0xFF]+HEX256[(p2)&0xFF] +
               HEX256[p1>>>24]+HEX256[(p1>>>16)&0xFF]+HEX256[(p1>>>8)&0xFF]+HEX256[(p1)&0xFF] +
               HEX256[p0>>>24]+HEX256[(p0>>>16)&0xFF]+HEX256[(p0>>>8)&0xFF]+HEX256[(p0)&0xFF];
      },
  
      fromHex: function(s) {
        var i;
        var p0 = 0, p1 = 0, p2 = 0;
        for (i = 0; i < 10; i++) p2 = p2 * 16 + ID16[s.charCodeAt(i)];
        for (i = 0; i < 11; i++) p1 = p1 * 16 + ID16[s.charCodeAt(i + 10)];
        for (i = 0; i < 11; i++) p0 = p0 * 16 + ID16[s.charCodeAt(i + 21)];
        if (isNaN(p0 + p1 + p2)) {
          return null;
        }
        var P16_11 = 17592186044416; // 16^11
        var INV_62 = 1.0 / 62;
  
        var acc;
        var ret = '';
        i = 0;
        for (; i < 7; ++i) {
          acc = p2;
          p2 = Math.floor(acc * INV_62);
          acc = (acc - p2 * 62) * P16_11 + p1;
          p1 = Math.floor(acc * INV_62);
          acc = (acc - p1 * 62) * P16_11 + p0;
          p0 = Math.floor(acc * INV_62);
          ret = BASE62[acc - p0 * 62] + ret;
        }
        p1 += p2 * P16_11;
        for (; i < 15; ++i) {
          acc = p1;
          p1 = Math.floor(acc * INV_62);
          acc = (acc - p1 * 62) * P16_11 + p0;
          p0 = Math.floor(acc * INV_62);
          ret = BASE62[acc - p0 * 62] + ret;
        }
        p0 += p1 * P16_11;
        for (; i < 21; ++i) {
          acc = p0;
          p0 = Math.floor(acc * INV_62);
          ret = BASE62[acc - p0 * 62] + ret;
        }
        return BASE62[p0] + ret;
      },
  
      // Expose the lookup tables
      HEX256: HEX256, // number -> 'hh'
      ID16: ID16,  // hexadecimal char code -> 0..15
      ID62: ID62,  // base62 char code -> 0..61
    };
  
    if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
      module.exports = Base62;
    } else {
      window.Base62 = Base62;
    }
  })();
  
  },{}],553:[function(require,module,exports){
  'use strict';
  
  /**
   * @private
   */
  var Base62 = require('./base62');
  
  /**
   * The URI prefix for URIs.
   *
   * @const
   * @private
   */
  var URI_PREFIX = 'spotify:';
  
  /**
   * The URL prefix for Play.
   *
   * @const
   * @private
   */
  var PLAY_HTTP_PREFIX = 'http://play.spotify.com/';
  
  /**
   * The HTTPS URL prefix for Play.
   *
   * @const
   * @private
   */
  var PLAY_HTTPS_PREFIX = 'https://play.spotify.com/';
  
  /**
   * The URL prefix for Open.
   *
   * @const
   * @private
   */
  var OPEN_HTTP_PREFIX = 'http://open.spotify.com/';
  
  /**
   * The HTTPS URL prefix for Open.
   *
   * @const
   * @private
   */
  var OPEN_HTTPS_PREFIX = 'https://open.spotify.com/';
  
  var ERROR_INVALID = new TypeError('Invalid Spotify URI!');
  var ERROR_NOT_IMPLEMENTED = new TypeError('Not implemented!');
  
  
  /**
   * The format for the URI to parse.
   *
   * @enum {number}
   * @private
   */
  var Format = {
    URI: 0,
    URL: 1
  };
  
  /**
   * Represents the result of a URI splitting operation.
   *
   * @typedef {{
   *    format: Format,
   *    components: Array.<string>
   * }}
   * @see _splitIntoComponents
   * @private
   */
  var SplittedURI;
  
  /**
   * Split an string URI or HTTP/HTTPS URL into components, skipping the prefix.
   *
   * @param {string} str A string URI to split.
   * @return {SplittedURI} The parsed URI.
   * @private
   */
  var _splitIntoComponents = function(str) {
    var components;
    var format;
    var query;
    var anchor;
  
    var querySplit = str.split('?');
    if (querySplit.length > 1) {
      str = querySplit.shift();
      query = querySplit.pop();
  
      var queryHashSplit = query.split('#');
      if (queryHashSplit.length > 1) {
        query = queryHashSplit.shift();
        anchor = queryHashSplit.pop();
      }
  
      query = decodeQueryString(query);
    }
  
    var hashSplit = str.split('#');
    if (hashSplit.length > 1) {
      // first token
      str = hashSplit.shift();
      // last token
      anchor = hashSplit.pop();
    }
  
    if (str.indexOf(URI_PREFIX) === 0) {
      components = str.slice(URI_PREFIX.length).split(':');
      format = Format.URI;
    } else {
      // For HTTP URLs, ignore any query string argument
      str = str.split('?')[0];
  
      if (str.indexOf(PLAY_HTTP_PREFIX) === 0) {
        components = str.slice(PLAY_HTTP_PREFIX.length).split('/');
      } else if (str.indexOf(PLAY_HTTPS_PREFIX) === 0) {
        components = str.slice(PLAY_HTTPS_PREFIX.length).split('/');
      } else if (str.indexOf(OPEN_HTTP_PREFIX) === 0) {
        components = str.slice(OPEN_HTTP_PREFIX.length).split('/');
      } else if (str.indexOf(OPEN_HTTPS_PREFIX) === 0) {
        components = str.slice(OPEN_HTTPS_PREFIX.length).split('/');
      } else {
        throw ERROR_INVALID;
      }
      format = Format.URL;
    }
  
    if (anchor) {
      components.push(anchor);
    }
  
    return {
      format: format,
      components: components,
      query: query
    };
  };
  
  /**
   * Encodes a component according to a format.
   *
   * @param {string} component A component string.
   * @param {Format} format A format.
   * @return {string} An encoded component string.
   * @private
   */
  var _encodeComponent = function(component, format) {
    component = encodeURIComponent(component);
    if (format === Format.URI) {
      component = component.replace(/%20/g, '+');
    }
  
    // encode characters that are not encoded by default by encodeURIComponent
    // but that the Spotify URI spec encodes: !'*()
    component = component.replace(/[!'()]/g, escape);
    component = component.replace(/\*/g, '%2A');
  
    return component;
  };
  
  /**
   * Decodes a component according to a format.
   *
   * @param {string} component An encoded component string.
   * @param {Format} format A format.
   * @return {string} An decoded component string.
   * @private
   */
  var _decodeComponent = function(component, format) {
    var part = format == Format.URI ? component.replace(/\+/g, '%20') : component;
    return decodeURIComponent(part);
  };
  
  /**
   * Returns the components of a URI as an array.
   *
   * @param {URI} uri A uri.
   * @param {Format} format The output format.
   * @return {Array.<string>} An array of uri components.
   * @private
   */
  var _getComponents = function(uri, format) {
    var base62;
    if (uri.id) {
      base62 = uri._base62Id;
    }
  
    var components;
    var i;
    var len;
    switch (uri.type) {
      case URI.Type.ALBUM:
        components = [URI.Type.ALBUM, base62];
        if (uri.disc) {
          components.push(uri.disc);
        }
        return components;
      case URI.Type.AD:
        return [URI.Type.AD, uri._base62Id];
      case URI.Type.ARTIST:
        return [URI.Type.ARTIST, base62];
      case URI.Type.ARTIST_TOPLIST:
        return [URI.Type.ARTIST, base62, URI.Type.TOP, uri.toplist];
      case URI.Type.SEARCH:
        return [URI.Type.SEARCH, _encodeComponent(uri.query, format)];
      case URI.Type.TRACK:
        if (uri.context || uri.play) {
          base62 += encodeQueryString({
            context: uri.context,
            play: uri.play
          });
        }
        if (uri.anchor) {
          base62 += '#' + uri.anchor;
        }
        return [URI.Type.TRACK, base62];
      case URI.Type.TRACKSET:
        var trackIds = [];
        for (i = 0, len = uri.tracks.length; i < len; i++) {
          trackIds.push(uri.tracks[i]._base62Id);
        }
        trackIds = [trackIds.join(',')];
        // Index can be 0 sometimes (required for trackset)
        if (uri.index !== null) {
          trackIds.push('#', uri.index);
        }
        return [URI.Type.TRACKSET, _encodeComponent(uri.name)].concat(trackIds);
      case URI.Type.FACEBOOK:
        return [URI.Type.USER, URI.Type.FACEBOOK, uri.uid];
      case URI.Type.AUDIO_FILE:
        return [URI.Type.AUDIO_FILE, uri.extension, uri._base62Id];
      case URI.Type.FOLDER:
        return [URI.Type.USER, _encodeComponent(uri.username, format), URI.Type.FOLDER, uri._base62Id];
      case URI.Type.FOLLOWERS:
        return [URI.Type.USER, _encodeComponent(uri.username, format), URI.Type.FOLLOWERS];
      case URI.Type.FOLLOWING:
        return [URI.Type.USER, _encodeComponent(uri.username, format), URI.Type.FOLLOWING];
      case URI.Type.PLAYLIST:
        return [URI.Type.USER, _encodeComponent(uri.username, format), URI.Type.PLAYLIST, base62];
      case URI.Type.PLAYLIST_V2:
        return [URI.Type.PLAYLIST, base62];
      case URI.Type.STARRED:
        return [URI.Type.USER, _encodeComponent(uri.username, format), URI.Type.STARRED];
      case URI.Type.TEMP_PLAYLIST:
        return [URI.Type.TEMP_PLAYLIST, uri.origin, uri.data];
      case URI.Type.CONTEXT_GROUP:
        return [URI.Type.CONTEXT_GROUP, uri.origin, uri.name];
      case URI.Type.USER_TOPLIST:
        return [URI.Type.USER, _encodeComponent(uri.username, format), URI.Type.TOP, uri.toplist];
      // Legacy Toplist
      case URI.Type.USER_TOP_TRACKS:
        return [URI.Type.USER, _encodeComponent(uri.username, format), URI.Type.TOPLIST];
      case URI.Type.TOPLIST:
        return [URI.Type.TOP, uri.toplist].concat(uri.global ? [URI.Type.GLOBAL] : ['country', uri.country]);
      case URI.Type.INBOX:
        return [URI.Type.USER, _encodeComponent(uri.username, format), URI.Type.INBOX];
      case URI.Type.ROOTLIST:
        return [URI.Type.USER, _encodeComponent(uri.username, format), URI.Type.ROOTLIST];
      case URI.Type.PUBLISHED_ROOTLIST:
        return [URI.Type.USER, _encodeComponent(uri.username, format), URI.Type.PUBLISHED_ROOTLIST];
      case URI.Type.COLLECTION_TRACK_LIST:
        return [URI.Type.USER, _encodeComponent(uri.username, format), URI.Type.COLLECTION_TRACK_LIST, base62];
      case URI.Type.PROFILE:
        if (uri.args && uri.args.length > 0)
          return [URI.Type.USER, _encodeComponent(uri.username, format)].concat(uri.args);
        return [URI.Type.USER, _encodeComponent(uri.username, format)];
      case URI.Type.LOCAL_ARTIST:
        return [URI.Type.LOCAL, _encodeComponent(uri.artist, format)];
      case URI.Type.LOCAL_ALBUM:
        return [URI.Type.LOCAL, _encodeComponent(uri.artist, format), _encodeComponent(uri.album, format)];
      case URI.Type.LOCAL:
        return [URI.Type.LOCAL,
          _encodeComponent(uri.artist, format),
          _encodeComponent(uri.album, format),
          _encodeComponent(uri.track, format),
          uri.duration];
      case URI.Type.LIBRARY:
        return [URI.Type.USER, _encodeComponent(uri.username, format), URI.Type.LIBRARY].concat(uri.category ? [uri.category] : []);
      case URI.Type.IMAGE:
        return [URI.Type.IMAGE, uri._base62Id];
      case URI.Type.MOSAIC:
        components = uri.ids.slice(0);
        components.unshift(URI.Type.MOSAIC);
        return components;
      case URI.Type.RADIO:
        return [URI.Type.RADIO, uri.args];
      case URI.Type.SPECIAL:
        components = [URI.Type.SPECIAL];
        var args = uri.args || [];
        for (i = 0, len = args.length; i < len; ++i)
          components.push(_encodeComponent(args[i], format));
        return components;
      case URI.Type.STATION:
        components = [URI.Type.STATION];
        var args = uri.args || [];
        for (i = 0, len = args.length; i < len; i++) {
          components.push(_encodeComponent(args[i], format));
        }
        return components;
      case URI.Type.APPLICATION:
        components = [URI.Type.APP, uri._base62Id];
        var args = uri.args || [];
        for (i = 0, len = args.length; i < len; ++i)
          components.push(_encodeComponent(args[i], format));
        return components;
      case URI.Type.COLLECTION_ALBUM:
        return [URI.Type.USER, _encodeComponent(uri.username, format), URI.Type.COLLECTION, URI.Type.ALBUM, base62];
      case URI.Type.COLLECTION_MISSING_ALBUM:
        return [URI.Type.USER, _encodeComponent(uri.username, format), URI.Type.COLLECTION, URI.Type.ALBUM, base62, 'missing'];
      case URI.Type.COLLECTION_ARTIST:
        return [URI.Type.USER, _encodeComponent(uri.username, format), URI.Type.COLLECTION, URI.Type.ARTIST, base62];
      case URI.Type.COLLECTION:
        return [URI.Type.USER, _encodeComponent(uri.username, format), URI.Type.COLLECTION].concat(uri.category ? [uri.category] : []);
      case URI.Type.EPISODE:
        if (uri.context || uri.play) {
          base62 += encodeQueryString({
            context: uri.context,
            play: uri.play
          });
        }
        return [URI.Type.EPISODE, base62];
      case URI.Type.SHOW:
        return [URI.Type.SHOW, base62];
      case URI.Type.CONCERT:
        return [URI.Type.CONCERT, base62];
      default:
        throw ERROR_INVALID;
    }
  };
  
  var encodeQueryString = function(values) {
    var str = '?';
    for (var i in values) {
      if (values.hasOwnProperty(i) && values[i] !== undefined) {
        if (str.length > 1) {
          str += '&';
        }
        str += i + '=' + encodeURIComponent(values[i]);
      }
    }
    return str;
  };
  
  var decodeQueryString = function(str) {
    return str.split('&').reduce(function(object, pair) {
      pair = pair.split('=');
      object[pair[0]] = decodeURIComponent(pair[1]);
      return object;
    }, {});
  };
  
  /**
   * Parses the components of a URI into a real URI object.
   *
   * @param {Array.<string>} components The components of the URI as a string
   *     array.
   * @param {Format} format The format of the source string.
   * @return {URI} The URI object.
   * @private
   */
  var _parseFromComponents = function(components, format, query) {
    var _current = 0;
    query = query || {};
  
    var _getNextComponent = function() {
      return components[_current++];
    };
  
    var _getIdComponent = function() {
      var component = _getNextComponent();
  
      if (component.length > 22) {
        throw new Error('Invalid ID');
      }
      return component;
    };
  
    var _getRemainingComponents = function() {
      return components.slice(_current);
    };
  
    var _getRemainingString = function() {
      var separator = (format == Format.URI) ? ':' : '/';
      return components.slice(_current).join(separator);
    };
  
    var part = _getNextComponent();
    var id;
    var i;
    var len;
  
    switch (part) {
      case URI.Type.ALBUM:
        return URI.albumURI(_getIdComponent(), parseInt(_getNextComponent(), 10));
      case URI.Type.AD:
        return URI.adURI(_getNextComponent());
      case URI.Type.ARTIST:
        id = _getIdComponent();
        if (_getNextComponent() == URI.Type.TOP) {
          return URI.artistToplistURI(id, _getNextComponent());
        } else {
          return URI.artistURI(id);
        }
      case URI.Type.AUDIO_FILE:
        return URI.audioFileURI(_getNextComponent(), _getNextComponent());
      case URI.Type.TEMP_PLAYLIST:
        return URI.temporaryPlaylistURI(_getNextComponent(), _getRemainingString());
      case URI.Type.PLAYLIST:
        return URI.playlistV2URI(_getIdComponent());
      case URI.Type.SEARCH:
        return URI.searchURI(_decodeComponent(_getRemainingString(), format));
      case URI.Type.TRACK:
        return URI.trackURI(_getIdComponent(), _getNextComponent(), query.context, query.play);
      case URI.Type.TRACKSET:
        var name = _decodeComponent(_getNextComponent());
        var tracksArray = _getNextComponent();
        var hashSign = _getNextComponent();
        var index = parseInt(_getNextComponent(), 10);
        // Sanity check: %23 is URL code for "#"
        if (hashSign !== '%23' || isNaN(index)) {
          index = null;
        }
        var tracksetTracks = [];
        if (tracksArray) {
          tracksArray = _decodeComponent(tracksArray).split(',');
          for (i = 0, len = tracksArray.length; i < len; i++) {
            var trackId = tracksArray[i];
            tracksetTracks.push(URI.trackURI(trackId));
          }
        }
        return URI.tracksetURI(tracksetTracks, name, index);
      case URI.Type.CONTEXT_GROUP:
        return URI.contextGroupURI(_getNextComponent(), _getNextComponent());
      case URI.Type.TOP:
        var type = _getNextComponent();
        if (_getNextComponent() == URI.Type.GLOBAL) {
          return URI.toplistURI(type, null, true);
        } else {
          return URI.toplistURI(type, _getNextComponent(), false);
        }
      case URI.Type.USER:
        var username = _decodeComponent(_getNextComponent(), format);
        var text = _getNextComponent();
        if (username == URI.Type.FACEBOOK && text != null) {
          return URI.facebookURI(parseInt(text, 10));
        } else if (text != null) {
          switch (text) {
            case URI.Type.PLAYLIST:
              return URI.playlistURI(username, _getIdComponent());
            case URI.Type.FOLDER:
              return URI.folderURI(username, _getIdComponent());
            case URI.Type.COLLECTION_TRACK_LIST:
              return URI.collectionTrackList(username, _getIdComponent());
            case URI.Type.COLLECTION:
              var collectionItemType = _getNextComponent();
              switch (collectionItemType) {
                case URI.Type.ALBUM:
                  id = _getIdComponent();
                  if (_getNextComponent() === 'missing') {
                    return URI.collectionMissingAlbumURI(username, id);
                  } else {
                    return URI.collectionAlbumURI(username, id);
                  }
                case URI.Type.ARTIST:
                  return URI.collectionArtistURI(username, _getIdComponent());
                default:
                  return URI.collectionURI(username, collectionItemType);
              }
            case URI.Type.STARRED:
              return URI.starredURI(username);
            case URI.Type.FOLLOWERS:
              return URI.followersURI(username);
            case URI.Type.FOLLOWING:
              return URI.followingURI(username);
            case URI.Type.TOP:
              return URI.userToplistURI(username, _getNextComponent());
            case URI.Type.INBOX:
              return URI.inboxURI(username);
            case URI.Type.ROOTLIST:
              return URI.rootlistURI(username);
            case URI.Type.PUBLISHED_ROOTLIST:
              return URI.publishedRootlistURI(username);
            case URI.Type.TOPLIST:
              // legacy toplist
              return URI.userTopTracksURI(username);
            case URI.Type.LIBRARY:
              return URI.libraryURI(username, _getNextComponent());
          }
        }
        var rem = _getRemainingComponents();
        if (text != null && rem.length > 0) {
          return URI.profileURI(username, [text].concat(rem));
        } else if (text != null) {
          return URI.profileURI(username, [text]);
        } else {
          return URI.profileURI(username);
        }
      case URI.Type.LOCAL:
        var artistNameComponent = _getNextComponent();
        var artistName = artistNameComponent && _decodeComponent(artistNameComponent, format);
        var albumNameComponent = _getNextComponent();
        var albumName = albumNameComponent && _decodeComponent(albumNameComponent, format);
        var trackNameComponent = _getNextComponent();
        var trackName = trackNameComponent && _decodeComponent(trackNameComponent, format);
        var durationComponent = _getNextComponent();
        var duration = parseInt(durationComponent, 10);
        if (trackNameComponent !== undefined) {
          return URI.localURI(artistName, albumName, trackName, duration);
        } else if (albumNameComponent !== undefined) {
          return URI.localAlbumURI(artistName, albumName);
        } else {
          return URI.localArtistURI(artistName);
        }
      case URI.Type.IMAGE:
        return URI.imageURI(_getIdComponent());
      case URI.Type.MOSAIC:
        return URI.mosaicURI(components.slice(_current));
      case URI.Type.RADIO:
        return URI.radioURI(_getRemainingString());
      case URI.Type.SPECIAL:
        var args = _getRemainingComponents();
        for (i = 0, len = args.length; i < len; ++i)
          args[i] = _decodeComponent(args[i], format);
        return URI.specialURI(args);
      case URI.Type.STATION:
        return URI.stationURI(_getRemainingComponents());
      case URI.Type.EPISODE:
        return URI.episodeURI(_getIdComponent(), query.context, query.play);
      case URI.Type.SHOW:
        return URI.showURI(_getIdComponent());
      case URI.Type.CONCERT:
        return URI.concertURI(_getIdComponent());
      case '':
        break;
      default:
        if (part === URI.Type.APP) {
          id = _getNextComponent();
        } else {
          id = part;
        }
        var decodedId = _decodeComponent(id, format);
        if (_encodeComponent(decodedId, format) !== id) {
          break;
        }
        var args = _getRemainingComponents();
        for (i = 0, len = args.length; i < len; ++i)
          args[i] = _decodeComponent(args[i], format);
        return URI.applicationURI(decodedId, args);
    }
  
    throw ERROR_INVALID;
  };
  
  /**
   * A class holding information about a uri.
   *
   * @constructor
   * @param {URI.Type} type
   * @param {Object} props
   */
  function URI(type, props) {
    this.type = type;
  
    // Merge properties into URI object.
    for (var prop in props) {
      if (typeof props[prop] == 'function') {
        continue;
      }
      this[prop] = props[prop];
    }
  }
  
  // Lazy convert the id to hexadecimal only when requested
  Object.defineProperty(URI.prototype, 'id', {
    get: function() {
      if (!this._hexId) {
        this._hexId = this._base62Id ? URI.idToHex(this._base62Id) : undefined;
      }
      return this._hexId;
    },
    set: function(id) {
      this._base62Id = id ? URI.hexToId(id) : undefined;
      this._hexId = undefined;
    },
    enumerable: true,
    configurable: true
  });
  
  /**
   * Creates an application URI object from the current URI object.
   *
   * If the current URI object is already an application type, a copy is made.
   *
   * @return {URI} The current URI as an application URI.
   */
  URI.prototype.toAppType = function() {
    if (this.type == URI.Type.APPLICATION) {
      return URI.applicationURI(this.id, this.args);
    } else {
      var components = _getComponents(this, Format.URL);
      var id = components.shift();
      var len = components.length;
      if (len) {
        while (len--) {
          components[len] = _decodeComponent(components[len], Format.URL);
        }
      }
      if (this.type == URI.Type.RADIO) {
        components = components.shift().split(':');
      }
      var result = URI.applicationURI(id, components);
      return result;
    }
  };
  
  /**
   * Creates a URI object from an application URI object.
   *
   * If the current URI object is not an application type, a copy is made.
   *
   * @return {URI} The current URI as a real typed URI.
   */
  URI.prototype.toRealType = function() {
    if (this.type == URI.Type.APPLICATION) {
      return _parseFromComponents([this.id].concat(this.args), Format.URI);
    } else {
      return new URI(null, this);
    }
  };
  
  /**
   * Returns the URI representation of this URI.
   *
   * @return {String} The URI representation of this uri.
   */
  URI.prototype.toURI = function() {
    return URI_PREFIX + _getComponents(this, Format.URI).join(':');
  };
  
  /**
   * Returns the String representation of this URI.
   *
   * @return {String} The URI representation of this uri.
   * @see {URI#toURI}
   */
  URI.prototype.toString = function() {
    return this.toURI();
  };
  
  /**
   * Get the URL path of this uri.
   *
   * @param {boolean} opt_leadingSlash True if a leading slash should be prepended.
   * @return {String} The path of this uri.
   */
  URI.prototype.toURLPath = function(opt_leadingSlash) {
    var components = _getComponents(this, Format.URL);
    if (components[0] === URI.Type.APP) {
      components.shift();
    }
  
    // Some URIs are allowed to have empty components. It should be investigated
    // whether we need to strip empty components at all from any URIs. For now,
    // we check specifically for tracksets and local tracks and strip empty
    // components for all other URIs.
    //
    // For tracksets, it's permissible to have a path that looks like
    // 'trackset//trackURI' because the identifier parameter for a trackset can
    // be blank. For local tracks, some metadata can be missing, like missing
    // album name would be 'spotify:local:artist::track:duration'.
    var isTrackset = components[0] === URI.Type.TRACKSET;
    var isLocalTrack = components[0] === URI.Type.LOCAL;
    var shouldStripEmptyComponents = !isTrackset && !isLocalTrack;
  
    if (shouldStripEmptyComponents) {
      var _temp = [];
      for (var i = 0, l = components.length; i < l; i++) {
        var component = components[i];
        if (!!component) {
          _temp.push(component);
        }
      }
      components = _temp;
    }
    var path = components.join('/');
    return opt_leadingSlash ? '/' + path : path;
  };
  
  /**
   * Returns the Play URL string for the uri.
   *
   * @return {string} The Play URL string for the uri.
   */
  URI.prototype.toPlayURL = function() {
      return PLAY_HTTPS_PREFIX + this.toURLPath();
  };
  
  /**
   * Returns the URL string for the uri.
   *
   * @return {string} The URL string for the uri.
   * @see {URL#toPlayURL}
   */
  URI.prototype.toURL = function() {
    return this.toPlayURL();
  };
  
  /**
   * Returns the Open URL string for the uri.
   *
   * @return {string} The Open URL string for the uri.
   */
  URI.prototype.toOpenURL = function() {
    return OPEN_HTTPS_PREFIX + this.toURLPath();
  };
  
  /**
   * Returns the Play HTTPS URL string for the uri.
   *
   * @return {string} The Play HTTPS URL string for the uri.
   */
  URI.prototype.toSecurePlayURL = function() {
      return this.toPlayURL();
  };
  
  /**
   * Returns the HTTPS URL string for the uri.
   *
   * @return {string} The HTTPS URL string for the uri.
   * @see {URL#toSecurePlayURL}
   */
  URI.prototype.toSecureURL = function() {
    return this.toPlayURL();
  };
  
  /**
   * Returns the Open HTTPS URL string for the uri.
   *
   * @return {string} The Open HTTPS URL string for the uri.
   */
  URI.prototype.toSecureOpenURL = function() {
    return this.toOpenURL();
  };
  
  /**
   * Returns the id of the uri as a bytestring.
   *
   * @return {Array} The id of the uri as a bytestring.
   */
  URI.prototype.idToByteString = function() {
    var hexId = Base62.toHex(this._base62Id);
    if (!hexId) {
      var zero = '';
      for (var i = 0; i < 16; i++) {
        zero += String.fromCharCode(0);
      }
      return zero;
    }
    var data = '';
    for (var i = 0; i < 32; i += 2) {
      var upper = Base62.ID16[hexId.charCodeAt(i)];
      var lower = Base62.ID16[hexId.charCodeAt(i + 1)];
      var byte = (upper << 4) + lower;
      data += String.fromCharCode(byte);
    }
    return data;
  };
  
  URI.prototype.getPath = function() {
    var uri = this.toString().replace(/[#?].*/, '');
    return uri;
  }
  
  URI.prototype.getBase62Id = function() {
    return this._base62Id;
  }
  
  
  /**
  * Checks whether two URI:s refer to the same thing even though they might
  * not necessarily be equal.
  *
  * These two Playlist URIs, for example, refer to the same playlist:
  *
  *   spotify:user:napstersean:playlist:3vxotOnOGDlZXyzJPLFnm2
  *   spotify:playlist:3vxotOnOGDlZXyzJPLFnm2
  *
  * @param {*} uri The uri to compare identity for.
  * @return {boolean} Whether they shared idenitity
  */
  URI.prototype.isSameIdentity = function(uri) {
    var uriObject = URI.from(uri);
    if (!uriObject) return false;
    if (this.toString() === uri.toString()) return true;
    if (
      (this.type === URI.Type.PLAYLIST || this.type === URI.Type.PLAYLIST_V2) &&
      (uriObject.type === URI.Type.PLAYLIST || uriObject.type === URI.Type.PLAYLIST_V2)
    ) {
      return this.id === uriObject.id;
    } else if (this.type === URI.Type.STATION && uriObject.type === URI.Type.STATION) {
      var thisStationContextUriObject = _parseFromComponents(this.args, Format.URI);
      return !!thisStationContextUriObject &&
        thisStationContextUriObject.isSameIdentity(
          _parseFromComponents(uriObject.args, Format.URI)
        );
    } else {
      return false;
    }
  }
  
  /**
   * The various URI Types.
   *
   * Note that some of the types in this enum are not real URI types, but are
   * actually URI particles. They are marked so.
   *
   * @enum {string}
   */
  URI.Type = {
    EMPTY: 'empty',
    ALBUM: 'album',
    AD: 'ad',
    /** URI particle; not an actual URI. */
    APP: 'app',
    APPLICATION: 'application',
    ARTIST: 'artist',
    ARTIST_TOPLIST: 'artist-toplist',
    AUDIO_FILE: 'audiofile',
    COLLECTION: 'collection',
    COLLECTION_ALBUM: 'collection-album',
    COLLECTION_MISSING_ALBUM: 'collection-missing-album',
    COLLECTION_ARTIST: 'collection-artist',
    CONTEXT_GROUP: 'context-group',
    EPISODE: 'episode',
    /** URI particle; not an actual URI. */
    FACEBOOK: 'facebook',
    FOLDER: 'folder',
    FOLLOWERS: 'followers',
    FOLLOWING: 'following',
    /** URI particle; not an actual URI. */
    GLOBAL: 'global',
    IMAGE: 'image',
    INBOX: 'inbox',
    LOCAL_ARTIST: 'local-artist',
    LOCAL_ALBUM: 'local-album',
    LOCAL: 'local',
    LIBRARY: 'library',
    MOSAIC: 'mosaic',
    PLAYLIST: 'playlist',
    /** Only used for URI classification. Not a valid URI fragment. */
    PLAYLIST_V2: 'playlist-v2',
    PROFILE: 'profile',
    PUBLISHED_ROOTLIST: 'published-rootlist',
    RADIO: 'radio',
    ROOTLIST: 'rootlist',
    COLLECTION_TRACK_LIST: 'collectiontracklist',
    SEARCH: 'search',
    SHOW: 'show',
    CONCERT: 'concert',
    SPECIAL: 'special',
    STARRED: 'starred',
    STATION: 'station',
    TEMP_PLAYLIST: 'temp-playlist',
    /** URI particle; not an actual URI. */
    TOP: 'top',
    TOPLIST: 'toplist',
    TRACK: 'track',
    TRACKSET: 'trackset',
    /** URI particle; not an actual URI. */
    USER: 'user',
    USER_TOPLIST: 'user-toplist',
    USER_TOP_TRACKS: 'user-top-tracks',
    /** Deprecated contant. Please use USER_TOP_TRACKS. */
    USET_TOP_TRACKS: 'user-top-tracks'
  };
  
  /**
   * Creates a new URI object from a parsed string argument.
   *
   * @param {string} str The string that will be parsed into a URI object.
   * @throws TypeError If the string argument is not a valid URI, a TypeError will
   *     be thrown.
   * @return {URI} The parsed URI object.
   */
  URI.fromString = function(str) {
    var splitted = _splitIntoComponents(str);
    return _parseFromComponents(splitted.components, splitted.format, splitted.query);
  };
  
  /**
   * Parses a given object into a URI instance.
   *
   * Unlike URI.fromString, this function could receive any kind of value. If
   * the value is already a URI instance, it is simply returned.
   * Otherwise the value will be stringified before parsing.
   *
   * This function also does not throw an error like URI.fromString, but
   * instead simply returns null if it can't parse the value.
   *
   * @param {*} value The value to parse.
   * @return {URI?} The corresponding URI instance, or null if the
   *     passed value is not a valid value.
   */
  URI.from = function(value) {
    try {
      if (value instanceof URI) {
        return value;
      }
      if (typeof value == 'object' && value.type) {
        return new URI(null, value);
      }
      return URI.fromString(value.toString());
    } catch(e) {
      return null;
    }
  };
  
  /**
   * Creates a new URI from a bytestring.
   *
   * @param {URI.Type} type The type of the URI.
   * @param {ByteString} idByteString The ID of the URI as a bytestring.
   * @param {Object} opt_args Optional arguments to the URI constructor.
   * @return {URI} The URI object created.
   */
  URI.fromByteString = function(type, idByteString, opt_args) {
    while (idByteString.length != 16) {
      idByteString = String.fromCharCode(0) + idByteString;
    }
    var hexId = '';
    for (var i = 0; i < idByteString.length; i++) {
      var byte = idByteString.charCodeAt(i);
      hexId += Base62.HEX256[byte];
    }
    var id = Base62.fromHex(hexId);
    var args = opt_args || {};
    args.id = id;
    return new URI(type, args);
  };
  
  /**
   * Clones a given SpotifyURI instance.
   *
   * @param {URI} uri The uri to clone.
   * @return {URI?} An instance of URI.
   */
  URI.clone = function(uri) {
    if (!(uri instanceof URI)) {
      return null;
    }
    return new URI(null, uri);
  };
  
  /**
   * @deprecated
   */
  URI.getCanonical = function(username) {
    return this.getCanonical(username);
  };
  
  /**
   * Returns the canonical representation of a username.
   *
   * @param {string} username The username to encode.
   * @return {string} The encoded canonical representation of the username.
   */
  URI.getCanonicalUsername = function(username) {
    return _encodeComponent(username, Format.URI);
  };
  
  /**
   * Returns the non-canonical representation of a username.
   *
   * @param {string} username The username to encode.
   * @return {string} The unencoded canonical representation of the username.
   */
  URI.getDisplayUsername = function(username) {
    return _decodeComponent(username, Format.URI);
  };
  
  /**
   * Returns the hex representation of a Base62 encoded id.
   *
   * @param {string} id The base62 encoded id.
   * @return {string} The hex representation of the base62 id.
   */
  URI.idToHex = function(id) {
    if (id.length == 22) {
      return Base62.toHex(id);
    }
    return id;
  };
  
  /**
   * Returns the base62 representation of a hex encoded id.
   *
   * @param {string} hex The hex encoded id.
   * @return {string} The base62 representation of the id.
   */
  URI.hexToId = function(hex) {
    if (hex.length == 32) {
      return Base62.fromHex(hex);
    }
    return hex;
  };
  
  /**
   * Creates a new empty URI.
   *
   * @return {URI} The empty URI.
   */
  URI.emptyURI = function() {
    return new URI(URI.Type.EMPTY, {});
  };
  
  /**
   * Creates a new 'album' type URI.
   *
   * @param {string} id The id of the album.
   * @param {number} disc The disc number of the album.
   * @return {URI} The album URI.
   */
  URI.albumURI = function(id, disc) {
    return new URI(URI.Type.ALBUM, {id: id, disc: disc});
  };
  
  /**
   * Creates a new 'ad' type URI.
   *
   * @param {string} id The id of the ad.
   * @return {URI} The ad URI.
   */
  URI.adURI = function(id) {
    return new URI(URI.Type.AD, {id: id});
  };
  
  /**
   * Creates a new 'audiofile' type URI.
   *
   * @param {string} extension The extension of the audiofile.
   * @param {string} id The id of the extension.
   * @return {URI} The audiofile URI.
   */
  URI.audioFileURI = function(extension, id) {
    return new URI(URI.Type.AUDIO_FILE, {id: id, extension: extension});
  };
  
  /**
   * Creates a new 'artist' type URI.
   *
   * @param {string} id The id of the artist.
   * @return {URI} The artist URI.
   */
  URI.artistURI = function(id) {
    return new URI(URI.Type.ARTIST, {id: id});
  };
  
  /**
   * Creates a new 'artist-toplist' type URI.
   *
   * @param {string} id The id of the artist.
   * @param {string} toplist The toplist type.
   * @return {URI} The artist-toplist URI.
   */
  URI.artistToplistURI = function(id, toplist) {
    return new URI(URI.Type.ARTIST_TOPLIST, {id: id, toplist: toplist});
  };
  
  /**
   * Creates a new 'search' type URI.
   *
   * @param {string} query The unencoded search query.
   * @return {URI} The search URI
   */
  URI.searchURI = function(query) {
    return new URI(URI.Type.SEARCH, {query: query});
  };
  
  /**
   * Creates a new 'track' type URI.
   *
   * @param {string} id The id of the track.
   * @param {string} anchor The point in the track formatted as mm:ss
   * @return {URI} The track URI.
   */
  URI.trackURI = function(id, anchor, context, play) {
    return new URI(URI.Type.TRACK, {
      id: id,
      anchor: anchor,
      context: context ? URI.fromString(context) : context,
      play: play
    });
  };
  
  /**
   * Creates a new 'trackset' type URI.
   *
   * @param {Array.<URI>} tracks An array of 'track' type URIs.
   * @param {string} name The name of the trackset.
   * @param {number} index The index in the trackset.
   * @return {URI} The trackset URI.
   */
  URI.tracksetURI = function(tracks, name, index) {
    return new URI(URI.Type.TRACKSET, {
      tracks: tracks,
      name: name || '',
      index: isNaN(index) ? null : index
    });
  };
  
  /**
   * Creates a new 'facebook' type URI.
   *
   * @param {string} uid The user id.
   * @return {URI} The facebook URI.
   */
  URI.facebookURI = function(uid) {
    return new URI(URI.Type.FACEBOOK, {uid: uid});
  };
  
  /**
   * Creates a new 'followers' type URI.
   *
   * @param {string} username The non-canonical username.
   * @return {URI} The followers URI.
   */
  URI.followersURI = function(username) {
    return new URI(URI.Type.FOLLOWERS, {username: username});
  };
  
  /**
   * Creates a new 'following' type URI.
   *
   * @param {string} username The non-canonical username.
   * @return {URI} The following URI.
   */
  URI.followingURI = function(username) {
    return new URI(URI.Type.FOLLOWING, {username: username});
  };
  
  /**
   * Creates a new 'playlist' type URI.
   *
   * @param {string} username The non-canonical username of the playlist owner.
   * @param {string} id The id of the playlist.
   * @return {URI} The playlist URI.
   */
  URI.playlistURI = function(username, id) {
    return new URI(URI.Type.PLAYLIST, {username: username, id: id});
  };
  
  /**
   * Creates a new 'playlist-v2' type URI.
   *
   * @param {string} id The id of the playlist.
   * @return {URI} The playlist URI.
   */
  URI.playlistV2URI = function(id) {
    return new URI(URI.Type.PLAYLIST_V2, {id: id});
  };
  
  /**
   * Creates a new 'folder' type URI.
   *
   * @param {string} username The non-canonical username of the folder owner.
   * @param {string} id The id of the folder.
   * @return {URI} The folder URI.
   */
  URI.folderURI = function(username, id) {
    return new URI(URI.Type.FOLDER, {username: username, id: id});
  };
  
  /**
   * Creates a new 'collectiontracklist' type URI.
   *
   * @param {string} username The non-canonical username of the collection owner.
   * @param {string} id The id of the tracklist.
   * @return {URI} The collectiontracklist URI.
   */
  URI.collectionTrackList = function(username, id) {
    return new URI(URI.Type.COLLECTION_TRACK_LIST, {username: username, id: id});
  };
  
  /**
   * Creates a new 'starred' type URI.
   *
   * @param {string} username The non-canonical username of the starred list owner.
   * @return {URI} The starred URI.
   */
  URI.starredURI = function(username) {
    return new URI(URI.Type.STARRED, {username: username});
  };
  
  /**
   * Creates a new 'user-toplist' type URI.
   *
   * @param {string} username The non-canonical username of the toplist owner.
   * @param {string} toplist The toplist type.
   * @return {URI} The user-toplist URI.
   */
  URI.userToplistURI = function(username, toplist) {
    return new URI(URI.Type.USER_TOPLIST, {username: username, toplist: toplist});
  };
  
  /**
   * Creates a new 'user-top-tracks' type URI.
   *
   * @deprecated
   * @param {string} username The non-canonical username of the toplist owner.
   * @return {URI} The user-top-tracks URI.
   */
  URI.userTopTracksURI = function(username) {
    return new URI(URI.Type.USER_TOP_TRACKS, {username: username});
  };
  
  /**
   * Creates a new 'toplist' type URI.
   *
   * @param {string} toplist The toplist type.
   * @param {string} country The country code for the toplist.
   * @param {boolean} global True if this is a global rather than a country list.
   * @return {URI} The toplist URI.
   */
  URI.toplistURI = function(toplist, country, global) {
    return new URI(URI.Type.TOPLIST, {toplist: toplist, country: country, global: !!global});
  };
  
  /**
   * Creates a new 'inbox' type URI.
   *
   * @param {string} username The non-canonical username of the inbox owner.
   * @return {URI} The inbox URI.
   */
  URI.inboxURI = function(username) {
    return new URI(URI.Type.INBOX, {username: username});
  };
  
  /**
   * Creates a new 'rootlist' type URI.
   *
   * @param {string} username The non-canonical username of the rootlist owner.
   * @return {URI} The rootlist URI.
   */
  URI.rootlistURI = function(username) {
    return new URI(URI.Type.ROOTLIST, {username: username});
  };
  
  /**
   * Creates a new 'published-rootlist' type URI.
   *
   * @param {string} username The non-canonical username of the published-rootlist owner.
   * @return {URI} The published-rootlist URI.
   */
  URI.publishedRootlistURI = function(username) {
    return new URI(URI.Type.PUBLISHED_ROOTLIST, {username: username});
  };
  
  /**
   * Creates a new 'local-artist' type URI.
   *
   * @param {string} artist The artist name.
   * @return {URI} The local-artist URI.
   */
  URI.localArtistURI = function(artist) {
    return new URI(URI.Type.LOCAL_ARTIST, {artist: artist});
  };
  
  /**
   * Creates a new 'local-album' type URI.
   *
   * @param {string} artist The artist name.
   * @param {string} album The album name.
   * @return {URI} The local-album URI.
   */
  URI.localAlbumURI = function(artist, album) {
    return new URI(URI.Type.LOCAL_ALBUM, {artist: artist, album: album});
  };
  
  /**
   * Creates a new 'local' type URI.
   *
   * @param {string} artist The artist name.
   * @param {string} album The album name.
   * @param {string} track The track name.
   * @param {number} duration The track duration in ms.
   * @return {URI} The local URI.
   */
  URI.localURI = function(artist, album, track, duration) {
    return new URI(URI.Type.LOCAL, {
      artist: artist,
      album: album,
      track: track,
      duration: duration
    });
  };
  
  /**
   * Creates a new 'library' type URI.
   *
   * @param {string} username The non-canonical username of the rootlist owner.
   * @param {string} category The category of the library.
   * @return {URI} The library URI.
   */
  URI.libraryURI = function(username, category) {
    return new URI(URI.Type.LIBRARY, {username: username, category: category});
  };
  
  /**
   * Creates a new 'collection' type URI.
   *
   * @param {string} username The non-canonical username of the rootlist owner.
   * @param {string} category The category of the collection.
   * @return {URI} The collection URI.
   */
  URI.collectionURI = function(username, category) {
    return new URI(URI.Type.COLLECTION, {username: username, category: category});
  };
  
  /**
   * Creates a new 'temp-playlist' type URI.
   *
   * @param {string} origin The origin of the temporary playlist.
   * @param {string} data Additional data for the playlist.
   * @return {URI} The temp-playlist URI.
   */
  URI.temporaryPlaylistURI = function(origin, data) {
    return new URI(URI.Type.TEMP_PLAYLIST, {origin: origin, data: data});
  };
  
  /**
   * Creates a new 'context-group' type URI.
   *
   * @deprecated
   * @param {string} origin The origin of the temporary playlist.
   * @param {string} name The name of the context group.
   * @return {URI} The context-group URI.
   */
  URI.contextGroupURI = function(origin, name) {
    return new URI(URI.Type.CONTEXT_GROUP, {origin: origin, name: name});
  };
  
  /**
   * Creates a new 'profile' type URI.
   *
   * @param {string} username The non-canonical username of the rootlist owner.
   * @param {Array.<string>} args A list of arguments.
   * @return {URI} The profile URI.
   */
  URI.profileURI = function(username, args) {
    return new URI(URI.Type.PROFILE, {username: username, args: args});
  };
  
  /**
   * Creates a new 'image' type URI.
   *
   * @param {string} id The id of the image.
   * @return {URI} The image URI.
   */
  URI.imageURI = function(id) {
    return new URI(URI.Type.IMAGE, {id: id});
  };
  
  /**
   * Creates a new 'mosaic' type URI.
   *
   * @param {Array.<string>} ids The ids of the mosaic immages.
   * @return {URI} The mosaic URI.
   */
  URI.mosaicURI = function(ids) {
    return new URI(URI.Type.MOSAIC, {ids: ids});
  };
  
  /**
   * Creates a new 'radio' type URI.
   *
   * @param {string} args The radio seed arguments.
   * @return {URI} The radio URI.
   */
  URI.radioURI = function(args) {
    args = typeof args === 'undefined' ? '' : args;
    return new URI(URI.Type.RADIO, {args: args});
  };
  
  /**
   * Creates a new 'special' type URI.
   *
   * @param {Array.<string>} args An array containing the other arguments.
   * @return {URI} The special URI.
   */
  URI.specialURI = function(args) {
    args = typeof args === 'undefined' ? [] : args;
    return new URI(URI.Type.SPECIAL, {args: args});
  };
  
  /**
   * Creates a new 'station' type URI.
   *
   * @param {Array.<string>} args An array of arguments for the station.
   * @return {URI} The station URI.
   */
  URI.stationURI = function(args) {
    args = typeof args === 'undefined' ? [] : args;
    return new URI(URI.Type.STATION, {args: args});
  };
  
  /**
   * Creates a new 'application' type URI.
   *
   * @param {string} id The id of the application.
   * @param {Array.<string>} args An array containing the arguments to the app.
   * @return {URI} The application URI.
   */
  URI.applicationURI = function(id, args) {
    args = typeof args === 'undefined' ? [] : args;
    return new URI(URI.Type.APPLICATION, {id: id, args: args});
  };
  
  /**
   * Creates a new 'collection-album' type URI.
   *
   * @param {string} username The non-canonical username of the rootlist owner.
   * @param {string} id The id of the album.
   * @return {URI} The collection-album URI.
   */
  URI.collectionAlbumURI = function(username, id) {
    return new URI(URI.Type.COLLECTION_ALBUM, {username: username, id: id});
  };
  
  /**
   * Creates a new 'collection-album-missing' type URI.
   *
   * @param {string} username The non-canonical username of the rootlist owner.
   * @param {string} id The id of the album.
   * @return {URI} The collection-album-missing URI.
   */
  URI.collectionMissingAlbumURI = function(username, id) {
    return new URI(URI.Type.COLLECTION_MISSING_ALBUM, {username: username, id: id});
  };
  
  /**
   * Creates a new 'collection-artist' type URI.
   *
   * @param {string} username The non-canonical username of the rootlist owner.
   * @param {string} id The id of the artist.
   * @return {URI} The collection-artist URI.
   */
  URI.collectionArtistURI = function(username, id) {
    return new URI(URI.Type.COLLECTION_ARTIST, {username: username, id: id});
  };
  
  /**
   * Creates a new 'episode' type URI.
   *
   * @param {string} id The id of the episode.
   * @param {string} context An optional context URI
   * @param {boolean} play Toggles autoplay in the episode URI
   * @return {URI} The episode URI.
   */
  URI.episodeURI = function(id, context, play) {
    return new URI(URI.Type.EPISODE, {
      id: id,
      context: context ? URI.fromString(context) : context,
      play: play
    });
  };
  
  /**
   * Creates a new 'show' type URI.
   *
   * @param {string} id The id of the show.
   * @return {URI} The show URI.
   */
  URI.showURI = function(id) {
    return new URI(URI.Type.SHOW, {id: id});
  };
  
  /**
   * Creates a new 'concert' type URI.
   *
   * @param {string} id The id of the concert.
   * @return {URI} The concert URI.
   */
  URI.concertURI = function(id) {
    return new URI(URI.Type.CONCERT, {id: id});
  };
  
  URI.isAlbum = function(uri) { return (URI.from(uri) || {}).type === URI.Type.ALBUM; };
  URI.isAd = function(uri) { return (URI.from(uri) || {}).type === URI.Type.AD; };
  URI.isApplication = function(uri) { return (URI.from(uri) || {}).type === URI.Type.APPLICATION; };
  URI.isArtist = function(uri) { return (URI.from(uri) || {}).type === URI.Type.ARTIST; };
  URI.isCollection = function(uri) { return (URI.from(uri) || {}).type === URI.Type.COLLECTION; };
  URI.isCollectionAlbum = function(uri) { return (URI.from(uri) || {}).type === URI.Type.COLLECTION_ALBUM; };
  URI.isCollectionArtist = function(uri) { return (URI.from(uri) || {}).type === URI.Type.COLLECTION_ARTIST; };
  URI.isEpisode = function(uri) { return (URI.from(uri) || {}).type === URI.Type.EPISODE; };
  URI.isFacebook = function(uri) { return (URI.from(uri) || {}).type === URI.Type.FACEBOOK; };
  URI.isFolder = function(uri) { return (URI.from(uri) || {}).type === URI.Type.FOLDER; };
  URI.isLocalArtist = function(uri) { return (URI.from(uri) || {}).type === URI.Type.LOCAL_ARTIST; };
  URI.isLocalAlbum = function(uri) { return (URI.from(uri) || {}).type === URI.Type.LOCAL_ALBUM; };
  URI.isLocalTrack = function(uri) { return (URI.from(uri) || {}).type === URI.Type.LOCAL; };
  URI.isMosaic = function(uri) { return (URI.from(uri) || {}).type === URI.Type.MOSAIC; };
  URI.isPlaylistV1 = function(uri) { return (URI.from(uri) || {}).type === URI.Type.PLAYLIST; };
  URI.isPlaylistV2 = function(uri) { return (URI.from(uri) || {}).type === URI.Type.PLAYLIST_V2; };
  URI.isRadio = function(uri) { return (URI.from(uri) || {}).type === URI.Type.RADIO; };
  URI.isRootlist = function(uri) { return (URI.from(uri) || {}).type === URI.Type.ROOTLIST; };
  URI.isSearch = function(uri) { return (URI.from(uri) || {}).type === URI.Type.SEARCH; };
  URI.isShow = function(uri) { return (URI.from(uri) || {}).type === URI.Type.SHOW; };
  URI.isConcert = function(uri) { return (URI.from(uri) || {}).type === URI.Type.CONCERT; };
  URI.isStation = function(uri) { return (URI.from(uri) || {}).type === URI.Type.STATION; };
  URI.isTrack = function(uri) { return (URI.from(uri) || {}).type === URI.Type.TRACK; };
  URI.isProfile = function(uri) { return (URI.from(uri) || {}).type === URI.Type.PROFILE; };
  URI.isPlaylistV1OrV2 = function(uri) {
    var uriObject = URI.from(uri);
    return !!uriObject && (uriObject.type === URI.Type.PLAYLIST || uriObject.type === URI.Type.PLAYLIST_V2);
  };
  
  /**
   * Export public interface
   */
  module.exports = URI;
  
  },{"./base62":552}],554:[function(require,module,exports){
  var player = require("./player");
  var types = require("./types");
  var play = require("./play");
  var play_history = require("./play_history");
  
  exports.Play = play.Play;
  exports.Player = player.Player;
  exports.PlayHistory = play_history.PlayHistory;
  exports.PlayerContext = types.Context;
  exports.PlayOptions = types.PlayOptions;
  exports.PlayerRestrictions = types.Restrictions;
  exports.PlayerSuppressions = types.Suppressions;
  exports.PlayerTrack = types.Track;
  exports.PlayerContextPage = types.ContextPage;
  exports.IndexPath = types.IndexPath;
  
  },{"./play":555,"./play_history":556,"./player":557,"./types":558}],555:[function(require,module,exports){
  var cosmos = require('spotify-cosmos-api');
  var nodefn = require('when/node');
  
  /**
   * Play is exposed as Player.Play. It represents a prepared playback action.
   * Play objects are obtained from the preparePlay method on the Player; the
   * constructor is not a public API.
   *
   * Instantiating a Play object does not make a Cosmos request, the caller is
   * expected to make that call and pass in a promise of the result in the
   * createSessionPromise parameter.
   *
   * @param {Cosmos.resolver} resolver The resolver to send requests to.
   * @param {Promise} createSessionPromise A promise of a Cosmos.Response for
   *     the call to sp://player/v2/xyz/session.
   * @param {Player} player The player that initiated the call.
   * @param {function()=Number} getTime Clock. Should usually return
   *     new Date().getTime()
   * @param {module:spotify-player/types.Context} The context that was passed
   *     to the preparePlay call.
   * @param {Cosmos.PlayOptions=} opt_options The options that were passed to
   *     the preparePlay call.
   */
  function Play(resolver, createSessionPromise, player, getTime, context, opt_options) {
    if (!(this instanceof Play)) {
      return new Play(resolver, createSessionPromise, player, getTime, context, opt_options);
    }
  
    if (!resolver || !createSessionPromise || !player || !getTime || !context) {
      throw new TypeError('Missing parameters for Play');
    }
  
    /**
     * @type {Cosmos.resolver}
     */
    this._resolver = resolver;
  
    /**
     * @type {Promise}
     */
    this._createSessionPromise = createSessionPromise;
  
    /**
     * @type {Player}
     */
    this._player = player;
  
    /**
     * @type {function()=Number}
     */
    this._getTime = getTime;
  
    /**
     * @type {module:spotify-player/types.Context}
     */
    this._context = context;
  
    /**
     * @type {Cosmos.PlayOptions=}
     */
    this._opt_options = opt_options;
  }
  
  /**
   * @return Promise of the session URL
   */
  Play.prototype._sessionUrl = function() {
    return this._createSessionPromise.then(function(response) {
      return response.getJSONBody().session;
    });
  };
  
  Play.prototype._createPlayRequest = function(sessionUrl) {
    var body = {
      logging_params: {
        command_initiated_time: this._getTime()
      }
    };
    return new cosmos.Request(cosmos.Action.POST, sessionUrl + '/play', null, body);
  };
  
  Play.prototype._createUpdateRequest = function(sessionUrl, body) {
    body.logging_params = {
      command_initiated_time: this._getTime()
    };
    return new cosmos.Request(cosmos.Action.POST, sessionUrl + '/update', null, body);
  };
  
  /**
   * Perform the playback action that this handle represents.
   *
   * It is legal to call play more than once, but the playback action is only
   * prefetched the first time. If subsequent preparation is required, a new
   * Play object should be obtained.
   */
  Play.prototype.play = function(opt_callback) {
    opt_callback = opt_callback || function() {};
  
    var self = this;
    if (this._createSessionPromise) {
      this._sessionUrl()
        .then(function(sessionUrl) {
          return nodefn.call(
            self._resolver.resolve.bind(self._resolver), self._createPlayRequest(sessionUrl));
        })
        .done(function() {
            opt_callback(null);
          }, function(error) {
            if (error.response && error.response.getStatusCode() === 404) {
              // The session has been invalidated. Try again without prefetching.
              self._createSessionPromise = null;
              self.play(opt_callback);
            } else {
              var wrappedError = new Error('Failed to prepare player session: ' + error.message);
              wrappedError.cause = function() { return error; };
              opt_callback(wrappedError);
            }
          });
    } else {
      this._player.play(this._context, this._opt_options, opt_callback);
    }
  };
  
  Play.prototype._update = function(body, opt_callback) {
    opt_callback = opt_callback || function() {};
  
    var self = this;
    if (!this._createSessionPromise) {
      return opt_callback(new Error('Cannot update invalidated Play'));
    }
  
    this._sessionUrl()
      .then(function(sessionUrl) {
        return nodefn.call(
          self._resolver.resolve.bind(self._resolver), self._createUpdateRequest(sessionUrl, body));
      })
      .done(function() {
          opt_callback(null);
        }, function(error) {
          var wrappedError = new Error('Failed to update player session: ' + error.message);
          wrappedError.cause = function() { return error; };
          opt_callback(wrappedError);
        });
  };
  
  Play.prototype.updateContext = function(context, opt_callback) {
    this._update({ context: context }, opt_callback);
  };
  
  Play.prototype.updatePage = function(page, opt_callback) {
    this._update({ page: page }, opt_callback);
  };
  
  Play.prototype.updateTrack = function(track, opt_callback) {
    this._update({ track: track }, opt_callback);
  };
  
  Play.prototype.updateViewUri = function(view_uri, opt_callback) {
    this._update({ view_uri: view_uri }, opt_callback);
  };
  
  /**
   * Invalidate the handle. This will cause any ongoing prefetching work to be
   * cancelled. It is good if any holder of a handle calls this method as soon
   * as it knows that it is not interested in its play action anymore.
   *
   * It is illegal to call play after this has been called. Invalidating an
   * invalidated Play is a no-op.
   */
  Play.prototype.invalidate = function(opt_callback) {
    if (!this._createSessionPromise) {
      // Already invalidated
      return;
    }
  
    opt_callback = opt_callback || function() {};
  
    var self = this;
    this._sessionUrl().done(function(sessionUrl) {
      var request = new cosmos.Request(
        cosmos.Action.DELETE, sessionUrl, null, null);
      self._resolver.resolve(request, function() {
        opt_callback(null);
      });
    }, function() {
      // Ignore errors
      opt_callback(null);
    });
  
    this._createSessionPromise = null;
  };
  
  exports.Play = Play;
  
  },{"spotify-cosmos-api":564,"when/node":592}],556:[function(require,module,exports){
  var cosmos = require('spotify-cosmos-api');
  
  var PLAY_HISTORY_URI = 'sp://play-history/v1';
  
  function PlayHistory(resolver) {
    if (!(this instanceof PlayHistory)) {
      return new PlayHistory(resolver);
    }
  
    if (!resolver) {
      throw new TypeError('Missing resolver for PlayHistory');
    }
  
    /**
     * @type {Cosmos.resolver}
     */
    this._resolver = resolver;
  }
  
  /**
   * Gets the tracks in the play history.
   *
   * @param {function(Error, Cosmos.Response=)} callback The function
   *    that will be called when the queue is available.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  PlayHistory.prototype.get = function(callback) {
    var request = new cosmos.Request(cosmos.Action.GET, PLAY_HISTORY_URI);
    return this._resolver.resolve(request, callback);
  };
  
  /**
   * Subscribe to play history. There will be an initial callback when subscribing.
   *
   * @param {function(Error, Cosmos.Response=)} callback The function
   *    that will be called when the queue changes.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
   PlayHistory.prototype.subscribe = function(callback) {
    var request = new cosmos.Request(cosmos.Action.SUB, PLAY_HISTORY_URI);
    return this._resolver.resolve(request, callback);
  };
  
  exports.PlayHistory = PlayHistory;
  
  },{"spotify-cosmos-api":564}],557:[function(require,module,exports){
  var cosmos = require('spotify-cosmos-api');
  var nodefn = require('when/node');
  var play = require('./play');
  
  var PLAYER_URI = 'sp://player/v2/';
  var DEFAULT_PLAYER_ID = 'main';
  
  /**
   * The object decribing the context for the playback
   * Can be used to pass viewUri or externalReferrer specific
   * for the play action.
   *
   * @typedef {Object} ContextConfig
   * @property {Cosmos.PlayerContext} context The context to play
   * @property {string=} opt_viewId Optional. Set to override the default viewUri
   * @property {string=} opt_externalReferrer Optional. Set to indicate that
   *      the playback is owne by external website/app.
   */
  
  /**
   * Player is the main entry point to control music playback with cosmos.
   *
   * Player in itself isn't what deals with playback; it is merely an object
   * that controls playback and provides ability to observe the player state.
   * There can be more than one Player object at the same time, and they
   * will sync up as one would expect.
   *
   * In practice there will be at least one Player object per feature that
   * wants to control playback. The feature can choose to create one per view
   * or have only one object, it doesn't really matter.
   *
   * @param {Cosmos.resolver} resolver The resolver to send requests to.
   * @param {String} viewUri The uri of the view that owns the playback.
   * @param {String} featureIdentifier The identifier of the feature that started
   *     playback. This is arguably the most important field of this class. It is
   *     used for logging messages, in particular EndSong and EndVideo. (In EndSong,
   *     it ends up in source_start / source_end.) It should be a short human
   *     readable name of the feature, for example "radio", "album", "playlist".
   *     Ideally, it should be the same for a given feature across platforms.
   *
   *     Each feature should have only one string constant that it passes in here.
   *     You must not pass lots of distinct values here; for instance do not send
   *     something that contains the username or artist identifier.
   * @param {String} featureVersion The version of the feature. It often makes sense to set
   *     this value to the version of the spm package, for instance "0.7.5".
   * @param {{playerId: string}} opt_options The additional parameters for the player.
   *  param {string} options.playerId Can be used to override the id of the player
   *      which will result in a different endpoint for the player requests.
   */
  function Player(resolver, viewUri, featureIdentifier, featureVersion, opt_options) {
    if (!(this instanceof Player)) {
      return new Player(resolver, viewUri, featureIdentifier, featureVersion);
    }

    if (!resolver || !viewUri || !featureIdentifier || !featureVersion) {
      throw new TypeError('Missing parameters for Player');
    }
  
    this._getTime = opt_options && opt_options.getTime || function() {
      return new Date().getTime();
    };
  
    /**
     * @type {string}
     */
    this._id = opt_options && opt_options.playerId || DEFAULT_PLAYER_ID;
  
    /**
     * @type {string}
     */
    this._referrerIdentifier = opt_options && opt_options.referrerIdentifier;
  
    /**
     * @type {Cosmos.resolver}
     */
    this._resolver = resolver;
  
    /**
     * @type {string}
     */
    this._viewUri = viewUri;
  
    /**
     * @type {string}
     */
    this._featureId = featureIdentifier;
  
    /**
     * @type {string}
     */
    this._featureVersion = featureVersion;
  }
  
  /**
   * The actions that can be understood by the PlayerResolver
   * in Cosmos Router.
   */
  Player.Actions = {
    PROBE_PLAY: 'probe_play',
    PLAY: 'play',
    SESSION: 'session',
    STOP: 'stop',
    UPDATE: 'update',
    PAUSE: 'pause',
    RESUME: 'resume',
    SKIP_NEXT: 'skip_next',
    SKIP_PREV: 'skip_prev',
    SHUFFLE: 'set_shuffling_context',
    REPEAT_CONTEXT: 'set_repeating_context',
    REPEAT_TRACK: 'set_repeating_track',
    SEEK_TO: 'seek_to'
  };
  
  /**
   * Private helper method for probe_play, play, and preparePlay
   */
  Player.prototype._playOrPrepareOrProbe = function(action, context, opt_options, opt_callback) {
    if (!context) {
      throw new TypeError('Invalid `context` object');
    }
  
    // Allow to not send the opt_options
    if (opt_options instanceof Function && !opt_callback) {
      opt_callback = opt_options;
      opt_options = null;
    }
  
    var params = {};
    params.context = context;
    params.play_origin = this._makePlayOrigin();
    params.options = opt_options;
  
    return this._sendRequestWithParams(action, params, opt_callback);
  };
  
  /**
   * Add a track to the manually queued tracks.
   *
   * @param {module:spotify-player/types.Track} track The track to add to the queue.
   * @param {function(Error, Cosmos.Response=)} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.addToQueue = function(track, opt_callback) {
    if (!track) {
      throw new TypeError('Invalid `track` object');
    }
    return this._sendRequestWithParams('add_to_queue', { track: track }, opt_callback);
  };
  
  /**
   * Asks the player if the given context is allowed to be played. This presents
   * the same API as play, but it will not actually initiate playback. Rather the
   * response status code will indicate if playback is permitted (200 OK) or not
   * (403 Forbidden).
   *
   * @see {@link Player#play}
   */
  Player.prototype.probePlay = function(context, opt_options, opt_callback) {
    return this._playOrPrepareOrProbe(Player.Actions.PROBE_PLAY, context, opt_options, opt_callback);
  };
  
  /**
   * Plays the given context.
   * Since both options and callback are optional this method can
   * take from 1 to 3 arguments. if callback is specified, it has to be
   * the last argument:
   *
   *    player.play(myContext);
   *    player.play(myContext, myPlayOptions);
   *    player.play(myContext, function myCallback() {});
   *    player.play(myContext, myPlayOptions, function myCallback() {});
   *
   * @param {module:spotify-player/types.Context} context The context to play.
   * @param {Cosmos.PlayOptions=} opt_options Optional play options.
   * @param {function(Error, Cosmos.Response?)=} opt_callback Optional
   *    callback that will be executed after player request is resolved.
   *
   *    NOTE: The reponse in the callback only has information about the
   *    request itself (i.e. if it was resolved correctly). If you want to have
   *    information about currently playing context you should subscribe to
   *    player events or fetch the current state using. getContext.
   *
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.play = function(context, opt_options, opt_callback) {
    return this._playOrPrepareOrProbe(Player.Actions.PLAY, context, opt_options, opt_callback);
  };
  
  /**
   * This method is like play, but it doesn't actually initiate playback, it
   * merely expresses intent that the user is rather likely going to do a play
   * action like this soon.
   *
   * @return {Player.Play} A handle that is used for actually performing the
   *     playback action. When the object will no longer be used, the user is
   *     responsible for calling invalidate on it, otherwise it will keep
   *     holding resources. As long as this object is not invalidated, the
   *     playback system will take that as a hint that it should try to prepare
   *     playback like this.
   */
  Player.prototype.preparePlay = function(context, opt_options) {
    var sessionPromise = nodefn.call(
      this._playOrPrepareOrProbe.bind(this), Player.Actions.SESSION, context, opt_options);
  
    return new play.Play(this._resolver, sessionPromise, this, this._getTime, context, opt_options);
  };
  
  /**
   * Stop the playback and clear the context.
   *
   * @param {function=} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.stop = function(opt_callback) {
    return this._sendRequestWithAction(Player.Actions.STOP, opt_callback);
  };
  
  /**
   * Update the current context.
   * Deprecated, use update functions for handle returned by preparePlay instead.
   *
   * @param {module:spotify-player/types.Context} context The context to update to.
   * @param {function=} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.update = function(context, opt_callback) {
    if (!context) {
      throw new TypeError('Invalid `context` object');
    }
    var params = { context: context };
    return this._sendRequestWithParams(Player.Actions.UPDATE, params, opt_callback);
  };
  
  /**
   * Resume the playback.
   *
   * @param {function=} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.resume = function(opt_callback) {
    return this._sendRequestWithAction(Player.Actions.RESUME, opt_callback);
  };
  
  /**
   * Pause the playback.
   *
   * @param {function=} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.pause = function(opt_callback) {
    return this._sendRequestWithAction(Player.Actions.PAUSE, opt_callback);
  };
  
  /**
   * Skip to the previous song.
   *
   * @param {Object=} opt_skip_options The object with the skip options
   *    like allow_seeking
   * @param {function=} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.skipToPrevTrack = function(opt_skip_options, opt_callback) {
    if (arguments.length === 1) {
      // In order to keep backwards compatibility we still need to support
      // passing just the callback as the first parameter
      if (typeof opt_skip_options === 'function') {
        opt_callback = opt_skip_options;
        opt_skip_options = null;
      }
    }
    var params = {};
    if (opt_skip_options) {
      params.options = opt_skip_options;
    }
    return this._sendRequestWithParams(Player.Actions.SKIP_PREV, params, opt_callback);
  };
  
  /**
   * Skip to the next song.
   *
   * @param {function=} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.skipToNextTrack = function(opt_options, opt_callback) {
    if (arguments.length === 1) {
      // In order to keep backwards compatibility we still need to support
      // passing just the callback as the first parameter
      if (typeof opt_options === 'function') {
        opt_callback = opt_options;
        opt_options = null;
      }
    }
    var params = {};
    if (opt_options) {
      params.options = opt_options;
    }
    return this._sendRequestWithParams(Player.Actions.SKIP_NEXT, params, opt_callback);
  };
  
  /**
   * Skip backwards until it gets to the specified track. This method never skips
   * to the beginning of the current track. For that, use seekTo.
   *
   * @discussion If the reverse runs out of tracks or skipping becomes disallowed
   *    for some other reason, this method will give up when it can no longer
   *    continue. This means that calling this for MFT users will almost
   *    certainly be a bad idea.
   *
   *    The intended purpose of this method is to make it possible to implement
   *    a view of the current future/reverse and allow the user to skip in it.
   *
   *    This method will look at the track URI and the track's UID (if present)
   *    to determine when it has reached its goal.
   *
   * @param {Object} track The track object, as it was provided from the
   *    player state reverse.
   * @param {function=} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.skipToSpecificPrevTrack = function(track, opt_callback) {
    return this._sendRequestWithParams(Player.Actions.SKIP_PREV, { track: track }, opt_callback);
  };
  
  /**
   * Skip forwards until it gets to the specified track.
   *
   * @discussion See discussion for -skipToPreviousTrack:
   *
   * @param {Object} track The track object, as it was provided from the
   *    player state reverse.
   * @param {function=} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.skipToSpecificNextTrack = function(track, opt_callback) {
    return this._sendRequestWithParams(Player.Actions.SKIP_NEXT, { track: track }, opt_callback);
  };
  
  /**
   * Deprecated alias for skipToPrevTrack
   */
  Player.prototype.skipToPrev = Player.prototype.skipToPrevTrack;
  
  /**
   * Deprecated alias for skipToNextTrack
   */
  Player.prototype.skipToNext = Player.prototype.skipToNextTrack;
  
  /**
   * Set the shuffle flag
   *
   * @param {boolean} isShuffling The value of the flag to set.
   * @param {function=} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.setShufflingContext = function(isShuffling, opt_callback) {
    return this._sendRequestWithParam(Player.Actions.SHUFFLE, isShuffling, opt_callback);
  };
  
  /**
   * Repeat the whole context
   *
   * @param {boolean} isRepeating The value of the flag to set.
   * @param {function=} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.setRepeatingContext = function(isRepeating, opt_callback) {
    return this._sendRequestWithParam(Player.Actions.REPEAT_CONTEXT, isRepeating, opt_callback);
  };
  
  /**
   * Repeat the single track
   *
   * @param {boolean} isRepeating The value of the flag to set.
   * @param {function=} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.setRepeatingTrack = function(isRepeating, opt_callback) {
    return this._sendRequestWithParam(Player.Actions.REPEAT_TRACK, isRepeating, opt_callback);
  };
  
  /**
   * Set the seek position. It should be set in seconds.
   *
   * @param {boolean} positionInMs Position to seek to in miliseconds.
   * @param {function=} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.seekTo = function(positionInMs, opt_callback) {
    return this._sendRequestWithParam(Player.Actions.SEEK_TO, positionInMs, opt_callback);
  };
  
  /**
   * Gets the current player state as an opaque string that can later be used to restore the
   * current state.
   *
   * @param {function(Error, Cosmos.Response=)} callback The function
   *    that will be called when the snapshot is available.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.save = function(callback) {
    var requestUri = this.getPlayerEndpointUri() + '/snapshot';
    var request = new cosmos.Request(cosmos.Action.GET, requestUri);
    return this._resolver.resolve(request, callback);
  };
  
  /**
   * Restores a state string that was previously saved by save().
   *
   * @param {string} state The state to restore.
   * @param {function(Error, Cosmos.Response=)} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.restore = function(state, opt_callback) {
    var requestUri = this.getPlayerEndpointUri() + '/snapshot';
    var request = new cosmos.Request(cosmos.Action.PUT, requestUri, null, state);
    return this._resolver.resolve(request, opt_callback);
  };
  
  /**
   * Gets the current player queue as an string that can later be modified to replace
   * the current queue.
   *
   * @param {function(Error, Cosmos.Response=)} callback The function
   *    that will be called when the queue is available.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  
  Player.prototype.getQueue = function(callback) {
    var requestUri = this.getPlayerEndpointUri() + '/queue';
    var request = new cosmos.Request(cosmos.Action.GET, requestUri);
    return this._resolver.resolve(request, callback);
  };
  
  /**
   * Replace the player queue. The state will not replace the current one of the player if
   * the revision does not match the current revision of the player. This happens when
   * the player queue was changed elsewhere by the time it was received until it was set.
   * A response-status of 409 will inform about this situation.
   *
   * @param {string} state The queue-state to replace with.
   * @param {function(Error, Cosmos.Response=)} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.setQueue = function(state, opt_callback) {
    var requestUri = this.getPlayerEndpointUri() + '/queue';
    var request = new cosmos.Request(cosmos.Action.PUT, requestUri, null, state);
    return this._resolver.resolve(request, opt_callback);
  };
  
  /**
   * Subscribe to queue changes. There will be an initial callback when subscribing.
   */
  Player.prototype.subscribeToQueue = function(callback) {
    var request = new cosmos.Request(cosmos.Action.SUB, this.getPlayerEndpointUri() + '/queue');
    return this._resolver.resolve(request, callback);
  };
  
  /**
   * Get the current player state.
   *
   * @param {function(Error, Cosmos.Response=)} callback The function
   *    that will be called when the state is available.
   * @param {Object=} opt_params The additional parameters for the request.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype.getState = function(callback, opt_params) {
    var request = new cosmos.Request(
      cosmos.Action.GET, this.getPlayerEndpointUriWithParams(opt_params));
    return this._resolver.resolve(request, callback);
  };
  
  /**
   * Subscribe to the player events
   *
   * @param {function(Error, Cosmos.Response=)} callback The function
   *    that will be called every time the event occurs.
   * @param {Object=} opt_params The additional parameters for the request.
   * @return {RequestHandle} The subscription handle.
   *
   *    This object can be used to cancel the subscription
   *    by calling 'cancel()' on it:
   *
   *    var sub = player.subscribe(function(err, response) { });
   *    sub.cancel();
   */
  Player.prototype.subscribe = function(callback, opt_params) {
    var request = new cosmos.Request(
      cosmos.Action.SUB, this.getPlayerEndpointUriWithParams(opt_params));
    return this._resolver.resolve(request, callback);
  };
  
  /**
   * Subscribe to player error events
   */
  Player.prototype.onError = function(callback) {
    var request = new cosmos.Request(cosmos.Action.SUB, this.getPlayerEndpointUri() + '/error');
    return this._resolver.resolve(request, callback);
  };
  
  Player.prototype._makePlayOrigin = function() {
    return {
      view_uri: this._viewUri,
      feature_identifier: this._featureId,
      feature_version: this._featureVersion,
      referrer_identifier: this._referrerIdentifier
    };
  };
  
  Player.prototype.getPlayerEndpointUri = function() {
    return PLAYER_URI + this._id;
  };
  
  Player.prototype.getPlayerEndpointUriWithParams = function(params) {
    params = params || {};
  
    var paramsArray = Object.keys(params).reduce(function(arr, key) {
      arr.push(key + '=' + encodeURIComponent(params[key]));
      return arr;
    }, []);
  
    if (paramsArray.length > 0) {
      return this.getPlayerEndpointUri() + '?' + paramsArray.join('&');
    } else {
      return this.getPlayerEndpointUri();
    }
  };
  
  /**
   * @private
   * Sends context-less request.
   *
   * @param {string} action The action to perform on the player.
   * @param {function=} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype._sendRequestWithAction = function(action, opt_callback) {
    return this._sendRequestWithParams(action, null, opt_callback);
  };
  
  /**
   * @private
   * Sends the request that only expects the single value in the body.
   *
   * @param {string} action The action to perform on the player.
   * @param {*} paramValue The value to send to the player.
   * @param {function=} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype._sendRequestWithParam = function(action, paramValue, opt_callback) {
    var params = {
      value: paramValue
    };
    return this._sendRequestWithParams(action, params, opt_callback);
  };
  
  /**
   * @private
   * Sends request with context and optional options.
   *
   * @param {string} action The action to perform on the player.
   * @param {Object?} params The context to play.
   * @param {function=} opt_callback If specified will be executed
   *    after request is resolved.
   * @return {Cosmos.RequestHandler} The object representing current request.
   */
  Player.prototype._sendRequestWithParams = function(action, params, opt_callback) {
    var requestUri = this.getPlayerEndpointUri() + '/' + action;
    var body = params || {};
    body.logging_params = {
      command_initiated_time: this._getTime()
    };
    var request = new cosmos.Request(
      cosmos.Action.POST, requestUri, null, body);
    return this._resolver.resolve(request, opt_callback);
  };
  
  exports.Player = Player;
  
  },{"./play":555,"spotify-cosmos-api":564,"when/node":592}],558:[function(require,module,exports){
  /**
   * A PlayerTrack represents a single track in a context.
   *
   * @typedef {Object}
   * @property {String} uri
   *    The URI of the track. This will be one of the Spotify URIs, such as
   *    spotify:track:GID or spotify:local:DATA.
   * @property {String} album_uri
   *    The URI of the album that the track appears on. This property must always
   *    be set if the track URI is set. This is a requirement so that functions
   *    like MFT rules and biased shuffle can operate without having to fetch
   *    additional metadata for each track.
   * @property {String} artist_uri
   *    The URI of the main artist of the track. This property must always be set
   *    if the track URI is set. This is a requirement so that functions like MFT
   *    rules and biased shuffle can operate without having to fetch additional
   *    metadata for each track.
   * @property {Object} metadata
   *    A map with custom metadata for the track. This map can hold anything that
   *    the feature decides to put in it. Features should be prepared that the
   *    metadata can disappear and be able to work around that situation.
   *    The metadata is passed around and persisted, so it will usually stick
   *    around, but there are certain situations where the data is lost because of
   *    transferring the track over legacy protocols, to Connect enabled speakers
   *    and back.
   */
  exports.Track;
  
  /**
   * PlayerRestrictions is used to encapsulate limitations of a player or a
   * context. The caller can specify that restrictions should apply when playing a
   * given context (see PlayerContext.restrictions), and a player can notify that
   * restrictions (see PlayState.restrictions).
   *
   * @typedef {Object}
   * @property {Array.<String>} disallow_skipping_prev_reasons
   *    The reasons why skipping to the previous track should be / is disallowed.
   * @property {Array.<String>} disallow_skipping_next_reasons
   *    The reasons why skipping to the next track should be / is disallowed.
   * @property {Array.<String>} disallow_peeking_prev_reasons
   *    The reasons why peeking at the previous track should be / is disallowed.
   * @property {Array.<String>} disallow_peeking_next_reasons
   *    The reasons why peeking at the next track should be / is disallowed.
   * @property {Array.<String>} disallow_skipping_to_reasons
   *    The reasons why skipping to a specific track should be / is disallowed.
   * @property {Array.<String>} disallow_pausing_reasons
   *    The reasons why pausing playback should be / is disallowed.
   * @property {Array.<String>} disallow_resuming_reasons
   *    The reasons why resuming playback should be / is disallowed.
   * @property {Array.<String>} disallow_toggling_repeat_context_reasons
   *    The reasons why toggling repeat context should be / is disallowed.
   * @property {Array.<String>} disallow_toggling_repeat_track_reasons
   *    The reasons why toggling repeat track should be / is disallowed.
   * @property {Array.<String>} disallow_toggling_shuffle_reasons
   *    The reasons why toggling shuffle should be / is disallowed.
   * @property {Array.<String>} disallow_seeking_reasons
   *    The reasons why seeking within a track should be / is disallowed.
   * @property {Array.<String>} disallow_muting_reasons
   *    The reasons why muting audio should be / is disallowed.
   */
  exports.Restrictions;
  
  /**
   * PlayerSuppressions are used to disable certain player rules while playing a
   * context. The caller can specify that suppressions should apply when playing a
   * context (see PlayerOptions.suppressions), and a player can notify those
   * suppressions (see PlayState.suppressions).
   *
   * @typedef {Object}
   * @property {Array.<String>} providers
   *    The provider identifiers of the rules that should be disabled.
   */
  exports.Suppressions;
  
  /**
   * An object representing the index of the track to play within a context.
   * In the simplest case there is only one page thus its index should be 0.
   *
   * @typedef {Object}
   * @property {Number} page The index of the page to find the track in.
   * @property {Number} track The index of the track in the page.
   */
  exports.IndexPath;
  
  /**
   * This structure holds options that should be applied to the player: they are
   * either specified at a global or per-context level. When they are specified
   * globally, finishing the current context or starting to play a new context
   * does not change the player options: specifying them at a context-level means
   * that the previous global setting will be restored when the player is asked to
   * play another context.
   *
   * @typedef {Object}
   * @property {Boolean} shuffling_context
   *    Specifies if the player should shuffle the context or not.
   * @property {Boolean} repeating_context
   *    Specifies if the player should repeat the context or not.
   * @property {Boolean} repeating_track
   *    Specifies if the player should repeat the current track or not.
   */
  exports.PlayerOptions;
  
  /**
   * Options for a specific playback request. This object can specify for instance
   * which track to play within the given context, where to seek within the track
   * etc.
   *
   * @typedef {Object}
   * @property {module:spotify-player/types.IndexPath} skip_to_index
   *    The index in the context to start playing from. It is possible to specify
   *    a page that is not yet loaded, in which case the page will be downloaded
   *    before playback starts. The context player state will indicate that the
   *    playback is delayed because of loading the page.
   * @property {Number} seek_to
   *    The number of milliseconds to seek into the track when starting to
   *    play it.
   * @property {Boolean} initially_paused
   *    When this property is set, the context player will load the context but
   *    will not start playing audio for the first track. The is_paused property
   *    is set in the context player state to indicate that playback is paused. To
   *    start playing the track, call the `resume` method on the context player.
   * @property {module:spotify-player/types.PlayerOptions} player_options_override
   *    Use this to temporarily override the player options for this play request
   *    only. This can for instance be used to implement shuffle play buttons that
   *    enable shuffling but only for this particular context.
   */
  exports.PlayOptions;
  
  /**
   * Allows the list of tracks that will be played to be split up into chunks.
   * Pages may loaded or unloaded: an unloaded page will be loaded by the player
   * when it needs more tracks to play.
   *
   * @typedef {Object}
   * @property {String} page_url
   *     The URL of this page. If this property is set to a valid URL, it will be
   *     used to fetch the tracks of the page. The URL must be something that is
   *     supported by the context page loader in use, e.g., Hermes endpoints or
   *     Spotify playlist URIs. In the latter case, the context page loader will
   *     trigger the loaded signal each time the underlying playlist changes. If
   *     the tracks for the (static) context page are already provided in the
   *     tracks property, the page is considered loaded and the URL can be left
   *     empty.
   * @property {String} next_page_url
   *     The URL of the next context page. When the context page is stored in a
   *     context, the next page URL will be used to automatically append new pages
   *     at the end of the pages (or the fallback page) array. The new page
   *     will have its page URL set according to the next page URL of the last
   *     page in the context. The context page loaded will be used at some point
   *     to fetch the tracks for the new page. When the new page is fetched from
   *     the backend, it can also have the next page URL set, and yet another page
   *     is added to the context.
   * @property {Array<module:spotify-player/types.Track>} tracks
   *     An optional array with the tracks of this context page. When the context
   *     page is not loaded, the property is not initialized, and a context page
   *     loader will be used to fetch a loaded version of the page. In a loaded
   *     page this property will be set to an array of tracks. Note that the array
   *     can be empty, which means that the page does not have any tracks. This is
   *     not the same as a page that is not loaded, which may or may not have
   *     tracks.
   */
  exports.ContextPage;
  
  /**
   * Describes a set of tracks to be played.
   *
   * @typedef {Object}
   * @property {String} entity_uri
   *    An optional Spotify URI that represents what this entire context contains.
   *    For established entities like playlist, album, artist, this should be their
   *    respective URIs. This can be used for inter-feature playback status
   *    indication.
   *
   *    For instance, when the search page starts to play an artist, it should
   *    provide the artist URI in this field. The playback status indication for
   *    that artist in the search result list should be set if the current context
   *    entity URI matches the artist URI. On the artist page, the artist play
   *    button should also look at the entity URI for playback indication.
   * @property {Object} metadata
   *    A map with custom metadata for the track. This map can hold anything that
   *    the feature decides to put in it. Features should be prepared that the
   *    metadata can disappear and be able to work around that situation.
   *    The metadata is passed around and persisted, so it will usually stick
   *    around, but there are certain situations where the data is lost because of
   *    transferring the track over legacy protocols, to Connect enabled speakers
   *    and back.
   * @property {Array<module:spotify-player/types.ContextPage>} pages
   *    An array of context pages that contain the tracks that should be played
   *    for this context. The context pages may or may not be loaded when passing
   *    the context to the player for playback. Note that if the last context page
   *    has a next page URL, at some point a new context page will be added to the
   *    end of the array, for that page URL. This will happen when playback is
   *    nearing the end of the available context pages.
   * @property {module:spotify-player/types.Restricitions} restrictions
   *    The restrictons that should be applied to the player when playing tracks
   *    from the context, e.g., whether the user is allowed to skip backward and
   *    forward in the context or enable shuffling and repeating. Note that the
   *    actual restrictions of the player at any given moment is decided not only
   *    by the context restrictions but also by the rules that are applied, e.g.,
   *    the MFT rules will always disable skipping backward, and will disable
   *    skipping forward as well when the user has reached the maximum number of
   *    skips allowed per time period.
   */
  exports.Context;
  
  },{}],559:[function(require,module,exports){
  arguments[4][538][0].apply(exports,arguments)
  },{"./request":563,"dup":538}],560:[function(require,module,exports){
  arguments[4][539][0].apply(exports,arguments)
  },{"./bootstrap":559,"dup":539,"spotify-deferred":566}],561:[function(require,module,exports){
  arguments[4][540][0].apply(exports,arguments)
  },{"./bootstrap":559,"dup":540,"spotify-deferred":566}],562:[function(require,module,exports){
  arguments[4][541][0].apply(exports,arguments)
  },{"./bootstrap":559,"dup":541,"spotify-deferred":566}],563:[function(require,module,exports){
  arguments[4][542][0].apply(exports,arguments)
  },{"dup":542,"spotify-deferred":566}],564:[function(require,module,exports){
  (function (global){
  'use strict';
  
  var window = global.window || {};
  var process = global.process;
  
  var common = require('cosmos-common-js');
  var Resolver = require('./scripts/resolver').Resolver;
  
  var SPResolver = null;
  var spResolver = null;
  
  var hasNativeBridge = window._getSpotifyModule &&
      typeof window._getSpotifyModule === 'function' &&
      window._getSpotifyModule('bridge');
  
  var nodeRegex = /(node)|(grunt)|(iojs)(\.exe)*$/;
  var isNodeJs = process && process.title && nodeRegex.test(process.argv[0]);
  
  if (!isNodeJs) {
    if (hasNativeBridge) {
      SPResolver = require('./env/bootstrap.native.js').NativeResolver;
      spResolver = new SPResolver(hasNativeBridge);
    } else {
      SPResolver = require('./env/bootstrap.web.js').WebResolver;
      spResolver = new SPResolver();
    }
  } else {
    SPResolver = require('./env/bootstrap.mock.js').MockResolver;
    spResolver = new SPResolver();
  
    exports.mockResolver = {
      addHandler: spResolver.addHandler.bind(spResolver),
      removeHandler: spResolver.removeHandler.bind(spResolver),
      clearHandlers: spResolver.clearHandlers.bind(spResolver)
    };
  }
  
  exports.Resolver = Resolver;
  exports.Action = common.request.Action;
  exports.Request = common.request.Request;
  exports.Response = common.response.Response;
  exports.resolver = spResolver ? new Resolver(spResolver) : null;
  
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"./env/bootstrap.mock.js":560,"./env/bootstrap.native.js":561,"./env/bootstrap.web.js":562,"./scripts/resolver":565,"cosmos-common-js":325}],565:[function(require,module,exports){
  arguments[4][545][0].apply(exports,arguments)
  },{"cosmos-common-js":325,"dup":545}],566:[function(require,module,exports){
  arguments[4][544][0].apply(exports,arguments)
  },{"dup":544}],567:[function(require,module,exports){
  (function (global){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  
  var _ponyfill = require('./ponyfill.js');
  
  var _ponyfill2 = _interopRequireDefault(_ponyfill);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  
  var root; /* global window */
  
  
  if (typeof self !== 'undefined') {
    root = self;
  } else if (typeof window !== 'undefined') {
    root = window;
  } else if (typeof global !== 'undefined') {
    root = global;
  } else if (typeof module !== 'undefined') {
    root = module;
  } else {
    root = Function('return this')();
  }
  
  var result = (0, _ponyfill2['default'])(root);
  exports['default'] = result;
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  },{"./ponyfill.js":568}],568:[function(require,module,exports){
  'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports['default'] = symbolObservablePonyfill;
  function symbolObservablePonyfill(root) {
    var result;
    var _Symbol = root.Symbol;
  
    if (typeof _Symbol === 'function') {
      if (_Symbol.observable) {
        result = _Symbol.observable;
      } else {
        result = _Symbol('observable');
        _Symbol.observable = result;
      }
    } else {
      result = '@@observable';
    }
  
    return result;
  };
  },{}],570:[function(require,module,exports){
  if (typeof Object.create === 'function') {
    // implementation from standard node.js 'util' module
    module.exports = function inherits(ctor, superCtor) {
      ctor.super_ = superCtor
      ctor.prototype = Object.create(superCtor.prototype, {
        constructor: {
          value: ctor,
          enumerable: false,
          writable: true,
          configurable: true
        }
      });
    };
  } else {
    // old school shim for old browsers
    module.exports = function inherits(ctor, superCtor) {
      ctor.super_ = superCtor
      var TempCtor = function () {}
      TempCtor.prototype = superCtor.prototype
      ctor.prototype = new TempCtor()
      ctor.prototype.constructor = ctor
    }
  }
  
  },{}],571:[function(require,module,exports){
  module.exports = function isBuffer(arg) {
    return arg && typeof arg === 'object'
      && typeof arg.copy === 'function'
      && typeof arg.fill === 'function'
      && typeof arg.readUInt8 === 'function';
  }
  },{}],573:[function(require,module,exports){
  /*
   * UUID-js: A js library to generate and parse UUIDs, TimeUUIDs and generate
   * TimeUUID based on dates for range selections.
   * @see http://www.ietf.org/rfc/rfc4122.txt
   **/
  
  function UUIDjs() {
  };
  
  UUIDjs.maxFromBits = function(bits) {
    return Math.pow(2, bits);
  };
  
  UUIDjs.limitUI04 = UUIDjs.maxFromBits(4);
  UUIDjs.limitUI06 = UUIDjs.maxFromBits(6);
  UUIDjs.limitUI08 = UUIDjs.maxFromBits(8);
  UUIDjs.limitUI12 = UUIDjs.maxFromBits(12);
  UUIDjs.limitUI14 = UUIDjs.maxFromBits(14);
  UUIDjs.limitUI16 = UUIDjs.maxFromBits(16);
  UUIDjs.limitUI32 = UUIDjs.maxFromBits(32);
  UUIDjs.limitUI40 = UUIDjs.maxFromBits(40);
  UUIDjs.limitUI48 = UUIDjs.maxFromBits(48);
  
  // Returns a random integer between min and max
  // Using Math.round() will give you a non-uniform distribution!
  // @see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/random
  function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }
  
  UUIDjs.randomUI04 = function() {
    return getRandomInt(0, UUIDjs.limitUI04-1);
  };
  UUIDjs.randomUI06 = function() {
    return getRandomInt(0, UUIDjs.limitUI06-1);
  };
  UUIDjs.randomUI08 = function() {
    return getRandomInt(0, UUIDjs.limitUI08-1);
  };
  UUIDjs.randomUI12 = function() {
    return getRandomInt(0, UUIDjs.limitUI12-1);
  };
  UUIDjs.randomUI14 = function() {
    return getRandomInt(0, UUIDjs.limitUI14-1);
  };
  UUIDjs.randomUI16 = function() {
    return getRandomInt(0, UUIDjs.limitUI16-1);
  };
  UUIDjs.randomUI32 = function() {
    return getRandomInt(0, UUIDjs.limitUI32-1);
  };
  UUIDjs.randomUI40 = function() {
    return (0 | Math.random() * (1 << 30)) + (0 | Math.random() * (1 << 40 - 30)) * (1 << 30);
  };
  UUIDjs.randomUI48 = function() {
    return (0 | Math.random() * (1 << 30)) + (0 | Math.random() * (1 << 48 - 30)) * (1 << 30);
  };
  
  UUIDjs.paddedString = function(string, length, z) {
    string = String(string);
    z = (!z) ? '0' : z;
    var i = length - string.length;
    for (; i > 0; i >>>= 1, z += z) {
      if (i & 1) {
        string = z + string;
      }
    }
    return string;
  };
  
  UUIDjs.prototype.fromParts = function(timeLow, timeMid, timeHiAndVersion, clockSeqHiAndReserved, clockSeqLow, node) {
    this.version = (timeHiAndVersion >> 12) & 0xF;
    this.hex = UUIDjs.paddedString(timeLow.toString(16), 8)
               + '-'
               + UUIDjs.paddedString(timeMid.toString(16), 4)
               + '-'
               + UUIDjs.paddedString(timeHiAndVersion.toString(16), 4)
               + '-'
               + UUIDjs.paddedString(clockSeqHiAndReserved.toString(16), 2)
               + UUIDjs.paddedString(clockSeqLow.toString(16), 2)
               + '-'
               + UUIDjs.paddedString(node.toString(16), 12);
    return this;
  };
  
  UUIDjs.prototype.toString = function() {
    return this.hex;
  };
  UUIDjs.prototype.toURN = function() {
    return 'urn:uuid:' + this.hex;
  };
  
  UUIDjs.prototype.toBytes = function() {
    var parts = this.hex.split('-');
    var ints = [];
    var intPos = 0;
    for (var i = 0; i < parts.length; i++) {
      for (var j = 0; j < parts[i].length; j+=2) {
        ints[intPos++] = parseInt(parts[i].substr(j, 2), 16);
      }
    }
    return ints;
  };
  
  UUIDjs.prototype.equals = function(uuid) {
    if (!(uuid instanceof UUID)) {
      return false;
    }
    if (this.hex !== uuid.hex) {
      return false;
    }
    return true;
  };
  
  UUIDjs.getTimeFieldValues = function(time) {
    var ts = time - Date.UTC(1582, 9, 15);
    var hm = ((ts / 0x100000000) * 10000) & 0xFFFFFFF;
    return { low: ((ts & 0xFFFFFFF) * 10000) % 0x100000000,
              mid: hm & 0xFFFF, hi: hm >>> 16, timestamp: ts };
  };
  
  UUIDjs._create4 = function() {
    return new UUIDjs().fromParts(
      UUIDjs.randomUI32(),
      UUIDjs.randomUI16(),
      0x4000 | UUIDjs.randomUI12(),
      0x80   | UUIDjs.randomUI06(),
      UUIDjs.randomUI08(),
      UUIDjs.randomUI48()
    );
  };
  
  UUIDjs._create1 = function() {
    var now = new Date().getTime();
    var sequence = UUIDjs.randomUI14();
    var node = (UUIDjs.randomUI08() | 1) * 0x10000000000 + UUIDjs.randomUI40();
    var tick = UUIDjs.randomUI04();
    var timestamp = 0;
    var timestampRatio = 1/4;
  
    if (now != timestamp) {
      if (now < timestamp) {
        sequence++;
      }
      timestamp = now;
      tick = UUIDjs.randomUI04();
    } else if (Math.random() < timestampRatio && tick < 9984) {
      tick += 1 + UUIDjs.randomUI04();
    } else {
      sequence++;
    }
  
    var tf = UUIDjs.getTimeFieldValues(timestamp);
    var tl = tf.low + tick;
    var thav = (tf.hi & 0xFFF) | 0x1000;
  
    sequence &= 0x3FFF;
    var cshar = (sequence >>> 8) | 0x80;
    var csl = sequence & 0xFF;
  
    return new UUIDjs().fromParts(tl, tf.mid, thav, cshar, csl, node);
  };
  
  UUIDjs.create = function(version) {
    version = version || 4;
    return this['_create' + version]();
  };
  
  UUIDjs.fromTime = function(time, last) {
    last = (!last) ? false : last;
    var tf = UUIDjs.getTimeFieldValues(time);
    var tl = tf.low;
    var thav = (tf.hi & 0xFFF) | 0x1000;  // set version '0001'
    if (last === false) {
      return new UUIDjs().fromParts(tl, tf.mid, thav, 0, 0, 0);
    } else {
      return new UUIDjs().fromParts(tl, tf.mid, thav, 0x80 | UUIDjs.limitUI06, UUIDjs.limitUI08 - 1, UUIDjs.limitUI48 - 1);
    }
  };
  
  UUIDjs.firstFromTime = function(time) {
    return UUIDjs.fromTime(time, false);
  };
  UUIDjs.lastFromTime = function(time) {
    return UUIDjs.fromTime(time, true);
  };
  
  UUIDjs.fromURN = function(strId) {
    var r, p = /^(?:urn:uuid:|\{)?([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{2})([0-9a-f]{2})-([0-9a-f]{12})(?:\})?$/i;
    if ((r = p.exec(strId))) {
      return new UUIDjs().fromParts(parseInt(r[1], 16), parseInt(r[2], 16),
                              parseInt(r[3], 16), parseInt(r[4], 16),
                              parseInt(r[5], 16), parseInt(r[6], 16));
    }
    return null;
  };
  
  UUIDjs.fromBytes = function(ints) {
    if (ints.length < 5) {
      return null;
    }
    var str = '';
    var pos = 0;
    var parts = [4, 2, 2, 2, 6];
    for (var i = 0; i < parts.length; i++) {
      for (var j = 0; j < parts[i]; j++) {
        var octet = ints[pos++].toString(16);
        if (octet.length == 1) {
          octet = '0' + octet;
        }
        str += octet;
      }
      if (parts[i] !== 6) {
        str += '-';
      }
    }
    return UUIDjs.fromURN(str);
  };
  
  UUIDjs.fromBinary = function(binary) {
    var ints = [];
    for (var i = 0; i < binary.length; i++) {
      ints[i] = binary.charCodeAt(i);
      if (ints[i] > 255 || ints[i] < 0) {
        throw new Error('Unexpected byte in binary data.');
      }
    }
    return UUIDjs.fromBytes(ints);
  };
  
  // Aliases to support legacy code. Do not use these when writing new code as
  // they may be removed in future versions!
  UUIDjs.new = function() {
    return this.create(4);
  };
  UUIDjs.newTS = function() {
    return this.create(1);
  };
  
  module.exports = UUIDjs;
  
  },{}],574:[function(require,module,exports){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  
  (function(define) { 'use strict';
  define(function (require) {
  
    var makePromise = require('./makePromise');
    var Scheduler = require('./Scheduler');
    var async = require('./env').asap;
  
    return makePromise({
      scheduler: new Scheduler(async)
    });
  
  });
  })(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });
  
  },{"./Scheduler":575,"./env":587,"./makePromise":590}],575:[function(require,module,exports){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  
  (function(define) { 'use strict';
  define(function() {
  
    // Credit to Twisol (https://github.com/Twisol) for suggesting
    // this type of extensible queue + trampoline approach for next-tick conflation.
  
    /**
     * Async task scheduler
     * @param {function} async function to schedule a single async function
     * @constructor
     */
    function Scheduler(async) {
      this._async = async;
      this._running = false;
  
      this._queue = this;
      this._queueLen = 0;
      this._afterQueue = {};
      this._afterQueueLen = 0;
  
      var self = this;
      this.drain = function() {
        self._drain();
      };
    }
  
    /**
     * Enqueue a task
     * @param {{ run:function }} task
     */
    Scheduler.prototype.enqueue = function(task) {
      this._queue[this._queueLen++] = task;
      this.run();
    };
  
    /**
     * Enqueue a task to run after the main task queue
     * @param {{ run:function }} task
     */
    Scheduler.prototype.afterQueue = function(task) {
      this._afterQueue[this._afterQueueLen++] = task;
      this.run();
    };
  
    Scheduler.prototype.run = function() {
      if (!this._running) {
        this._running = true;
        this._async(this.drain);
      }
    };
  
    /**
     * Drain the handler queue entirely, and then the after queue
     */
    Scheduler.prototype._drain = function() {
      var i = 0;
      for (; i < this._queueLen; ++i) {
        this._queue[i].run();
        this._queue[i] = void 0;
      }
  
      this._queueLen = 0;
      this._running = false;
  
      for (i = 0; i < this._afterQueueLen; ++i) {
        this._afterQueue[i].run();
        this._afterQueue[i] = void 0;
      }
  
      this._afterQueueLen = 0;
    };
  
    return Scheduler;
  
  });
  }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));
  
  },{}],576:[function(require,module,exports){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  
  (function(define) { 'use strict';
  define(function() {
  
    /**
     * Custom error type for promises rejected by promise.timeout
     * @param {string} message
     * @constructor
     */
    function TimeoutError (message) {
      Error.call(this);
      this.message = message;
      this.name = TimeoutError.name;
      if (typeof Error.captureStackTrace === 'function') {
        Error.captureStackTrace(this, TimeoutError);
      }
    }
  
    TimeoutError.prototype = Object.create(Error.prototype);
    TimeoutError.prototype.constructor = TimeoutError;
  
    return TimeoutError;
  });
  }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));
  },{}],577:[function(require,module,exports){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  
  (function(define) { 'use strict';
  define(function() {
  
    makeApply.tryCatchResolve = tryCatchResolve;
  
    return makeApply;
  
    function makeApply(Promise, call) {
      if(arguments.length < 2) {
        call = tryCatchResolve;
      }
  
      return apply;
  
      function apply(f, thisArg, args) {
        var p = Promise._defer();
        var l = args.length;
        var params = new Array(l);
        callAndResolve({ f:f, thisArg:thisArg, args:args, params:params, i:l-1, call:call }, p._handler);
  
        return p;
      }
  
      function callAndResolve(c, h) {
        if(c.i < 0) {
          return call(c.f, c.thisArg, c.params, h);
        }
  
        var handler = Promise._handler(c.args[c.i]);
        handler.fold(callAndResolveNext, c, void 0, h);
      }
  
      function callAndResolveNext(c, x, h) {
        c.params[c.i] = x;
        c.i -= 1;
        callAndResolve(c, h);
      }
    }
  
    function tryCatchResolve(f, thisArg, args, resolver) {
      try {
        resolver.resolve(f.apply(thisArg, args));
      } catch(e) {
        resolver.reject(e);
      }
    }
  
  });
  }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));
  
  
  
  },{}],578:[function(require,module,exports){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  
  (function(define) { 'use strict';
  define(function(require) {
  
    var state = require('../state');
    var applier = require('../apply');
  
    return function array(Promise) {
  
      var applyFold = applier(Promise);
      var toPromise = Promise.resolve;
      var all = Promise.all;
  
      var ar = Array.prototype.reduce;
      var arr = Array.prototype.reduceRight;
      var slice = Array.prototype.slice;
  
      // Additional array combinators
  
      Promise.any = any;
      Promise.some = some;
      Promise.settle = settle;
  
      Promise.map = map;
      Promise.filter = filter;
      Promise.reduce = reduce;
      Promise.reduceRight = reduceRight;
  
      /**
       * When this promise fulfills with an array, do
       * onFulfilled.apply(void 0, array)
       * @param {function} onFulfilled function to apply
       * @returns {Promise} promise for the result of applying onFulfilled
       */
      Promise.prototype.spread = function(onFulfilled) {
        return this.then(all).then(function(array) {
          return onFulfilled.apply(this, array);
        });
      };
  
      return Promise;
  
      /**
       * One-winner competitive race.
       * Return a promise that will fulfill when one of the promises
       * in the input array fulfills, or will reject when all promises
       * have rejected.
       * @param {array} promises
       * @returns {Promise} promise for the first fulfilled value
       */
      function any(promises) {
        var p = Promise._defer();
        var resolver = p._handler;
        var l = promises.length>>>0;
  
        var pending = l;
        var errors = [];
  
        for (var h, x, i = 0; i < l; ++i) {
          x = promises[i];
          if(x === void 0 && !(i in promises)) {
            --pending;
            continue;
          }
  
          h = Promise._handler(x);
          if(h.state() > 0) {
            resolver.become(h);
            Promise._visitRemaining(promises, i, h);
            break;
          } else {
            h.visit(resolver, handleFulfill, handleReject);
          }
        }
  
        if(pending === 0) {
          resolver.reject(new RangeError('any(): array must not be empty'));
        }
  
        return p;
  
        function handleFulfill(x) {
          /*jshint validthis:true*/
          errors = null;
          this.resolve(x); // this === resolver
        }
  
        function handleReject(e) {
          /*jshint validthis:true*/
          if(this.resolved) { // this === resolver
            return;
          }
  
          errors.push(e);
          if(--pending === 0) {
            this.reject(errors);
          }
        }
      }
  
      /**
       * N-winner competitive race
       * Return a promise that will fulfill when n input promises have
       * fulfilled, or will reject when it becomes impossible for n
       * input promises to fulfill (ie when promises.length - n + 1
       * have rejected)
       * @param {array} promises
       * @param {number} n
       * @returns {Promise} promise for the earliest n fulfillment values
       *
       * @deprecated
       */
      function some(promises, n) {
        /*jshint maxcomplexity:7*/
        var p = Promise._defer();
        var resolver = p._handler;
  
        var results = [];
        var errors = [];
  
        var l = promises.length>>>0;
        var nFulfill = 0;
        var nReject;
        var x, i; // reused in both for() loops
  
        // First pass: count actual array items
        for(i=0; i<l; ++i) {
          x = promises[i];
          if(x === void 0 && !(i in promises)) {
            continue;
          }
          ++nFulfill;
        }
  
        // Compute actual goals
        n = Math.max(n, 0);
        nReject = (nFulfill - n + 1);
        nFulfill = Math.min(n, nFulfill);
  
        if(n > nFulfill) {
          resolver.reject(new RangeError('some(): array must contain at least '
          + n + ' item(s), but had ' + nFulfill));
        } else if(nFulfill === 0) {
          resolver.resolve(results);
        }
  
        // Second pass: observe each array item, make progress toward goals
        for(i=0; i<l; ++i) {
          x = promises[i];
          if(x === void 0 && !(i in promises)) {
            continue;
          }
  
          Promise._handler(x).visit(resolver, fulfill, reject, resolver.notify);
        }
  
        return p;
  
        function fulfill(x) {
          /*jshint validthis:true*/
          if(this.resolved) { // this === resolver
            return;
          }
  
          results.push(x);
          if(--nFulfill === 0) {
            errors = null;
            this.resolve(results);
          }
        }
  
        function reject(e) {
          /*jshint validthis:true*/
          if(this.resolved) { // this === resolver
            return;
          }
  
          errors.push(e);
          if(--nReject === 0) {
            results = null;
            this.reject(errors);
          }
        }
      }
  
      /**
       * Apply f to the value of each promise in a list of promises
       * and return a new list containing the results.
       * @param {array} promises
       * @param {function(x:*, index:Number):*} f mapping function
       * @returns {Promise}
       */
      function map(promises, f) {
        return Promise._traverse(f, promises);
      }
  
      /**
       * Filter the provided array of promises using the provided predicate.  Input may
       * contain promises and values
       * @param {Array} promises array of promises and values
       * @param {function(x:*, index:Number):boolean} predicate filtering predicate.
       *  Must return truthy (or promise for truthy) for items to retain.
       * @returns {Promise} promise that will fulfill with an array containing all items
       *  for which predicate returned truthy.
       */
      function filter(promises, predicate) {
        var a = slice.call(promises);
        return Promise._traverse(predicate, a).then(function(keep) {
          return filterSync(a, keep);
        });
      }
  
      function filterSync(promises, keep) {
        // Safe because we know all promises have fulfilled if we've made it this far
        var l = keep.length;
        var filtered = new Array(l);
        for(var i=0, j=0; i<l; ++i) {
          if(keep[i]) {
            filtered[j++] = Promise._handler(promises[i]).value;
          }
        }
        filtered.length = j;
        return filtered;
  
      }
  
      /**
       * Return a promise that will always fulfill with an array containing
       * the outcome states of all input promises.  The returned promise
       * will never reject.
       * @param {Array} promises
       * @returns {Promise} promise for array of settled state descriptors
       */
      function settle(promises) {
        return all(promises.map(settleOne));
      }
  
      function settleOne(p) {
        var h = Promise._handler(p);
        if(h.state() === 0) {
          return toPromise(p).then(state.fulfilled, state.rejected);
        }
  
        h._unreport();
        return state.inspect(h);
      }
  
      /**
       * Traditional reduce function, similar to `Array.prototype.reduce()`, but
       * input may contain promises and/or values, and reduceFunc
       * may return either a value or a promise, *and* initialValue may
       * be a promise for the starting value.
       * @param {Array|Promise} promises array or promise for an array of anything,
       *      may contain a mix of promises and values.
       * @param {function(accumulated:*, x:*, index:Number):*} f reduce function
       * @returns {Promise} that will resolve to the final reduced value
       */
      function reduce(promises, f /*, initialValue */) {
        return arguments.length > 2 ? ar.call(promises, liftCombine(f), arguments[2])
            : ar.call(promises, liftCombine(f));
      }
  
      /**
       * Traditional reduce function, similar to `Array.prototype.reduceRight()`, but
       * input may contain promises and/or values, and reduceFunc
       * may return either a value or a promise, *and* initialValue may
       * be a promise for the starting value.
       * @param {Array|Promise} promises array or promise for an array of anything,
       *      may contain a mix of promises and values.
       * @param {function(accumulated:*, x:*, index:Number):*} f reduce function
       * @returns {Promise} that will resolve to the final reduced value
       */
      function reduceRight(promises, f /*, initialValue */) {
        return arguments.length > 2 ? arr.call(promises, liftCombine(f), arguments[2])
            : arr.call(promises, liftCombine(f));
      }
  
      function liftCombine(f) {
        return function(z, x, i) {
          return applyFold(f, void 0, [z,x,i]);
        };
      }
    };
  
  });
  }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));
  
  },{"../apply":577,"../state":591}],579:[function(require,module,exports){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  
  (function(define) { 'use strict';
  define(function() {
  
    return function flow(Promise) {
  
      var resolve = Promise.resolve;
      var reject = Promise.reject;
      var origCatch = Promise.prototype['catch'];
  
      /**
       * Handle the ultimate fulfillment value or rejection reason, and assume
       * responsibility for all errors.  If an error propagates out of result
       * or handleFatalError, it will be rethrown to the host, resulting in a
       * loud stack track on most platforms and a crash on some.
       * @param {function?} onResult
       * @param {function?} onError
       * @returns {undefined}
       */
      Promise.prototype.done = function(onResult, onError) {
        this._handler.visit(this._handler.receiver, onResult, onError);
      };
  
      /**
       * Add Error-type and predicate matching to catch.  Examples:
       * promise.catch(TypeError, handleTypeError)
       *   .catch(predicate, handleMatchedErrors)
       *   .catch(handleRemainingErrors)
       * @param onRejected
       * @returns {*}
       */
      Promise.prototype['catch'] = Promise.prototype.otherwise = function(onRejected) {
        if (arguments.length < 2) {
          return origCatch.call(this, onRejected);
        }
  
        if(typeof onRejected !== 'function') {
          return this.ensure(rejectInvalidPredicate);
        }
  
        return origCatch.call(this, createCatchFilter(arguments[1], onRejected));
      };
  
      /**
       * Wraps the provided catch handler, so that it will only be called
       * if the predicate evaluates truthy
       * @param {?function} handler
       * @param {function} predicate
       * @returns {function} conditional catch handler
       */
      function createCatchFilter(handler, predicate) {
        return function(e) {
          return evaluatePredicate(e, predicate)
            ? handler.call(this, e)
            : reject(e);
        };
      }
  
      /**
       * Ensures that onFulfilledOrRejected will be called regardless of whether
       * this promise is fulfilled or rejected.  onFulfilledOrRejected WILL NOT
       * receive the promises' value or reason.  Any returned value will be disregarded.
       * onFulfilledOrRejected may throw or return a rejected promise to signal
       * an additional error.
       * @param {function} handler handler to be called regardless of
       *  fulfillment or rejection
       * @returns {Promise}
       */
      Promise.prototype['finally'] = Promise.prototype.ensure = function(handler) {
        if(typeof handler !== 'function') {
          return this;
        }
  
        return this.then(function(x) {
          return runSideEffect(handler, this, identity, x);
        }, function(e) {
          return runSideEffect(handler, this, reject, e);
        });
      };
  
      function runSideEffect (handler, thisArg, propagate, value) {
        var result = handler.call(thisArg);
        return maybeThenable(result)
          ? propagateValue(result, propagate, value)
          : propagate(value);
      }
  
      function propagateValue (result, propagate, x) {
        return resolve(result).then(function () {
          return propagate(x);
        });
      }
  
      /**
       * Recover from a failure by returning a defaultValue.  If defaultValue
       * is a promise, it's fulfillment value will be used.  If defaultValue is
       * a promise that rejects, the returned promise will reject with the
       * same reason.
       * @param {*} defaultValue
       * @returns {Promise} new promise
       */
      Promise.prototype['else'] = Promise.prototype.orElse = function(defaultValue) {
        return this.then(void 0, function() {
          return defaultValue;
        });
      };
  
      /**
       * Shortcut for .then(function() { return value; })
       * @param  {*} value
       * @return {Promise} a promise that:
       *  - is fulfilled if value is not a promise, or
       *  - if value is a promise, will fulfill with its value, or reject
       *    with its reason.
       */
      Promise.prototype['yield'] = function(value) {
        return this.then(function() {
          return value;
        });
      };
  
      /**
       * Runs a side effect when this promise fulfills, without changing the
       * fulfillment value.
       * @param {function} onFulfilledSideEffect
       * @returns {Promise}
       */
      Promise.prototype.tap = function(onFulfilledSideEffect) {
        return this.then(onFulfilledSideEffect)['yield'](this);
      };
  
      return Promise;
    };
  
    function rejectInvalidPredicate() {
      throw new TypeError('catch predicate must be a function');
    }
  
    function evaluatePredicate(e, predicate) {
      return isError(predicate) ? e instanceof predicate : predicate(e);
    }
  
    function isError(predicate) {
      return predicate === Error
        || (predicate != null && predicate.prototype instanceof Error);
    }
  
    function maybeThenable(x) {
      return (typeof x === 'object' || typeof x === 'function') && x !== null;
    }
  
    function identity(x) {
      return x;
    }
  
  });
  }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));
  
  },{}],580:[function(require,module,exports){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  /** @author Jeff Escalante */
  
  (function(define) { 'use strict';
  define(function() {
  
    return function fold(Promise) {
  
      Promise.prototype.fold = function(f, z) {
        var promise = this._beget();
  
        this._handler.fold(function(z, x, to) {
          Promise._handler(z).fold(function(x, z, to) {
            to.resolve(f.call(this, z, x));
          }, x, this, to);
        }, z, promise._handler.receiver, promise._handler);
  
        return promise;
      };
  
      return Promise;
    };
  
  });
  }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));
  
  },{}],581:[function(require,module,exports){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  
  (function(define) { 'use strict';
  define(function(require) {
  
    var inspect = require('../state').inspect;
  
    return function inspection(Promise) {
  
      Promise.prototype.inspect = function() {
        return inspect(Promise._handler(this));
      };
  
      return Promise;
    };
  
  });
  }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));
  
  },{"../state":591}],582:[function(require,module,exports){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  
  (function(define) { 'use strict';
  define(function() {
  
    return function generate(Promise) {
  
      var resolve = Promise.resolve;
  
      Promise.iterate = iterate;
      Promise.unfold = unfold;
  
      return Promise;
  
      /**
       * @deprecated Use github.com/cujojs/most streams and most.iterate
       * Generate a (potentially infinite) stream of promised values:
       * x, f(x), f(f(x)), etc. until condition(x) returns true
       * @param {function} f function to generate a new x from the previous x
       * @param {function} condition function that, given the current x, returns
       *  truthy when the iterate should stop
       * @param {function} handler function to handle the value produced by f
       * @param {*|Promise} x starting value, may be a promise
       * @return {Promise} the result of the last call to f before
       *  condition returns true
       */
      function iterate(f, condition, handler, x) {
        return unfold(function(x) {
          return [x, f(x)];
        }, condition, handler, x);
      }
  
      /**
       * @deprecated Use github.com/cujojs/most streams and most.unfold
       * Generate a (potentially infinite) stream of promised values
       * by applying handler(generator(seed)) iteratively until
       * condition(seed) returns true.
       * @param {function} unspool function that generates a [value, newSeed]
       *  given a seed.
       * @param {function} condition function that, given the current seed, returns
       *  truthy when the unfold should stop
       * @param {function} handler function to handle the value produced by unspool
       * @param x {*|Promise} starting value, may be a promise
       * @return {Promise} the result of the last value produced by unspool before
       *  condition returns true
       */
      function unfold(unspool, condition, handler, x) {
        return resolve(x).then(function(seed) {
          return resolve(condition(seed)).then(function(done) {
            return done ? seed : resolve(unspool(seed)).spread(next);
          });
        });
  
        function next(item, newSeed) {
          return resolve(handler(item)).then(function() {
            return unfold(unspool, condition, handler, newSeed);
          });
        }
      }
    };
  
  });
  }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));
  
  },{}],583:[function(require,module,exports){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  
  (function(define) { 'use strict';
  define(function() {
  
    return function progress(Promise) {
  
      /**
       * @deprecated
       * Register a progress handler for this promise
       * @param {function} onProgress
       * @returns {Promise}
       */
      Promise.prototype.progress = function(onProgress) {
        return this.then(void 0, void 0, onProgress);
      };
  
      return Promise;
    };
  
  });
  }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));
  
  },{}],584:[function(require,module,exports){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  
  (function(define) { 'use strict';
  define(function(require) {
  
    var env = require('../env');
    var TimeoutError = require('../TimeoutError');
  
    function setTimeout(f, ms, x, y) {
      return env.setTimer(function() {
        f(x, y, ms);
      }, ms);
    }
  
    return function timed(Promise) {
      /**
       * Return a new promise whose fulfillment value is revealed only
       * after ms milliseconds
       * @param {number} ms milliseconds
       * @returns {Promise}
       */
      Promise.prototype.delay = function(ms) {
        var p = this._beget();
        this._handler.fold(handleDelay, ms, void 0, p._handler);
        return p;
      };
  
      function handleDelay(ms, x, h) {
        setTimeout(resolveDelay, ms, x, h);
      }
  
      function resolveDelay(x, h) {
        h.resolve(x);
      }
  
      /**
       * Return a new promise that rejects after ms milliseconds unless
       * this promise fulfills earlier, in which case the returned promise
       * fulfills with the same value.
       * @param {number} ms milliseconds
       * @param {Error|*=} reason optional rejection reason to use, defaults
       *   to a TimeoutError if not provided
       * @returns {Promise}
       */
      Promise.prototype.timeout = function(ms, reason) {
        var p = this._beget();
        var h = p._handler;
  
        var t = setTimeout(onTimeout, ms, reason, p._handler);
  
        this._handler.visit(h,
          function onFulfill(x) {
            env.clearTimer(t);
            this.resolve(x); // this = h
          },
          function onReject(x) {
            env.clearTimer(t);
            this.reject(x); // this = h
          },
          h.notify);
  
        return p;
      };
  
      function onTimeout(reason, h, ms) {
        var e = typeof reason === 'undefined'
          ? new TimeoutError('timed out after ' + ms + 'ms')
          : reason;
        h.reject(e);
      }
  
      return Promise;
    };
  
  });
  }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));
  
  },{"../TimeoutError":576,"../env":587}],585:[function(require,module,exports){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  
  (function(define) { 'use strict';
  define(function(require) {
  
    var setTimer = require('../env').setTimer;
    var format = require('../format');
  
    return function unhandledRejection(Promise) {
  
      var logError = noop;
      var logInfo = noop;
      var localConsole;
  
      if(typeof console !== 'undefined') {
        // Alias console to prevent things like uglify's drop_console option from
        // removing console.log/error. Unhandled rejections fall into the same
        // category as uncaught exceptions, and build tools shouldn't silence them.
        localConsole = console;
        logError = typeof localConsole.error !== 'undefined'
          ? function (e) { localConsole.error(e); }
          : function (e) { localConsole.log(e); };
  
        logInfo = typeof localConsole.info !== 'undefined'
          ? function (e) { localConsole.info(e); }
          : function (e) { localConsole.log(e); };
      }
  
      Promise.onPotentiallyUnhandledRejection = function(rejection) {
        enqueue(report, rejection);
      };
  
      Promise.onPotentiallyUnhandledRejectionHandled = function(rejection) {
        enqueue(unreport, rejection);
      };
  
      Promise.onFatalRejection = function(rejection) {
        enqueue(throwit, rejection.value);
      };
  
      var tasks = [];
      var reported = [];
      var running = null;
  
      function report(r) {
        if(!r.handled) {
          reported.push(r);
          logError('Potentially unhandled rejection [' + r.id + '] ' + format.formatError(r.value));
        }
      }
  
      function unreport(r) {
        var i = reported.indexOf(r);
        if(i >= 0) {
          reported.splice(i, 1);
          logInfo('Handled previous rejection [' + r.id + '] ' + format.formatObject(r.value));
        }
      }
  
      function enqueue(f, x) {
        tasks.push(f, x);
        if(running === null) {
          running = setTimer(flush, 0);
        }
      }
  
      function flush() {
        running = null;
        while(tasks.length > 0) {
          tasks.shift()(tasks.shift());
        }
      }
  
      return Promise;
    };
  
    function throwit(e) {
      throw e;
    }
  
    function noop() {}
  
  });
  }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));
  
  },{"../env":587,"../format":588}],586:[function(require,module,exports){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  
  (function(define) { 'use strict';
  define(function() {
  
    return function addWith(Promise) {
      /**
       * Returns a promise whose handlers will be called with `this` set to
       * the supplied receiver.  Subsequent promises derived from the
       * returned promise will also have their handlers called with receiver
       * as `this`. Calling `with` with undefined or no arguments will return
       * a promise whose handlers will again be called in the usual Promises/A+
       * way (no `this`) thus safely undoing any previous `with` in the
       * promise chain.
       *
       * WARNING: Promises returned from `with`/`withThis` are NOT Promises/A+
       * compliant, specifically violating 2.2.5 (http://promisesaplus.com/#point-41)
       *
       * @param {object} receiver `this` value for all handlers attached to
       *  the returned promise.
       * @returns {Promise}
       */
      Promise.prototype['with'] = Promise.prototype.withThis = function(receiver) {
        var p = this._beget();
        var child = p._handler;
        child.receiver = receiver;
        this._handler.chain(child, receiver);
        return p;
      };
  
      return Promise;
    };
  
  });
  }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));
  
  
  },{}],587:[function(require,module,exports){
  (function (process){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  
  /*global process,document,setTimeout,clearTimeout,MutationObserver,WebKitMutationObserver*/
  (function(define) { 'use strict';
  define(function(require) {
    /*jshint maxcomplexity:6*/
  
    // Sniff "best" async scheduling option
    // Prefer process.nextTick or MutationObserver, then check for
    // setTimeout, and finally vertx, since its the only env that doesn't
    // have setTimeout
  
    var MutationObs;
    var capturedSetTimeout = typeof setTimeout !== 'undefined' && setTimeout;
  
    // Default env
    var setTimer = function(f, ms) { return setTimeout(f, ms); };
    var clearTimer = function(t) { return clearTimeout(t); };
    var asap = function (f) { return capturedSetTimeout(f, 0); };
  
    // Detect specific env
    if (isNode()) { // Node
      asap = function (f) { return process.nextTick(f); };
  
    } else if (MutationObs = hasMutationObserver()) { // Modern browser
      asap = initMutationObserver(MutationObs);
  
    } else if (!capturedSetTimeout) { // vert.x
      var vertxRequire = require;
      var vertx = vertxRequire('vertx');
      setTimer = function (f, ms) { return vertx.setTimer(ms, f); };
      clearTimer = vertx.cancelTimer;
      asap = vertx.runOnLoop || vertx.runOnContext;
    }
  
    return {
      setTimer: setTimer,
      clearTimer: clearTimer,
      asap: asap
    };
  
    function isNode () {
      return typeof process !== 'undefined' &&
        Object.prototype.toString.call(process) === '[object process]';
    }
  
    function hasMutationObserver () {
      return (typeof MutationObserver === 'function' && MutationObserver) ||
        (typeof WebKitMutationObserver === 'function' && WebKitMutationObserver);
    }
  
    function initMutationObserver(MutationObserver) {
      var scheduled;
      var node = document.createTextNode('');
      var o = new MutationObserver(run);
      o.observe(node, { characterData: true });
  
      function run() {
        var f = scheduled;
        scheduled = void 0;
        f();
      }
  
      var i = 0;
      return function (f) {
        scheduled = f;
        node.data = (i ^= 1);
      };
    }
  });
  }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));
  
  }).call(this,require('_process'))
  },{"_process":481}],588:[function(require,module,exports){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  
  (function(define) { 'use strict';
  define(function() {
  
    return {
      formatError: formatError,
      formatObject: formatObject,
      tryStringify: tryStringify
    };
  
    /**
     * Format an error into a string.  If e is an Error and has a stack property,
     * it's returned.  Otherwise, e is formatted using formatObject, with a
     * warning added about e not being a proper Error.
     * @param {*} e
     * @returns {String} formatted string, suitable for output to developers
     */
    function formatError(e) {
      var s = typeof e === 'object' && e !== null && (e.stack || e.message) ? e.stack || e.message : formatObject(e);
      return e instanceof Error ? s : s + ' (WARNING: non-Error used)';
    }
  
    /**
     * Format an object, detecting "plain" objects and running them through
     * JSON.stringify if possible.
     * @param {Object} o
     * @returns {string}
     */
    function formatObject(o) {
      var s = String(o);
      if(s === '[object Object]' && typeof JSON !== 'undefined') {
        s = tryStringify(o, s);
      }
      return s;
    }
  
    /**
     * Try to return the result of JSON.stringify(x).  If that fails, return
     * defaultValue
     * @param {*} x
     * @param {*} defaultValue
     * @returns {String|*} JSON.stringify(x) or defaultValue
     */
    function tryStringify(x, defaultValue) {
      try {
        return JSON.stringify(x);
      } catch(e) {
        return defaultValue;
      }
    }
  
  });
  }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));
  
  },{}],589:[function(require,module,exports){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  
  (function(define) { 'use strict';
  define(function() {
  
    return function liftAll(liftOne, combine, dst, src) {
      if(typeof combine === 'undefined') {
        combine = defaultCombine;
      }
  
      return Object.keys(src).reduce(function(dst, key) {
        var f = src[key];
        return typeof f === 'function' ? combine(dst, liftOne(f), key) : dst;
      }, typeof dst === 'undefined' ? defaultDst(src) : dst);
    };
  
    function defaultCombine(o, f, k) {
      o[k] = f;
      return o;
    }
  
    function defaultDst(src) {
      return typeof src === 'function' ? src.bind() : Object.create(src);
    }
  });
  }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));
  
  },{}],590:[function(require,module,exports){
  (function (process){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  
  (function(define) { 'use strict';
  define(function() {
  
    return function makePromise(environment) {
  
      var tasks = environment.scheduler;
      var emitRejection = initEmitRejection();
  
      var objectCreate = Object.create ||
        function(proto) {
          function Child() {}
          Child.prototype = proto;
          return new Child();
        };
  
      /**
       * Create a promise whose fate is determined by resolver
       * @constructor
       * @returns {Promise} promise
       * @name Promise
       */
      function Promise(resolver, handler) {
        this._handler = resolver === Handler ? handler : init(resolver);
      }
  
      /**
       * Run the supplied resolver
       * @param resolver
       * @returns {Pending}
       */
      function init(resolver) {
        var handler = new Pending();
  
        try {
          resolver(promiseResolve, promiseReject, promiseNotify);
        } catch (e) {
          promiseReject(e);
        }
  
        return handler;
  
        /**
         * Transition from pre-resolution state to post-resolution state, notifying
         * all listeners of the ultimate fulfillment or rejection
         * @param {*} x resolution value
         */
        function promiseResolve (x) {
          handler.resolve(x);
        }
        /**
         * Reject this promise with reason, which will be used verbatim
         * @param {Error|*} reason rejection reason, strongly suggested
         *   to be an Error type
         */
        function promiseReject (reason) {
          handler.reject(reason);
        }
  
        /**
         * @deprecated
         * Issue a progress event, notifying all progress listeners
         * @param {*} x progress event payload to pass to all listeners
         */
        function promiseNotify (x) {
          handler.notify(x);
        }
      }
  
      // Creation
  
      Promise.resolve = resolve;
      Promise.reject = reject;
      Promise.never = never;
  
      Promise._defer = defer;
      Promise._handler = getHandler;
  
      /**
       * Returns a trusted promise. If x is already a trusted promise, it is
       * returned, otherwise returns a new trusted Promise which follows x.
       * @param  {*} x
       * @return {Promise} promise
       */
      function resolve(x) {
        return isPromise(x) ? x
          : new Promise(Handler, new Async(getHandler(x)));
      }
  
      /**
       * Return a reject promise with x as its reason (x is used verbatim)
       * @param {*} x
       * @returns {Promise} rejected promise
       */
      function reject(x) {
        return new Promise(Handler, new Async(new Rejected(x)));
      }
  
      /**
       * Return a promise that remains pending forever
       * @returns {Promise} forever-pending promise.
       */
      function never() {
        return foreverPendingPromise; // Should be frozen
      }
  
      /**
       * Creates an internal {promise, resolver} pair
       * @private
       * @returns {Promise}
       */
      function defer() {
        return new Promise(Handler, new Pending());
      }
  
      // Transformation and flow control
  
      /**
       * Transform this promise's fulfillment value, returning a new Promise
       * for the transformed result.  If the promise cannot be fulfilled, onRejected
       * is called with the reason.  onProgress *may* be called with updates toward
       * this promise's fulfillment.
       * @param {function=} onFulfilled fulfillment handler
       * @param {function=} onRejected rejection handler
       * @param {function=} onProgress @deprecated progress handler
       * @return {Promise} new promise
       */
      Promise.prototype.then = function(onFulfilled, onRejected, onProgress) {
        var parent = this._handler;
        var state = parent.join().state();
  
        if ((typeof onFulfilled !== 'function' && state > 0) ||
          (typeof onRejected !== 'function' && state < 0)) {
          // Short circuit: value will not change, simply share handler
          return new this.constructor(Handler, parent);
        }
  
        var p = this._beget();
        var child = p._handler;
  
        parent.chain(child, parent.receiver, onFulfilled, onRejected, onProgress);
  
        return p;
      };
  
      /**
       * If this promise cannot be fulfilled due to an error, call onRejected to
       * handle the error. Shortcut for .then(undefined, onRejected)
       * @param {function?} onRejected
       * @return {Promise}
       */
      Promise.prototype['catch'] = function(onRejected) {
        return this.then(void 0, onRejected);
      };
  
      /**
       * Creates a new, pending promise of the same type as this promise
       * @private
       * @returns {Promise}
       */
      Promise.prototype._beget = function() {
        return begetFrom(this._handler, this.constructor);
      };
  
      function begetFrom(parent, Promise) {
        var child = new Pending(parent.receiver, parent.join().context);
        return new Promise(Handler, child);
      }
  
      // Array combinators
  
      Promise.all = all;
      Promise.race = race;
      Promise._traverse = traverse;
  
      /**
       * Return a promise that will fulfill when all promises in the
       * input array have fulfilled, or will reject when one of the
       * promises rejects.
       * @param {array} promises array of promises
       * @returns {Promise} promise for array of fulfillment values
       */
      function all(promises) {
        return traverseWith(snd, null, promises);
      }
  
      /**
       * Array<Promise<X>> -> Promise<Array<f(X)>>
       * @private
       * @param {function} f function to apply to each promise's value
       * @param {Array} promises array of promises
       * @returns {Promise} promise for transformed values
       */
      function traverse(f, promises) {
        return traverseWith(tryCatch2, f, promises);
      }
  
      function traverseWith(tryMap, f, promises) {
        var handler = typeof f === 'function' ? mapAt : settleAt;
  
        var resolver = new Pending();
        var pending = promises.length >>> 0;
        var results = new Array(pending);
  
        for (var i = 0, x; i < promises.length && !resolver.resolved; ++i) {
          x = promises[i];
  
          if (x === void 0 && !(i in promises)) {
            --pending;
            continue;
          }
  
          traverseAt(promises, handler, i, x, resolver);
        }
  
        if(pending === 0) {
          resolver.become(new Fulfilled(results));
        }
  
        return new Promise(Handler, resolver);
  
        function mapAt(i, x, resolver) {
          if(!resolver.resolved) {
            traverseAt(promises, settleAt, i, tryMap(f, x, i), resolver);
          }
        }
  
        function settleAt(i, x, resolver) {
          results[i] = x;
          if(--pending === 0) {
            resolver.become(new Fulfilled(results));
          }
        }
      }
  
      function traverseAt(promises, handler, i, x, resolver) {
        if (maybeThenable(x)) {
          var h = getHandlerMaybeThenable(x);
          var s = h.state();
  
          if (s === 0) {
            h.fold(handler, i, void 0, resolver);
          } else if (s > 0) {
            handler(i, h.value, resolver);
          } else {
            resolver.become(h);
            visitRemaining(promises, i+1, h);
          }
        } else {
          handler(i, x, resolver);
        }
      }
  
      Promise._visitRemaining = visitRemaining;
      function visitRemaining(promises, start, handler) {
        for(var i=start; i<promises.length; ++i) {
          markAsHandled(getHandler(promises[i]), handler);
        }
      }
  
      function markAsHandled(h, handler) {
        if(h === handler) {
          return;
        }
  
        var s = h.state();
        if(s === 0) {
          h.visit(h, void 0, h._unreport);
        } else if(s < 0) {
          h._unreport();
        }
      }
  
      /**
       * Fulfill-reject competitive race. Return a promise that will settle
       * to the same state as the earliest input promise to settle.
       *
       * WARNING: The ES6 Promise spec requires that race()ing an empty array
       * must return a promise that is pending forever.  This implementation
       * returns a singleton forever-pending promise, the same singleton that is
       * returned by Promise.never(), thus can be checked with ===
       *
       * @param {array} promises array of promises to race
       * @returns {Promise} if input is non-empty, a promise that will settle
       * to the same outcome as the earliest input promise to settle. if empty
       * is empty, returns a promise that will never settle.
       */
      function race(promises) {
        if(typeof promises !== 'object' || promises === null) {
          return reject(new TypeError('non-iterable passed to race()'));
        }
  
        // Sigh, race([]) is untestable unless we return *something*
        // that is recognizable without calling .then() on it.
        return promises.length === 0 ? never()
           : promises.length === 1 ? resolve(promises[0])
           : runRace(promises);
      }
  
      function runRace(promises) {
        var resolver = new Pending();
        var i, x, h;
        for(i=0; i<promises.length; ++i) {
          x = promises[i];
          if (x === void 0 && !(i in promises)) {
            continue;
          }
  
          h = getHandler(x);
          if(h.state() !== 0) {
            resolver.become(h);
            visitRemaining(promises, i+1, h);
            break;
          } else {
            h.visit(resolver, resolver.resolve, resolver.reject);
          }
        }
        return new Promise(Handler, resolver);
      }
  
      // Promise internals
      // Below this, everything is @private
  
      /**
       * Get an appropriate handler for x, without checking for cycles
       * @param {*} x
       * @returns {object} handler
       */
      function getHandler(x) {
        if(isPromise(x)) {
          return x._handler.join();
        }
        return maybeThenable(x) ? getHandlerUntrusted(x) : new Fulfilled(x);
      }
  
      /**
       * Get a handler for thenable x.
       * NOTE: You must only call this if maybeThenable(x) == true
       * @param {object|function|Promise} x
       * @returns {object} handler
       */
      function getHandlerMaybeThenable(x) {
        return isPromise(x) ? x._handler.join() : getHandlerUntrusted(x);
      }
  
      /**
       * Get a handler for potentially untrusted thenable x
       * @param {*} x
       * @returns {object} handler
       */
      function getHandlerUntrusted(x) {
        try {
          var untrustedThen = x.then;
          return typeof untrustedThen === 'function'
            ? new Thenable(untrustedThen, x)
            : new Fulfilled(x);
        } catch(e) {
          return new Rejected(e);
        }
      }
  
      /**
       * Handler for a promise that is pending forever
       * @constructor
       */
      function Handler() {}
  
      Handler.prototype.when
        = Handler.prototype.become
        = Handler.prototype.notify // deprecated
        = Handler.prototype.fail
        = Handler.prototype._unreport
        = Handler.prototype._report
        = noop;
  
      Handler.prototype._state = 0;
  
      Handler.prototype.state = function() {
        return this._state;
      };
  
      /**
       * Recursively collapse handler chain to find the handler
       * nearest to the fully resolved value.
       * @returns {object} handler nearest the fully resolved value
       */
      Handler.prototype.join = function() {
        var h = this;
        while(h.handler !== void 0) {
          h = h.handler;
        }
        return h;
      };
  
      Handler.prototype.chain = function(to, receiver, fulfilled, rejected, progress) {
        this.when({
          resolver: to,
          receiver: receiver,
          fulfilled: fulfilled,
          rejected: rejected,
          progress: progress
        });
      };
  
      Handler.prototype.visit = function(receiver, fulfilled, rejected, progress) {
        this.chain(failIfRejected, receiver, fulfilled, rejected, progress);
      };
  
      Handler.prototype.fold = function(f, z, c, to) {
        this.when(new Fold(f, z, c, to));
      };
  
      /**
       * Handler that invokes fail() on any handler it becomes
       * @constructor
       */
      function FailIfRejected() {}
  
      inherit(Handler, FailIfRejected);
  
      FailIfRejected.prototype.become = function(h) {
        h.fail();
      };
  
      var failIfRejected = new FailIfRejected();
  
      /**
       * Handler that manages a queue of consumers waiting on a pending promise
       * @constructor
       */
      function Pending(receiver, inheritedContext) {
        Promise.createContext(this, inheritedContext);
  
        this.consumers = void 0;
        this.receiver = receiver;
        this.handler = void 0;
        this.resolved = false;
      }
  
      inherit(Handler, Pending);
  
      Pending.prototype._state = 0;
  
      Pending.prototype.resolve = function(x) {
        this.become(getHandler(x));
      };
  
      Pending.prototype.reject = function(x) {
        if(this.resolved) {
          return;
        }
  
        this.become(new Rejected(x));
      };
  
      Pending.prototype.join = function() {
        if (!this.resolved) {
          return this;
        }
  
        var h = this;
  
        while (h.handler !== void 0) {
          h = h.handler;
          if (h === this) {
            return this.handler = cycle();
          }
        }
  
        return h;
      };
  
      Pending.prototype.run = function() {
        var q = this.consumers;
        var handler = this.handler;
        this.handler = this.handler.join();
        this.consumers = void 0;
  
        for (var i = 0; i < q.length; ++i) {
          handler.when(q[i]);
        }
      };
  
      Pending.prototype.become = function(handler) {
        if(this.resolved) {
          return;
        }
  
        this.resolved = true;
        this.handler = handler;
        if(this.consumers !== void 0) {
          tasks.enqueue(this);
        }
  
        if(this.context !== void 0) {
          handler._report(this.context);
        }
      };
  
      Pending.prototype.when = function(continuation) {
        if(this.resolved) {
          tasks.enqueue(new ContinuationTask(continuation, this.handler));
        } else {
          if(this.consumers === void 0) {
            this.consumers = [continuation];
          } else {
            this.consumers.push(continuation);
          }
        }
      };
  
      /**
       * @deprecated
       */
      Pending.prototype.notify = function(x) {
        if(!this.resolved) {
          tasks.enqueue(new ProgressTask(x, this));
        }
      };
  
      Pending.prototype.fail = function(context) {
        var c = typeof context === 'undefined' ? this.context : context;
        this.resolved && this.handler.join().fail(c);
      };
  
      Pending.prototype._report = function(context) {
        this.resolved && this.handler.join()._report(context);
      };
  
      Pending.prototype._unreport = function() {
        this.resolved && this.handler.join()._unreport();
      };
  
      /**
       * Wrap another handler and force it into a future stack
       * @param {object} handler
       * @constructor
       */
      function Async(handler) {
        this.handler = handler;
      }
  
      inherit(Handler, Async);
  
      Async.prototype.when = function(continuation) {
        tasks.enqueue(new ContinuationTask(continuation, this));
      };
  
      Async.prototype._report = function(context) {
        this.join()._report(context);
      };
  
      Async.prototype._unreport = function() {
        this.join()._unreport();
      };
  
      /**
       * Handler that wraps an untrusted thenable and assimilates it in a future stack
       * @param {function} then
       * @param {{then: function}} thenable
       * @constructor
       */
      function Thenable(then, thenable) {
        Pending.call(this);
        tasks.enqueue(new AssimilateTask(then, thenable, this));
      }
  
      inherit(Pending, Thenable);
  
      /**
       * Handler for a fulfilled promise
       * @param {*} x fulfillment value
       * @constructor
       */
      function Fulfilled(x) {
        Promise.createContext(this);
        this.value = x;
      }
  
      inherit(Handler, Fulfilled);
  
      Fulfilled.prototype._state = 1;
  
      Fulfilled.prototype.fold = function(f, z, c, to) {
        runContinuation3(f, z, this, c, to);
      };
  
      Fulfilled.prototype.when = function(cont) {
        runContinuation1(cont.fulfilled, this, cont.receiver, cont.resolver);
      };
  
      var errorId = 0;
  
      /**
       * Handler for a rejected promise
       * @param {*} x rejection reason
       * @constructor
       */
      function Rejected(x) {
        Promise.createContext(this);
  
        this.id = ++errorId;
        this.value = x;
        this.handled = false;
        this.reported = false;
  
        this._report();
      }
  
      inherit(Handler, Rejected);
  
      Rejected.prototype._state = -1;
  
      Rejected.prototype.fold = function(f, z, c, to) {
        to.become(this);
      };
  
      Rejected.prototype.when = function(cont) {
        if(typeof cont.rejected === 'function') {
          this._unreport();
        }
        runContinuation1(cont.rejected, this, cont.receiver, cont.resolver);
      };
  
      Rejected.prototype._report = function(context) {
        tasks.afterQueue(new ReportTask(this, context));
      };
  
      Rejected.prototype._unreport = function() {
        if(this.handled) {
          return;
        }
        this.handled = true;
        tasks.afterQueue(new UnreportTask(this));
      };
  
      Rejected.prototype.fail = function(context) {
        this.reported = true;
        emitRejection('unhandledRejection', this);
        Promise.onFatalRejection(this, context === void 0 ? this.context : context);
      };
  
      function ReportTask(rejection, context) {
        this.rejection = rejection;
        this.context = context;
      }
  
      ReportTask.prototype.run = function() {
        if(!this.rejection.handled && !this.rejection.reported) {
          this.rejection.reported = true;
          emitRejection('unhandledRejection', this.rejection) ||
            Promise.onPotentiallyUnhandledRejection(this.rejection, this.context);
        }
      };
  
      function UnreportTask(rejection) {
        this.rejection = rejection;
      }
  
      UnreportTask.prototype.run = function() {
        if(this.rejection.reported) {
          emitRejection('rejectionHandled', this.rejection) ||
            Promise.onPotentiallyUnhandledRejectionHandled(this.rejection);
        }
      };
  
      // Unhandled rejection hooks
      // By default, everything is a noop
  
      Promise.createContext
        = Promise.enterContext
        = Promise.exitContext
        = Promise.onPotentiallyUnhandledRejection
        = Promise.onPotentiallyUnhandledRejectionHandled
        = Promise.onFatalRejection
        = noop;
  
      // Errors and singletons
  
      var foreverPendingHandler = new Handler();
      var foreverPendingPromise = new Promise(Handler, foreverPendingHandler);
  
      function cycle() {
        return new Rejected(new TypeError('Promise cycle'));
      }
  
      // Task runners
  
      /**
       * Run a single consumer
       * @constructor
       */
      function ContinuationTask(continuation, handler) {
        this.continuation = continuation;
        this.handler = handler;
      }
  
      ContinuationTask.prototype.run = function() {
        this.handler.join().when(this.continuation);
      };
  
      /**
       * Run a queue of progress handlers
       * @constructor
       */
      function ProgressTask(value, handler) {
        this.handler = handler;
        this.value = value;
      }
  
      ProgressTask.prototype.run = function() {
        var q = this.handler.consumers;
        if(q === void 0) {
          return;
        }
  
        for (var c, i = 0; i < q.length; ++i) {
          c = q[i];
          runNotify(c.progress, this.value, this.handler, c.receiver, c.resolver);
        }
      };
  
      /**
       * Assimilate a thenable, sending it's value to resolver
       * @param {function} then
       * @param {object|function} thenable
       * @param {object} resolver
       * @constructor
       */
      function AssimilateTask(then, thenable, resolver) {
        this._then = then;
        this.thenable = thenable;
        this.resolver = resolver;
      }
  
      AssimilateTask.prototype.run = function() {
        var h = this.resolver;
        tryAssimilate(this._then, this.thenable, _resolve, _reject, _notify);
  
        function _resolve(x) { h.resolve(x); }
        function _reject(x)  { h.reject(x); }
        function _notify(x)  { h.notify(x); }
      };
  
      function tryAssimilate(then, thenable, resolve, reject, notify) {
        try {
          then.call(thenable, resolve, reject, notify);
        } catch (e) {
          reject(e);
        }
      }
  
      /**
       * Fold a handler value with z
       * @constructor
       */
      function Fold(f, z, c, to) {
        this.f = f; this.z = z; this.c = c; this.to = to;
        this.resolver = failIfRejected;
        this.receiver = this;
      }
  
      Fold.prototype.fulfilled = function(x) {
        this.f.call(this.c, this.z, x, this.to);
      };
  
      Fold.prototype.rejected = function(x) {
        this.to.reject(x);
      };
  
      Fold.prototype.progress = function(x) {
        this.to.notify(x);
      };
  
      // Other helpers
  
      /**
       * @param {*} x
       * @returns {boolean} true iff x is a trusted Promise
       */
      function isPromise(x) {
        return x instanceof Promise;
      }
  
      /**
       * Test just enough to rule out primitives, in order to take faster
       * paths in some code
       * @param {*} x
       * @returns {boolean} false iff x is guaranteed *not* to be a thenable
       */
      function maybeThenable(x) {
        return (typeof x === 'object' || typeof x === 'function') && x !== null;
      }
  
      function runContinuation1(f, h, receiver, next) {
        if(typeof f !== 'function') {
          return next.become(h);
        }
  
        Promise.enterContext(h);
        tryCatchReject(f, h.value, receiver, next);
        Promise.exitContext();
      }
  
      function runContinuation3(f, x, h, receiver, next) {
        if(typeof f !== 'function') {
          return next.become(h);
        }
  
        Promise.enterContext(h);
        tryCatchReject3(f, x, h.value, receiver, next);
        Promise.exitContext();
      }
  
      /**
       * @deprecated
       */
      function runNotify(f, x, h, receiver, next) {
        if(typeof f !== 'function') {
          return next.notify(x);
        }
  
        Promise.enterContext(h);
        tryCatchReturn(f, x, receiver, next);
        Promise.exitContext();
      }
  
      function tryCatch2(f, a, b) {
        try {
          return f(a, b);
        } catch(e) {
          return reject(e);
        }
      }
  
      /**
       * Return f.call(thisArg, x), or if it throws return a rejected promise for
       * the thrown exception
       */
      function tryCatchReject(f, x, thisArg, next) {
        try {
          next.become(getHandler(f.call(thisArg, x)));
        } catch(e) {
          next.become(new Rejected(e));
        }
      }
  
      /**
       * Same as above, but includes the extra argument parameter.
       */
      function tryCatchReject3(f, x, y, thisArg, next) {
        try {
          f.call(thisArg, x, y, next);
        } catch(e) {
          next.become(new Rejected(e));
        }
      }
  
      /**
       * @deprecated
       * Return f.call(thisArg, x), or if it throws, *return* the exception
       */
      function tryCatchReturn(f, x, thisArg, next) {
        try {
          next.notify(f.call(thisArg, x));
        } catch(e) {
          next.notify(e);
        }
      }
  
      function inherit(Parent, Child) {
        Child.prototype = objectCreate(Parent.prototype);
        Child.prototype.constructor = Child;
      }
  
      function snd(x, y) {
        return y;
      }
  
      function noop() {}
  
      function initEmitRejection() {
        /*global process, self, CustomEvent*/
        if(typeof process !== 'undefined' && process !== null
          && typeof process.emit === 'function') {
          // Returning falsy here means to call the default
          // onPotentiallyUnhandledRejection API.  This is safe even in
          // browserify since process.emit always returns falsy in browserify:
          // https://github.com/defunctzombie/node-process/blob/master/browser.js#L40-L46
          return function(type, rejection) {
            return type === 'unhandledRejection'
              ? process.emit(type, rejection.value, rejection)
              : process.emit(type, rejection);
          };
        } else if(typeof self !== 'undefined' && typeof CustomEvent === 'function') {
          return (function(noop, self, CustomEvent) {
            var hasCustomEvent = false;
            try {
              var ev = new CustomEvent('unhandledRejection');
              hasCustomEvent = ev instanceof CustomEvent;
            } catch (e) {}
  
            return !hasCustomEvent ? noop : function(type, rejection) {
              var ev = new CustomEvent(type, {
                detail: {
                  reason: rejection.value,
                  key: rejection
                },
                bubbles: false,
                cancelable: true
              });
  
              return !self.dispatchEvent(ev);
            };
          }(noop, self, CustomEvent));
        }
  
        return noop;
      }
  
      return Promise;
    };
  });
  }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));
  
  }).call(this,require('_process'))
  },{"_process":481}],591:[function(require,module,exports){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  /** @author Brian Cavalier */
  /** @author John Hann */
  
  (function(define) { 'use strict';
  define(function() {
  
    return {
      pending: toPendingState,
      fulfilled: toFulfilledState,
      rejected: toRejectedState,
      inspect: inspect
    };
  
    function toPendingState() {
      return { state: 'pending' };
    }
  
    function toRejectedState(e) {
      return { state: 'rejected', reason: e };
    }
  
    function toFulfilledState(x) {
      return { state: 'fulfilled', value: x };
    }
  
    function inspect(handler) {
      var state = handler.state();
      return state === 0 ? toPendingState()
         : state > 0   ? toFulfilledState(handler.value)
                       : toRejectedState(handler.value);
    }
  
  });
  }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));
  
  },{}],592:[function(require,module,exports){
  /** @license MIT License (c) copyright 2013 original author or authors */
  
  /**
   * Collection of helpers for interfacing with node-style asynchronous functions
   * using promises.
   *
   * @author Brian Cavalier
   * @contributor Renato Zannon
   */
  
  (function(define) {
  define(function(require) {
  
    var when = require('./when');
    var _liftAll = require('./lib/liftAll');
    var setTimer = require('./lib/env').setTimer;
    var slice = Array.prototype.slice;
  
    var _apply = require('./lib/apply')(when.Promise, dispatch);
  
    return {
      lift: lift,
      liftAll: liftAll,
      apply: apply,
      call: call,
      createCallback: createCallback,
      bindCallback: bindCallback,
      liftCallback: liftCallback
    };
  
    /**
     * Takes a node-style async function and calls it immediately (with an optional
     * array of arguments or promises for arguments). It returns a promise whose
     * resolution depends on whether the async functions calls its callback with the
     * conventional error argument or not.
     *
     * With this it becomes possible to leverage existing APIs while still reaping
     * the benefits of promises.
     *
     * @example
     *    function onlySmallNumbers(n, callback) {
     *		if(n < 10) {
     *			callback(null, n + 10);
     *		} else {
     *			callback(new Error("Calculation failed"));
     *		}
     *	}
     *
     *    var nodefn = require("when/node/function");
     *
     *    // Logs '15'
     *    nodefn.apply(onlySmallNumbers, [5]).then(console.log, console.error);
     *
     *    // Logs 'Calculation failed'
     *    nodefn.apply(onlySmallNumbers, [15]).then(console.log, console.error);
     *
     * @param {function} f node-style function that will be called
     * @param {Array} [args] array of arguments to func
     * @returns {Promise} promise for the value func passes to its callback
     */
    function apply(f, args) {
      return _apply(f, this, args || []);
    }
  
    function dispatch(f, thisArg, args, h) {
      var cb = createCallback(h);
      try {
        switch(args.length) {
          case 2: f.call(thisArg, args[0], args[1], cb); break;
          case 1: f.call(thisArg, args[0], cb); break;
          case 0: f.call(thisArg, cb); break;
          default:
            args.push(cb);
            f.apply(thisArg, args);
        }
      } catch(e) {
        h.reject(e);
      }
    }
  
    /**
     * Has the same behavior that {@link apply} has, with the difference that the
     * arguments to the function are provided individually, while {@link apply} accepts
     * a single array.
     *
     * @example
     *    function sumSmallNumbers(x, y, callback) {
     *		var result = x + y;
     *		if(result < 10) {
     *			callback(null, result);
     *		} else {
     *			callback(new Error("Calculation failed"));
     *		}
     *	}
     *
     *    // Logs '5'
     *    nodefn.call(sumSmallNumbers, 2, 3).then(console.log, console.error);
     *
     *    // Logs 'Calculation failed'
     *    nodefn.call(sumSmallNumbers, 5, 10).then(console.log, console.error);
     *
     * @param {function} f node-style function that will be called
     * @param {...*} [args] arguments that will be forwarded to the function
     * @returns {Promise} promise for the value func passes to its callback
     */
    function call(f /*, args... */) {
      return _apply(f, this, slice.call(arguments, 1));
    }
  
    /**
     * Takes a node-style function and returns new function that wraps the
     * original and, instead of taking a callback, returns a promise. Also, it
     * knows how to handle promises given as arguments, waiting for their
     * resolution before executing.
     *
     * Upon execution, the orginal function is executed as well. If it passes
     * a truthy value as the first argument to the callback, it will be
     * interpreted as an error condition, and the promise will be rejected
     * with it. Otherwise, the call is considered a resolution, and the promise
     * is resolved with the callback's second argument.
     *
     * @example
     *    var fs = require("fs"), nodefn = require("when/node/function");
     *
     *    var promiseRead = nodefn.lift(fs.readFile);
     *
     *    // The promise is resolved with the contents of the file if everything
     *    // goes ok
     *    promiseRead('exists.txt').then(console.log, console.error);
     *
     *    // And will be rejected if something doesn't work out
     *    // (e.g. the files does not exist)
     *    promiseRead('doesnt_exist.txt').then(console.log, console.error);
     *
     *
     * @param {Function} f node-style function to be lifted
     * @param {...*} [args] arguments to be prepended for the new function @deprecated
     * @returns {Function} a promise-returning function
     */
    function lift(f /*, args... */) {
      var args1 = arguments.length > 1 ? slice.call(arguments, 1) : [];
      return function() {
        // TODO: Simplify once partialing has been removed
        var l = args1.length;
        var al = arguments.length;
        var args = new Array(al + l);
        var i;
        for(i=0; i<l; ++i) {
          args[i] = args1[i];
        }
        for(i=0; i<al; ++i) {
          args[i+l] = arguments[i];
        }
        return _apply(f, this, args);
      };
    }
  
    /**
     * Lift all the functions/methods on src
     * @param {object|function} src source whose functions will be lifted
     * @param {function?} combine optional function for customizing the lifting
     *  process. It is passed dst, the lifted function, and the property name of
     *  the original function on src.
     * @param {(object|function)?} dst option destination host onto which to place lifted
     *  functions. If not provided, liftAll returns a new object.
     * @returns {*} If dst is provided, returns dst with lifted functions as
     *  properties.  If dst not provided, returns a new object with lifted functions.
     */
    function liftAll(src, combine, dst) {
      return _liftAll(lift, combine, dst, src);
    }
  
    /**
     * Takes an object that responds to the resolver interface, and returns
     * a function that will resolve or reject it depending on how it is called.
     *
     * @example
     *	function callbackTakingFunction(callback) {
     *		if(somethingWrongHappened) {
     *			callback(error);
     *		} else {
     *			callback(null, interestingValue);
     *		}
     *	}
     *
     *	var when = require('when'), nodefn = require('when/node/function');
     *
     *	var deferred = when.defer();
     *	callbackTakingFunction(nodefn.createCallback(deferred.resolver));
     *
     *	deferred.promise.then(function(interestingValue) {
     *		// Use interestingValue
     *	});
     *
     * @param {Resolver} resolver that will be 'attached' to the callback
     * @returns {Function} a node-style callback function
     */
    function createCallback(resolver) {
      return function(err, value) {
        if(err) {
          resolver.reject(err);
        } else if(arguments.length > 2) {
          resolver.resolve(slice.call(arguments, 1));
        } else {
          resolver.resolve(value);
        }
      };
    }
  
    /**
     * Attaches a node-style callback to a promise, ensuring the callback is
     * called for either fulfillment or rejection. Returns a promise with the same
     * state as the passed-in promise.
     *
     * @example
     *	var deferred = when.defer();
     *
     *	function callback(err, value) {
     *		// Handle err or use value
     *	}
     *
     *	bindCallback(deferred.promise, callback);
     *
     *	deferred.resolve('interesting value');
     *
     * @param {Promise} promise The promise to be attached to.
     * @param {Function} callback The node-style callback to attach.
     * @returns {Promise} A promise with the same state as the passed-in promise.
     */
    function bindCallback(promise, callback) {
      promise = when(promise);
  
      if (callback) {
        promise.then(success, wrapped);
      }
  
      return promise;
  
      function success(value) {
        wrapped(null, value);
      }
  
      function wrapped(err, value) {
        setTimer(function () {
          callback(err, value);
        }, 0);
      }
    }
  
    /**
     * Takes a node-style callback and returns new function that accepts a
     * promise, calling the original callback when the promise is either
     * fulfilled or rejected with the appropriate arguments.
     *
     * @example
     *	var deferred = when.defer();
     *
     *	function callback(err, value) {
     *		// Handle err or use value
     *	}
     *
     *	var wrapped = liftCallback(callback);
     *
     *	// `wrapped` can now be passed around at will
     *	wrapped(deferred.promise);
     *
     *	deferred.resolve('interesting value');
     *
     * @param {Function} callback The node-style callback to wrap.
     * @returns {Function} The lifted, promise-accepting function.
     */
    function liftCallback(callback) {
      return function(promise) {
        return bindCallback(promise, callback);
      };
    }
  });
  
  })(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });
  
  
  
  
  },{"./lib/apply":577,"./lib/env":587,"./lib/liftAll":589,"./when":593}],593:[function(require,module,exports){
  /** @license MIT License (c) copyright 2010-2014 original author or authors */
  
  /**
   * Promises/A+ and when() implementation
   * when is part of the cujoJS family of libraries (http://cujojs.com/)
   * @author Brian Cavalier
   * @author John Hann
   */
  (function(define) { 'use strict';
  define(function (require) {
  
    var timed = require('./lib/decorators/timed');
    var array = require('./lib/decorators/array');
    var flow = require('./lib/decorators/flow');
    var fold = require('./lib/decorators/fold');
    var inspect = require('./lib/decorators/inspect');
    var generate = require('./lib/decorators/iterate');
    var progress = require('./lib/decorators/progress');
    var withThis = require('./lib/decorators/with');
    var unhandledRejection = require('./lib/decorators/unhandledRejection');
    var TimeoutError = require('./lib/TimeoutError');
  
    var Promise = [array, flow, fold, generate, progress,
      inspect, withThis, timed, unhandledRejection]
      .reduce(function(Promise, feature) {
        return feature(Promise);
      }, require('./lib/Promise'));
  
    var apply = require('./lib/apply')(Promise);
  
    // Public API
  
    when.promise     = promise;              // Create a pending promise
    when.resolve     = Promise.resolve;      // Create a resolved promise
    when.reject      = Promise.reject;       // Create a rejected promise
  
    when.lift        = lift;                 // lift a function to return promises
    when['try']      = attempt;              // call a function and return a promise
    when.attempt     = attempt;              // alias for when.try
  
    when.iterate     = Promise.iterate;      // DEPRECATED (use cujojs/most streams) Generate a stream of promises
    when.unfold      = Promise.unfold;       // DEPRECATED (use cujojs/most streams) Generate a stream of promises
  
    when.join        = join;                 // Join 2 or more promises
  
    when.all         = all;                  // Resolve a list of promises
    when.settle      = settle;               // Settle a list of promises
  
    when.any         = lift(Promise.any);    // One-winner race
    when.some        = lift(Promise.some);   // Multi-winner race
    when.race        = lift(Promise.race);   // First-to-settle race
  
    when.map         = map;                  // Array.map() for promises
    when.filter      = filter;               // Array.filter() for promises
    when.reduce      = lift(Promise.reduce);       // Array.reduce() for promises
    when.reduceRight = lift(Promise.reduceRight);  // Array.reduceRight() for promises
  
    when.isPromiseLike = isPromiseLike;      // Is something promise-like, aka thenable
  
    when.Promise     = Promise;              // Promise constructor
    when.defer       = defer;                // Create a {promise, resolve, reject} tuple
  
    // Error types
  
    when.TimeoutError = TimeoutError;
  
    /**
     * Get a trusted promise for x, or by transforming x with onFulfilled
     *
     * @param {*} x
     * @param {function?} onFulfilled callback to be called when x is
     *   successfully fulfilled.  If promiseOrValue is an immediate value, callback
     *   will be invoked immediately.
     * @param {function?} onRejected callback to be called when x is
     *   rejected.
     * @param {function?} onProgress callback to be called when progress updates
     *   are issued for x. @deprecated
     * @returns {Promise} a new promise that will fulfill with the return
     *   value of callback or errback or the completion value of promiseOrValue if
     *   callback and/or errback is not supplied.
     */
    function when(x, onFulfilled, onRejected, onProgress) {
      var p = Promise.resolve(x);
      if (arguments.length < 2) {
        return p;
      }
  
      return p.then(onFulfilled, onRejected, onProgress);
    }
  
    /**
     * Creates a new promise whose fate is determined by resolver.
     * @param {function} resolver function(resolve, reject, notify)
     * @returns {Promise} promise whose fate is determine by resolver
     */
    function promise(resolver) {
      return new Promise(resolver);
    }
  
    /**
     * Lift the supplied function, creating a version of f that returns
     * promises, and accepts promises as arguments.
     * @param {function} f
     * @returns {Function} version of f that returns promises
     */
    function lift(f) {
      return function() {
        for(var i=0, l=arguments.length, a=new Array(l); i<l; ++i) {
          a[i] = arguments[i];
        }
        return apply(f, this, a);
      };
    }
  
    /**
     * Call f in a future turn, with the supplied args, and return a promise
     * for the result.
     * @param {function} f
     * @returns {Promise}
     */
    function attempt(f /*, args... */) {
      /*jshint validthis:true */
      for(var i=0, l=arguments.length-1, a=new Array(l); i<l; ++i) {
        a[i] = arguments[i+1];
      }
      return apply(f, this, a);
    }
  
    /**
     * Creates a {promise, resolver} pair, either or both of which
     * may be given out safely to consumers.
     * @return {{promise: Promise, resolve: function, reject: function, notify: function}}
     */
    function defer() {
      return new Deferred();
    }
  
    function Deferred() {
      var p = Promise._defer();
  
      function resolve(x) { p._handler.resolve(x); }
      function reject(x) { p._handler.reject(x); }
      function notify(x) { p._handler.notify(x); }
  
      this.promise = p;
      this.resolve = resolve;
      this.reject = reject;
      this.notify = notify;
      this.resolver = { resolve: resolve, reject: reject, notify: notify };
    }
  
    /**
     * Determines if x is promise-like, i.e. a thenable object
     * NOTE: Will return true for *any thenable object*, and isn't truly
     * safe, since it may attempt to access the `then` property of x (i.e.
     *  clever/malicious getters may do weird things)
     * @param {*} x anything
     * @returns {boolean} true if x is promise-like
     */
    function isPromiseLike(x) {
      return x && typeof x.then === 'function';
    }
  
    /**
     * Return a promise that will resolve only once all the supplied arguments
     * have resolved. The resolution value of the returned promise will be an array
     * containing the resolution values of each of the arguments.
     * @param {...*} arguments may be a mix of promises and values
     * @returns {Promise}
     */
    function join(/* ...promises */) {
      return Promise.all(arguments);
    }
  
    /**
     * Return a promise that will fulfill once all input promises have
     * fulfilled, or reject when any one input promise rejects.
     * @param {array|Promise} promises array (or promise for an array) of promises
     * @returns {Promise}
     */
    function all(promises) {
      return when(promises, Promise.all);
    }
  
    /**
     * Return a promise that will always fulfill with an array containing
     * the outcome states of all input promises.  The returned promise
     * will only reject if `promises` itself is a rejected promise.
     * @param {array|Promise} promises array (or promise for an array) of promises
     * @returns {Promise} promise for array of settled state descriptors
     */
    function settle(promises) {
      return when(promises, Promise.settle);
    }
  
    /**
     * Promise-aware array map function, similar to `Array.prototype.map()`,
     * but input array may contain promises or values.
     * @param {Array|Promise} promises array of anything, may contain promises and values
     * @param {function(x:*, index:Number):*} mapFunc map function which may
     *  return a promise or value
     * @returns {Promise} promise that will fulfill with an array of mapped values
     *  or reject if any input promise rejects.
     */
    function map(promises, mapFunc) {
      return when(promises, function(promises) {
        return Promise.map(promises, mapFunc);
      });
    }
  
    /**
     * Filter the provided array of promises using the provided predicate.  Input may
     * contain promises and values
     * @param {Array|Promise} promises array of promises and values
     * @param {function(x:*, index:Number):boolean} predicate filtering predicate.
     *  Must return truthy (or promise for truthy) for items to retain.
     * @returns {Promise} promise that will fulfill with an array containing all items
     *  for which predicate returned truthy.
     */
    function filter(promises, predicate) {
      return when(promises, function(promises) {
        return Promise.filter(promises, predicate);
      });
    }
  
    return when;
  });
  })(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });
  
  },{"./lib/Promise":574,"./lib/TimeoutError":576,"./lib/apply":577,"./lib/decorators/array":578,"./lib/decorators/flow":579,"./lib/decorators/fold":580,"./lib/decorators/inspect":581,"./lib/decorators/iterate":582,"./lib/decorators/progress":583,"./lib/decorators/timed":584,"./lib/decorators/unhandledRejection":585,"./lib/decorators/with":586}],595:[function(require,module,exports){
  'use strict';
  (function (global){
    'use strict';
    
    const $ = require('../spotify-elements');
    const live = require('../live');
    const sliderHandler = require('./slider');
    
    const CANCLE_SELECTOR = '[data-interaction-intent="reddit-cancel"]';
    const CONFIRM_SELECTOR = '[data-interaction-intent="reddit-confirm"]';
    const OPEN_SELECTOR = '[data-interaction-intent="reddit-open-setting"]';
    const MOVEUP_SELECTOR = '[data-interaction-intent="subreddit-moveup"]'
    const MOVEDOWN_SELECTOR = '[data-interaction-intent="subreddit-movedown"]'
    const DELETE_SELECTOR = '[data-interaction-intent="subreddit-delete"]'

    const TITLE_SELECTOR = '[data-bind="settingTitle"]'
    const FOOTER_CANCLE_SELECTOR = '[data-bind="cancelButtonLabel"]'
    const FOOTER_APPLY_SELECTOR = '[data-bind="okButtonLabel"]'
    const FORM_PLACEHOLDER_SELECTOR = '[data-bind="reddit-form-placeholder"]'
    const FORM_TITLE_SELECTOR = '[data-bind="reddit-form-title"]'
    const OPTIONS_TITLE_SELECTOR = '[data-bind="reddit-option-title"]'

    let model = live("reddit:setting")

    let isAttached = false
    let settingButton;
    let mainPopover;
    let popover;
    let backdrop;
    let subList;

    let subInputForm;
    let subInput;
    let autoComp;

    let subreddits;
    let optionMain;
    let options;

    let oldSubredditsString;
    let oldOptions;
    let oldOptionsString;

    var hbs = require('../../../../libs/spotify-handlebars');
    hbs.register(require('../../../../libs/spotify-handlebars/helpers'));
    var slider = require('./lib/view-helpers/sliderSetting');
    hbs.register(slider);
    
    let reduxStore;
    let strings;

    let sortByButton;
    const SORTBY_SELECTOR = '[data-interaction-intent="reddit-open-sortby-contextmenu"]';
    
    let sortTimeButton;
    const SORTTIME_SELECTOR = '[data-interaction-intent="reddit-open-sorttime-contextmenu"]';
    
    function fadeIn() {
      initState()
      sliderHandler.attach();
      sliderHandler.update();
      popover.style.display = "block",
      document.body.classList.add("modal-open"),
      backdrop.classList.add("fade"),
      popover.classList.add("fade"),
      setTimeout(() => {
        backdrop.classList.add("in"),
        popover.classList.add("in")
      }, 10)
    }

    function fadeOut(resetState = true) {
      backdrop.classList.remove("in"),
      popover.classList.remove("in"),
      document.body.classList.remove("modal-open"),
      setTimeout(() => {
        backdrop.classList.remove("fade"),
        popover.classList.remove("fade"),
        popover.style.display = "none",
        document.activeElement && document.activeElement.blur()
      }, 300)
      
      detach();
    }

    function addSub(subName) {
      subreddits.unshift(subName);
      drawSubList();
    }

    function drawSubList() {
      var subs = "";
      subreddits.forEach((sub, index) => {
        subs += `
        <div class="card-info-subtitle-metadata" data-interaction-intent="remove-subreddit">${sub}</div>
        <button class="button button-icon-only spoticon-arrow-up-16" data-interaction-intent="subreddit-moveup" data-subindex="${index}" ${index == 0 && "disabled"}></button>
        <button class="button button-icon-only spoticon-arrow-down-16" data-interaction-intent="subreddit-movedown" data-subindex="${index}" ${index == (subreddits.length-1) && "disabled"}></button>
        <button class="button button-icon-only spoticon-x-16" data-interaction-intent="subreddit-delete" data-subindex="${index}"></button>
      `
      })
      subList.innerHTML = subs
    }

    function onSubUp(event, node) {
      var curIndex = parseInt(node.attribute("data-subindex"));
      var newIndex = curIndex - 1
      if (newIndex >= 0) {
        const temp = subreddits[curIndex];
        subreddits[curIndex] = subreddits[newIndex];
        subreddits[newIndex] = temp;
      }
      drawSubList();
    }

    function onSubDown(event, node) {
      const curIndex = parseInt(node.attribute("data-subindex"));
      const newIndex = curIndex + 1
      if (newIndex < subreddits.length) {
        const temp = subreddits[curIndex];
        subreddits[curIndex] = subreddits[newIndex];
        subreddits[newIndex] = temp;
      }
      drawSubList();
    }

    function onSubDelete(event, node) {
      var curIndex = node.attribute("data-subindex");
      subreddits.splice(curIndex, 1);
      drawSubList();
    }

    function onApply() {
      fadeOut(false);

      var subredditString = JSON.stringify(subreddits);
      var optionString = JSON.stringify(options)

      localStorage.setItem("SubredditList", subredditString);
      localStorage.setItem("RedditOptions", optionString);

      if ((oldSubredditsString !== subredditString) ||
          (oldOptions.youtube !== options.youtube)
        ) {
        document.location.reload();
      } else if (oldOptionsString !== optionString) {
        reduxStore.dispatch((dispatch) => {
          dispatch({
            type: 'fetch-data-result',
            path: 'reload-data',
            username: 'reddit',
            result: {
              options
            }
          });
        });
        initState();
      }
    }

    function drawSlider() {
      model.update(options);
      optionMain.innerHTML  = slider("reddit:setting", "typename", null, true, strings["setting.options.show_type_name"])
      optionMain.innerHTML += slider("reddit:setting", "upvote", null, true, strings["setting.options.show_upvote"])
      optionMain.innerHTML += slider("reddit:setting", "follower", null, true, strings["setting.options.show_follower_count"])
      optionMain.innerHTML += slider("reddit:setting", "youtube", null, true, strings["setting.options.fetch_youtube"])
    }

    function initState() {
      subreddits = oldSubredditsString = localStorage.getItem("SubredditList");
      subreddits = JSON.parse(subreddits);
      drawSubList();

      options = oldOptionsString = localStorage.getItem("RedditOptions");
      options = JSON.parse(options);
      //Copy values
      oldOptions = {...options};
      drawSlider();
      
      model.update(options);
    }

    function hideContextMenu() {
      document.getElementById("reddit-sortby-context-menu").style.display = "none";  
      document.getElementById("reddit-sorttime-context-menu").style.display = "none";  
      document.removeEventListener("click", hideContextMenu);
    }

    function toggleSortByMenu(event) {
      const contextMenu = document.getElementById("reddit-sortby-context-menu");
      if (contextMenu.style.display === "block" || !event) {
        contextMenu.style.display = "none";
      } else {
        if (!options) initState();
        
        contextMenu.style.display = "block";
        contextMenu.children[0].style.left = `${event.pageX}px`;
        contextMenu.children[0].style.top = `${event.pageY}px`;
        document.addEventListener("click", hideContextMenu);
      }
    }

    function toggleSortTimeMenu(event) {
      const contextMenu = document.getElementById("reddit-sorttime-context-menu");
      if (contextMenu.style.display === "block" || !event) {
        contextMenu.style.display = "none";
      } else {
        if (!options) initState();
        
        contextMenu.style.display = "block";
        contextMenu.children[0].style.left = `${event.pageX}px`;
        contextMenu.children[0].style.top = `${event.pageY}px`;
        document.addEventListener("click", hideContextMenu);
      }
    }

    function changeSortBy(type) {
      const currentArgs = live('spotify:application').get("arguments").split(":");
      currentArgs[1] = type;
      live('spotify:application').update({
        arguments: currentArgs.join(':')
      });
      options.sortBy = type;
      onApply();
      $(SORTBY_SELECTOR).text(strings.sorts.replace("{0}", strings[`sort.${options.sortBy}`]));
    }

    function changeSortTime(type) {
      const currentArgs = live('spotify:application').get("arguments").split(":");
      currentArgs[2] = type;
      live('spotify:application').update({
        arguments: currentArgs.join(':')
      });
      options.sortTime = type;
      onApply();
      $(SORTTIME_SELECTOR).text(strings[`sort.${options.sortTime}`]);
    }

    var update = exports.update = () => {
      if (!settingButton) {
        sliderHandler.update();
      
        settingButton = document.getElementById("reddit-setting");
        if (settingButton) {
          $(document).delegate('click', OPEN_SELECTOR, fadeIn);
          $(document).delegate('click', CANCLE_SELECTOR, fadeOut);
          $(document).delegate('click', CONFIRM_SELECTOR, onApply);
          $(document).delegate('click', MOVEUP_SELECTOR, onSubUp);
          $(document).delegate('click', MOVEDOWN_SELECTOR, onSubDown);
          $(document).delegate('click', DELETE_SELECTOR, onSubDelete);
        }
      }

      if (!sortByButton) {
        sortByButton = document.getElementById("reddit-sortby");
        if (sortByButton) {
          const contextMenu = document.getElementById("reddit-sortby-context-menu");
          $(document).delegate('click', SORTBY_SELECTOR, toggleSortByMenu);
          for (const item of contextMenu.getElementsByClassName("item")) {
            const type = item.getAttribute("data-bind");
            item.addEventListener("mouseover", () => {
              item.classList.add("hover");
            });
            item.addEventListener("mouseout", () => {
              item.classList.remove("hover");
            });
            item.addEventListener("click", () => {
              changeSortBy(type);
            })
          }
        }
      }

      if (!sortTimeButton) {
        sortTimeButton = document.getElementById("reddit-sorttime");
        if (sortTimeButton) {
          const contextMenu = document.getElementById("reddit-sorttime-context-menu");
          $(document).delegate('click', SORTTIME_SELECTOR, toggleSortTimeMenu);
          for (const item of contextMenu.getElementsByClassName("item")) {
            const type = item.getAttribute("data-bind");
            item.addEventListener("mouseover", () => {
              item.classList.add("hover");
            });
            item.addEventListener("mouseout", () => {
              item.classList.remove("hover");
            });
            item.addEventListener("click", () => {
              changeSortTime(type);
            })
          }
        }
      }
    };
    
    var attach = exports.attach = () => {
      if (isAttached) return;
      isAttached = true;
      
      sliderHandler.attach();

      mainPopover = document.getElementById("reddit-setting-popover");
      popover = mainPopover.getElementsByClassName("popover")[0];
      backdrop = mainPopover.getElementsByClassName("modal-backdrop")[0];
      subList = document.getElementById("reddit-subreddit-list");

      subInputForm = document.getElementById("subreddit-input-form");
      subInput = document.getElementById("subreddit-input");
      autoComp = document.getElementById("autocomplete");

      var currentActive = -1;
      var inputTimeout;

      function onInputFocus() {
        if (inputTimeout) {
          clearTimeout(inputTimeout)
          inputTimeout = null;
        }
        const val = subInput.value;
        if (!val) {
          clearAutoComp();
          return;
        }
        // wait 0.3s before fetching result
        inputTimeout = setTimeout(() => {
          fetch(`https://www.reddit.com/subreddits/search.json?q=${val}&limit=5`)
            .then(r => r.json())
            .then(data => {
              if (!data || !data.data || data.data.children.length < 1) return;
              
              data = data.data.children
              var list = '';
              data.forEach((sub, index) => {
                list += `<div index="${index}">${sub.data.display_name}</div>`
              })
              autoComp.innerHTML = list, 
              autoComp.style.opacity = 1;
              for (const child of autoComp.children) {
                child.addEventListener("click", () => {
                  addSub(child.innerText);
                  clearAutoComp();
                })

                child.addEventListener("mouseover", () => {
                  currentActive = parseInt(child.getAttribute("index"));
                  child.classList.add("active")
                })

                child.addEventListener("mouseout", () => {
                  child.classList.remove("active")
                })
              }
            })
        }, 300)
      }
      
      function clearAutoComp() {
        autoComp.style.opacity = 0;
        setTimeout(() => {
          autoComp.innerHTML = "";
          currentActive = -1;
        }, 200);
      }

      function changeActive(dir) {
        const newActive = currentActive + dir;
        if (newActive >= 0 && newActive < autoComp.children.length) {
          currentActive = newActive;
        }

        for (const child of autoComp.children) {
        }

        autoComp.children[currentActive].classList.add("active");
      }

      function activateActive() {
        autoComp.children[currentActive].click();
      }

      subInputForm.addEventListener("submit", (event) => {
        event.preventDefault();
        if (subInput.value) {
          addSub(subInput.value);
        }
        subInput.value = "";
        subInput.blur();
      });

      subInputForm.addEventListener("focusout", () => {
        clearAutoComp();
      })
      
      subInput.addEventListener("focus", onInputFocus)

      subInput.addEventListener("input", onInputFocus)

      subInput.addEventListener("keydown", function(event) {
        if (event.keyCode == 40) { //down
          changeActive(1);
        } else if (event.keyCode == 38) { //up
          changeActive(-1);
        } else if (event.keyCode == 13 && currentActive !== -1) {
          event.preventDefault();
          activateActive();
        }
      });

      optionMain = document.getElementById("reddit-option");
      model.on('publish', function (data) {
        for (var prop in data) {
          options[prop] = data[prop];
        }
        
        model.update(options);
      })
    };
    
    var detach = exports.detach = () => {
      if (!isAttached) return;
      isAttached = false;
      sliderHandler.detach();
    };
    // bnmbnm
    exports.passStore = store => {
      reduxStore = store;
      strings = reduxStore.getState().application.i18n.strings;
      if (strings) {
        $(OPEN_SELECTOR).text(strings.settings)
        $(TITLE_SELECTOR).text(strings.settings)
        $(FOOTER_CANCLE_SELECTOR).text(strings.cancle)
        $(FOOTER_APPLY_SELECTOR).text(strings.save)
        $(FORM_TITLE_SELECTOR).text(strings["setting.form.add_subreddit"])
        $(FORM_PLACEHOLDER_SELECTOR).attribute("placeholder", strings["setting.form.subreddit_name"])
        $(OPTIONS_TITLE_SELECTOR).text(strings["setting.options.title"])

        if (!options) initState();
        $(SORTBY_SELECTOR).text(strings.sorts.replace("{0}", strings[`sort.${options.sortBy}`]))
        $('[data-bind="hot"]').text(strings["sort.hot"])
        $('[data-bind="new"]').text(strings["sort.new"])
        $('[data-bind="top"]').text(strings["sort.top"])
        $('[data-bind="rising"]').text(strings["sort.rising"])
        $('[data-bind="controversial"]').text(strings["sort.controversial"])
        $('[data-bind="hour"]').text(strings["sort.hour"])
        $('[data-bind="day"]').text(strings["sort.day"])
        $('[data-bind="week"]').text(strings["sort.week"])
        $('[data-bind="month"]').text(strings["sort.month"])
        $('[data-bind="year"]').text(strings["sort.year"])
        $('[data-bind="all"]').text(strings["sort.all"])
      }
    };

  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./creators":25,"./application.selectors":33,"../../../../libs/bridge":53,"../live":172,"../spotify-elements":210,"../../../../libs/spotify-handlebars/helpers":299,"../../../../libs/spotify-handlebars":309,"./lib/view-helpers/sliderSetting":596,"./slider":599}],596:[function(require,module,exports){
  'use strict';
  
  /**
   * Module dependencies
   */
  
  const Handlebars = require('../../../../libs/spotify-handlebars');
  const live = require('../../../../libs/live');
  const tpl = require('../../templates/sliderSetting.hbs');
  
  /**
   * Export a handlebars helper to render a settings row with a slider
   *
   * @param {String} uri - Uri to the controlled live model
   * @param {String} property - Name of property to control on live model
   * @param {String} className - ClassNames we want to add
   *     to the container
   * @param {Boolean} allowedProperty - Name of property defining if
   *     setting is available.
   *     If not specified, the setting will be available.
   *     If false, the slider will become disabled.
   */
  
  module.exports = function (uri, property, className, allowed, description) {
    let containerClassName;
  
    if (typeof className === 'string') {
      containerClassName = className;
    }
  
    var value = live(uri).get(property);

    if (value === undefined) {
      live(uri).update({ [property]: false})
      value = false;
    }
  
    const id = `slider-${uri}-${property}`;
  
    return new Handlebars.runtime.SafeString(tpl({
      description,
      uri,
      id,
      property,
      containerClassName: containerClassName,
      enabled: value,
      allowed: allowed,
      labelId: `${id}-label`
    }));
  };
  
  },{"../../../../libs/live":172,"../../../../libs/spotify-handlebars":309,"../../templates/sliderSetting.hbs":597}],597:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  var partial$0 = require('../../../libs/glue/templates/slider.hbs');
  HandlebarsCompiler.registerPartial('../../../libs/glue/templates/slider.hbs', partial$0);
  module.exports = HandlebarsCompiler.template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
  
    return "<div class=\"setting-row "
      + alias4(((helper = (helper = helpers.containerClassName || (depth0 != null ? depth0.containerClassName : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"containerClassName","hash":{},"data":data}) : helper)))
      + "\" data-row-uri=\""
      + alias4(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"uri","hash":{},"data":data}) : helper)))
      + "\" data-ta-id=\""
      + alias4(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"uri","hash":{},"data":data}) : helper)))
      + "\">\n  <label class=\"col description\" id=\""
      + alias4(((helper = (helper = helpers.labelId || (depth0 != null ? depth0.labelId : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"labelId","hash":{},"data":data}) : helper)))
      + "\">"
      + ((stack1 = ((helper = (helper = helpers.description || (depth0 != null ? depth0.description : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"description","hash":{},"data":data}) : helper))) != null ? stack1 : "")
      + "</label>\n  <div class=\"col action\">\n"
      + ((stack1 = container.invokePartial(partials["../../../libs/glue/templates/slider.hbs"],depth0,{"name":"../../../libs/glue/templates/slider.hbs","data":data,"indent":"    ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
      + "  </div>\n</div>\n";
  },"usePartial":true,"useData":true});
  
  },{"../../../libs/glue/templates/slider.hbs":598,"hbsfy/runtime":368}],598:[function(require,module,exports){
  // hbsfy compiled Handlebars template
  var HandlebarsCompiler = require('hbsfy/runtime');
  module.exports = HandlebarsCompiler.template({"1":function(container,depth0,helpers,partials,data) {
      return "true";
  },"3":function(container,depth0,helpers,partials,data) {
      return "false";
  },"5":function(container,depth0,helpers,partials,data) {
      return " enabled";
  },"7":function(container,depth0,helpers,partials,data) {
      return " disabled";
  },"9":function(container,depth0,helpers,partials,data) {
      return "enabled";
  },"11":function(container,depth0,helpers,partials,data) {
      return "disabled";
  },"13":function(container,depth0,helpers,partials,data) {
      return "aria-disabled=\"true\"";
  },"15":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "data-slider=\""
      + container.escapeExpression(((helper = (helper = helpers.id || (depth0 != null ? depth0.id : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"id","hash":{},"data":data}) : helper)))
      + "\"";
  },"17":function(container,depth0,helpers,partials,data) {
      return "data-slider";
  },"19":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "data-ta-id=\""
      + container.escapeExpression(((helper = (helper = helpers.id || (depth0 != null ? depth0.id : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"id","hash":{},"data":data}) : helper)))
      + "\"";
  },"21":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "aria-labelledby=\""
      + container.escapeExpression(((helper = (helper = helpers.labelId || (depth0 != null ? depth0.labelId : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"labelId","hash":{},"data":data}) : helper)))
      + "\"";
  },"23":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "data-uri=\""
      + container.escapeExpression(((helper = (helper = helpers.uri || (depth0 != null ? depth0.uri : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"uri","hash":{},"data":data}) : helper)))
      + "\"";
  },"25":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "data-slider-property=\""
      + container.escapeExpression(((helper = (helper = helpers.property || (depth0 != null ? depth0.property : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"property","hash":{},"data":data}) : helper)))
      + "\"";
  },"27":function(container,depth0,helpers,partials,data) {
      var helper;
  
    return "data-slider-allowed-property=\""
      + container.escapeExpression(((helper = (helper = helpers.allowedProperty || (depth0 != null ? depth0.allowedProperty : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"allowedProperty","hash":{},"data":data}) : helper)))
      + "\"";
  },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
      var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing;
  
    return "<div\n  tabindex=\"0\"\n  role=\"checkbox\"\n  aria-checked=\""
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.enabled : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.program(3, data, 0),"data":data})) != null ? stack1 : "")
      + "\"\n  class=\"slider"
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.enabled : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + ((stack1 = (helpers.compare || (depth0 && depth0.compare) || alias2).call(alias1,(depth0 != null ? depth0.allowed : depth0),false,{"name":"compare","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\"\n  data-ta-slider-state=\""
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.enabled : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.program(11, data, 0),"data":data})) != null ? stack1 : "")
      + "\"\n  "
      + ((stack1 = (helpers.compare || (depth0 && depth0.compare) || alias2).call(alias1,(depth0 != null ? depth0.allowed : depth0),false,{"name":"compare","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n  "
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.id : depth0),{"name":"if","hash":{},"fn":container.program(15, data, 0),"inverse":container.program(17, data, 0),"data":data})) != null ? stack1 : "")
      + "\n  "
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.id : depth0),{"name":"if","hash":{},"fn":container.program(19, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n  "
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.labelId : depth0),{"name":"if","hash":{},"fn":container.program(21, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n  "
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.uri : depth0),{"name":"if","hash":{},"fn":container.program(23, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + "\n  "
      + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.property : depth0),{"name":"if","hash":{},"fn":container.program(25, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
      + ">\n  <div></div>\n</div>\n";
  },"useData":true});
  
  },{"hbsfy/runtime":368}],599:[function(require,module,exports){
    /**
     * @module spotify-events/slider
     */
    'use strict';
    
    const $ = require('../spotify-elements');
    const center = require('./center');
    
    const live = require('../live');
    
    const ATTR_SLIDER = 'slider';
    const ATTR_PROPERTY = 'slider-property';
    const SELECTOR_SLIDER = `[data-${ATTR_SLIDER}]`;
    const CLASSNAME_ENABLED = 'enabled';
    const CLASSNAME_DISABLED = 'disabled';
    const ATTR_ALLOWED = 'slider-allowed-property';
    const TA_ID_SLIDER_STATE = 'data-ta-slider-state';
    
    const SPACE_KEY_CODE = 32;
    
    // Stored nodes, used to not add listeners multiple times
    const storedNodes = [];
    
    /**
     * Handle a click on a slider node.
     *
     * @param {MouseEvent} event Event object.
     * @param {Elements} elem An elements DOM node for the slider.
     */
    function handleClick(event, elem) {
      const property = elem.data(ATTR_PROPERTY);
      if (property) {
        // If a property is set, update the model. That will later trigger
        // an update of the view and a change event on center will be sent.
        toggleModelPropertyAndPublish(elem[0], property);
      } else {
        // If no property is set, emit a change event on center, and let the handler
        // decide what's the state of the slider by emiting a 'slider-set' event.
        emitChangeEvent(elem[0], !elem.hasClass(CLASSNAME_ENABLED));
      }
    }
    
    /**
     * Handle a keyboard event on a slider node.
     *
     * @param {KeyboardEvent} event Event object.
     * @param {Elements} elem An elements DOM node for the slider.
     */
    function handleKeyUp(event, elem) {
      if (event.keyCode !== SPACE_KEY_CODE) {
        return;
      }
    
      handleClick(event, elem);
    }
    
    /**
     * Handle when a slider-set event happens on center.
     *
     * @param {Object} event An object with data.
     */
    function onSliderSet(event) {
      if (!event.id) return;
    
      const elem = $(`[data-${ATTR_SLIDER}=${event.id}]`);
      if (!elem) return;
    
      const node = elem[0];
      const enabled = !!event.enabled;
    
      const property = elem.data(ATTR_PROPERTY);
    
      if (property) {
        toggleModelPropertyAndPublish(node, property, enabled);
      } else {
        setState(node, enabled);
      }
    }
    
    /**
     * Update the live model with new data. If no data-uri is found from
     * the passed node, nothing happens.
     *
     * @param {HTMLElement} node A DOM node for the slider.
     * @param {string} property A property name on the model.
     * @param {boolean=} enabled Optional boolean flag. If true, this
     *     sets it to true, if false this sets it to false. If not provided,
     *     this sets it to the opposite of the current value in the model.
     */
    function toggleModelPropertyAndPublish(node, property, enabled) {

      const uri = getUriFromNode(node);
      if (!uri) return;
    
      const model = live(uri);
      // If the model doesn't have a current value, we can't safely
      // set a new value without maybe being wrong.
      const value = model.get(property);

      if (value === undefined) return;
    
      const newValue = enabled === undefined ? !value : enabled;
    
      const data = {};
      data[property] = newValue;
    
      // Publishing the new value to the model will trigger the update
      // handler added here in this module to update the visual state
      // of the button.
      model.publish(data);
    }
    
    /**
     * Update the state of one slider node.
     *
     * @param {HTMLElement} node A DOM node for the slider.
     */
    function updateNode(node) {
      // Don't handle the same URI and node twice.
      if (storedNodes.indexOf(node) > -1) return;
      storedNodes.push(node);
      const elem = $(node);
      const property = elem.data(ATTR_PROPERTY);
      const allowedProperty = elem.data(ATTR_ALLOWED);
    
      if (!property) return;
    
      const uri = getUriFromNode(node);
      if (!uri) return;
    
      const model = live(uri);
    
      model.get(property, function (error, value) {
        if (error) throw error;
        setState(node, !!value);
        emitChangeEvent(node, !!value);
    
        model.on('update', function (properties) {
          if (property in properties) {
            setState(node, !!properties[property]);
            emitChangeEvent(node, !!properties[property]);
          }
    
          if (allowedProperty && allowedProperty in properties) {
            disableNode(node, !properties[allowedProperty]);
          }
        });
      });
    }
    
    /**
     * Update a slider node based on a value.
     *
     * @param {HTMLElement} node A DOM node for a slider.
     * @param {boolean} enabled True if should be enabled, false otherwise.
     */
    function setState(node, enabled) {
      const elem = $(node);
      if (enabled) {
        elem.addClass(CLASSNAME_ENABLED);
        elem.setAttribute('aria-checked', 'true');
        elem.setAttribute(TA_ID_SLIDER_STATE, 'enabled');
      } else {
        elem.removeClass(CLASSNAME_ENABLED);
        elem.setAttribute('aria-checked', 'false');
        elem.setAttribute(TA_ID_SLIDER_STATE, 'disabled');
      }
    }
    
    /**
     * Update a slider node based on whether is disabled
     *
     * @param {HTMLElement} node A DOM node for a slider.
     * @param {boolean} disabled True if should be disabled, false otherwise.
     */
    function disableNode(node, disabled) {
      const elem = $(node);
      if (disabled) {
        elem.addClass(CLASSNAME_DISABLED);
      } else {
        elem.removeClass(CLASSNAME_DISABLED);
      }
    }
    
    /**
     * Emit a change event on center. It will have the following format:
     * {
     *   element: HTMLElement,
     *   id: 'id from data-slider attribute' || null,
     *   enabled: true // the new value
     * }
     *
     * @param {HTMLElement} node A DOM node for a slider.
     * @param {boolean} enabled True if enabled, false otherwise.
     */
    function emitChangeEvent(node, enabled) {
      const id = $(node).data(ATTR_SLIDER) || null;
      center.emit('slider-change', {
        element: node,
        id: id,
        enabled: enabled
      });
    }
    
    /**
     * Get a URI from a node.
     *
     * @param {HTMLElement} node A DOM node.
     *
     * @return {string?} A URI string or null if not found.
     */
    function getUriFromNode(node) {
      const stateNode = node.closest('[data-uri]');
      if (!stateNode) return null;
      return stateNode.getAttribute('data-uri');
    }
    
    let isAttached = false;
    
    /**
     * Update the module based on the current DOM state.
     *
     * @param {HTMLElement=} node Optional DOM node to make changes inside.
     */
    exports.update = function (node) {
      if (!isAttached) return;

      const nodes = $(node || document).search(SELECTOR_SLIDER);
      if (!nodes) return;
    
      nodes.forEach(updateNode);
    };
    
    /**
     * Attach event listeners etc.
     */
    exports.attach = function () {
      if (isAttached) return;
      isAttached = true;
    
      const doc = $(document);
      doc.delegate('click', SELECTOR_SLIDER, handleClick);
      doc.delegate('keyup', SELECTOR_SLIDER, handleKeyUp);
    
      center.on('slider-set', onSliderSet);
    };
    
    /**
     * Detach event listeners etc.
     */
    exports.detach = function () {
      if (!isAttached) return;
      isAttached = false;
    
      const doc = $(document);
      doc.undelegate('click', SELECTOR_SLIDER, handleClick);
      doc.undelegate('keyup', SELECTOR_SLIDER, handleKeyUp);
    
      center.off('slider-set', onSliderSet);

      storedNodes.length = 0;
    };
    
},{"../live":172,"../spotify-elements":210,"./center":221}],600:[function(require,module,exports){
  'use strict';
  
  const $ = require('../spotify-elements');

  const CARD = '.card-type-youtube';
  const CANCLE_SELECTOR = '[data-interaction-intent="youtube-close"]'
  
  let currentCards = [];
  let youtubeMain = document.getElementById("youtube-container")

  let main;
  let popover;
  let backdrop;

  function addCardImage(card) {
    let html;
    const element = card.querySelector('[data-html]');
    if (element) {
      html = element.getAttribute('data-html');
      card.addEventListener("click", () => {
        youtubeMain.innerHTML = unescape(html);
        fadeIn();
      })
    }
  }
  
  function fadeIn() {
    popover.style.display = "block",
    document.body.classList.add("modal-open"),
    backdrop.classList.add("fade"),
    popover.classList.add("fade"),
    setTimeout(() => {
      backdrop.classList.add("in"),
      popover.classList.add("in")
    }, 10)
  }

  function fadeOut() {
    backdrop.classList.remove("in"),
    popover.classList.remove("in"),
    document.body.classList.remove("modal-open"),
    setTimeout(() => {
      backdrop.classList.remove("fade"),
      popover.classList.remove("fade"),
      popover.style.display = "none",
      document.activeElement && document.activeElement.blur(),
      youtubeMain.innerHTML = "";
    }, 300);

  }

  let isAttached = false;
  
  exports.attach = function () {
    if (isAttached) return;
    isAttached = true;

    main = document.getElementById("reddit-youtube");
    popover = main.getElementsByClassName("popover")[0];
    backdrop = main.getElementsByClassName("modal-backdrop")[0];
    $(document).delegate('click', CANCLE_SELECTOR, fadeOut);
  };
  
  exports.detach = function () {
    if (!isAttached) return;
    isAttached = false;
  
    currentCards = [];
  };
  
  exports.update = function () {
    if (!isAttached) return;
  
    // Forget orphaned cards
    currentCards = currentCards.filter(function (card) {
      return card.closest('body');
    });
  
    const cards = document.querySelectorAll(CARD);
    cards.forEach(card => {
      const known = currentCards.indexOf(card) !== -1;
      if (known) {
        return;
      }
      currentCards.push(card);
      addCardImage(card);
    });
  };

},{"../spotify-elements":210}]},{},[24]);
  