[
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.swp\nbuild\ndist\ninstaller\nnode_modules\ncoverage\nnpm-debug.log"
  },
  {
    "path": "Gruntfile.js",
    "content": "var electron = require('electron-prebuilt')\nvar packagejson = require('./package.json')\nvar conf = require('./src/conf.js')\n\nmodule.exports = function (grunt) {\n  require('load-grunt-tasks')(grunt)\n\n  var BASENAME = conf['BASENAME']\n  var APPNAME = BASENAME\n  var DEVELPER_ID = conf['DEVELPER_ID']\n  var COMPANY = conf['COMPANY']\n  var ICON = conf['ICON']\n  var ICON_URL = conf['ICON_URL']\n  var BUNDLE_ID = conf['BUNDLE_ID']\n  var OSX_OUT = conf['OSX_OUT']\n  var OSX_FILENAME = conf['OSX_FILENAME']\n  var ELECTRON_VERSION = require('./node_modules/electron-prebuilt/package.json').version\n  var target = grunt.option('target') || 'development'\n  var env = process.env\n  var certificateFile = grunt.option('certificateFile')\n  var certificatePassword = grunt.option('certificatePassword')\n\n  env.NODE_PATH = '..:' + env.NODE_PATH\n  env.NODE_ENV = target\n\n  grunt.initConfig({\n    IDENTITY: 'Developer ID Application: ' + DEVELPER_ID,\n    APPNAME: APPNAME,\n    OSX_OUT: OSX_OUT,\n    OSX_FILENAME: OSX_FILENAME,\n    OSX_FILENAME_ESCAPED: OSX_FILENAME.replace(' ', '\\\\ ').replace('(', '\\\\(').replace(')', '\\\\)'),\n\n    // Electron\n    electron: {\n      windows: {\n        options: {\n          name: BASENAME,\n          dir: 'build',\n          out: 'dist',\n          version: ELECTRON_VERSION,\n          platform: 'win32',\n          arch: 'x64',\n          asar: true,\n          icon: ICON + '.ico'\n        }\n      },\n      osx: {\n        options: {\n          name: APPNAME,\n          dir: 'build',\n          out: 'dist',\n          version: ELECTRON_VERSION,\n          platform: 'darwin',\n          arch: 'x64',\n          asar: true,\n          'app-bundle-id': BUNDLE_ID\n        }\n      }\n    },\n\n    // Edits .exe files.\n    rcedit: {\n      exes: {\n        files: [{\n          expand: true,\n          cwd: 'dist/' + BASENAME + '-win32',\n          src: [BASENAME + '.exe']\n        }],\n        options: {\n          icon: ICON + '.ico',\n          'file-version': packagejson.version,\n          'product-version': packagejson.version,\n          'version-string': {\n            'CompanyName': COMPANY,\n            'ProductVersion': packagejson.version,\n            'ProductName': APPNAME,\n            'FileDescription': APPNAME,\n            'InternalName': BASENAME + '.exe',\n            'OriginalFilename': BASENAME + '.exe',\n            'LegalCopyright': 'Copyright 2015 ' + COMPANY + ' All rights reserved.'\n          }\n        }\n      }\n    },\n\n    // Windows installer options.\n    'create-windows-installer': {\n      appDirectory: 'dist/' + BASENAME + '-win32/',\n      authors: COMPANY,\n      // loadingGif: 'resources/loading.gif',\n      // setupIcon: 'resources/setup.ico',\n      iconUrl: ICON_URL,\n      description: APPNAME,\n      title: APPNAME,\n      exe: BASENAME + '.exe',\n      version: packagejson.version,\n      signWithParams: '/f ' + certificateFile + ' /p ' + certificatePassword + ' /tr http://timestamp.comodoca.com/rfc3161'\n    },\n\n    // Copy files.\n    copy: {\n      dev: {\n        files: [{\n          expand: true,\n          cwd: '.',\n          src: ['package.json', 'conf.json', 'index.html'],\n          dest: 'build/'\n        }, {\n          expand: true,\n          cwd: 'images/',\n          src: ['**/*'],\n          dest: 'build/images/'\n        }, {\n          expand: true,\n          cwd: 'fonts/',\n          src: ['**/*'],\n          dest: 'build/fonts/'\n        }, {\n          expand: true,\n          cwd: 'node_modules/material-design-icons/iconfont/',\n          src: ['MaterialIcons-Regular.woff2'],\n          dest: 'build/fonts/MaterialIcons/'\n        }, {\n          expand: true,\n          cwd: 'node_modules/',\n          src: Object.keys(packagejson.dependencies).map(function (dep) { return dep + '/**/*' }),\n          dest: 'build/node_modules/'\n        }, {\n          src: ICON + '.png',\n          dest: 'build/images/logo.png'\n        }]\n      },\n      windows: {\n        files: [{\n          expand: true,\n          cwd: 'resources',\n          src: [],\n          dest: 'dist/' + BASENAME + '-win32/resources/resources/'\n        }],\n        options: {\n          mode: true\n        }\n      },\n      osx: {\n        files: [{\n          expand: true,\n          cwd: 'resources',\n          src: [],\n          dest: '<%= OSX_FILENAME %>/Contents/Resources/resources/'\n        }, {\n          src: ICON + '.icns',\n          dest: '<%= OSX_FILENAME %>/Contents/Resources/atom.icns'\n        }],\n        options: {\n          mode: true\n        }\n      }\n    },\n\n    // Javascript (Babel)\n    babel: {\n      options: {\n        stage: 0,\n        sourceMap: 'inline',\n        blacklist: 'regenerator'\n      },\n      dist: {\n        files: [{\n          expand: true,\n          cwd: 'src/',\n          src: ['**/*.js'],\n          dest: 'build/'\n        }]\n      }\n    },\n\n    // Styles (LESS)\n    less: {\n      options: {\n        sourceMapFileInline: true\n      },\n      dist: {\n        files: {\n          'build/main.css': 'styles/index.less'\n        }\n      }\n    },\n\n    // Rename files.\n    rename: {\n      installer: {\n        src: 'installer/Setup.exe',\n        dest: 'installer/' + BASENAME + 'Setup-' + packagejson.version + '.exe'\n      }\n    },\n\n    // Shell options.\n    shell: {\n      electron: {\n        command: electron + ' build',\n        options: {\n          async: true,\n          execOptions: {\n            env: env\n          }\n        }\n      },\n      sign: {\n        options: {\n          failOnError: false\n        },\n        command: [\n          'codesign --deep -v -f -s \"<%= IDENTITY %>\" <%= OSX_FILENAME_ESCAPED %>/Contents/Frameworks/*',\n          'codesign -v -f -s \"<%= IDENTITY %>\" <%= OSX_FILENAME_ESCAPED %>',\n          'codesign -vvv --display <%= OSX_FILENAME_ESCAPED %>',\n          'codesign -v --verify <%= OSX_FILENAME_ESCAPED %>'\n        ].join(' && ')\n      },\n      zip: {\n        command: 'ditto -c -k --sequesterRsrc --keepParent <%= OSX_FILENAME_ESCAPED %> <%= OSX_OUT %>/' + BASENAME + '-' + packagejson.version + '.zip'\n      }\n    },\n\n    // Clean options\n    clean: {\n      release: ['build/', 'dist/', 'installer/']\n    },\n\n    // Live Reload\n    watchChokidar: {\n      options: {\n        spawn: true\n      },\n      livereload: {\n        options: {livereload: true},\n        files: ['build/**/*']\n      },\n      js: {\n        files: ['src/**/*.js'],\n        tasks: ['newer:babel']\n      },\n      less: {\n        files: ['styles/**/*.less'],\n        tasks: ['less']\n      },\n      copy: {\n        files: ['images/*', 'index.html', 'fonts/*'],\n        tasks: ['newer:copy:dev']\n      }\n    }\n  })\n\n  // Define default (development) task.\n  grunt.registerTask('default', ['newer:babel', 'less', 'newer:copy:dev', 'shell:electron', 'watchChokidar'])\n\n  // Define platform-specific release task.\n  if (process.platform === 'win32') {\n    grunt.registerTask('release', ['clean:release', 'babel', 'less', 'copy:dev', 'electron:windows', 'copy:windows', 'rcedit:exes', 'create-windows-installer', 'rename:installer'])\n  } else {\n    grunt.registerTask('release', ['clean:release', 'babel', 'less', 'copy:dev', 'electron:osx', 'copy:osx', 'shell:sign', 'shell:zip'])\n  }\n\n  // Bail-out on electron command if this dies.\n  process.on('SIGINT', function () {\n    grunt.task.run(['shell:electron:kill'])\n    process.exit(1)\n  })\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2015 Terra Eclipse, Inc.\nhttp://www.terraeclipse.com/\n\nCopyright 2015 Brian Link\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n\n-------------------------------------------------------------------------\n                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS"
  },
  {
    "path": "Makefile",
    "content": "all: install dev\n\ninstall:\n\t@echo \"\"\n\t@test -d ./node_modules || npm install\n\ndev:\n\t@echo \"\"\n\t@./node_modules/.bin/standard ./src/**/*.js\n\t@./node_modules/.bin/grunt\n\nrelease: install\n\t@echo \"\"\n\t@./node_modules/.bin/grunt release --target=\"production\"\n\nrun:\n\t@echo \"\"\n\t@open ./dist/RedisExplorer-darwin-x64/RedisExplorer.app\n\ncopy:\n\t@echo \"\"\n\t@rm -Rf /Applications/RedisExplorer.app\n\t@cp -R ./dist/RedisExplorer-darwin-x64/RedisExplorer.app /Applications/\n\n.PHONY: install\n.PHONY: dev\n.PHONY: release\n.PHONY: run\n.PHONY: copy"
  },
  {
    "path": "README.md",
    "content": "RedisExplorer\n==============\n\nAn [electron](http://electron.atom.io/)-powered GUI for [redis](http://redis.io/).\n\n![screencap](https://raw.githubusercontent.com/cpsubrian/redis-explorer/master/resources/screencap.gif)\n\nInstallation\n------------\n\nCurrently, I'm only building for Mac OSX, but that may change as the feature-set\nbecomes more stable.\n\nYou'll find all the latest releases [here](https://github.com/cpsubrian/redis-explorer/releases).\n\nConnecting to Remote Hosts\n--------------------------\n\nThe app currently **only** supports connecting to remote hosts via an ssh tunnel\nvia your local `ssh-agent` and **no password**. Right now only redis servers\nrunning on the default port can be connected to, but many more configuration\noptions are on the roadmap.\n\nAvailable hosts will be parsed from your `~/.ssh/config`, which should contain\nentries like:\n\n```\nHost myhost\n  Hostname [ip address]\n  User [username]\n\nHost anotherhost\n  Hostname [ip address]\n  Port [port]\n  User [username]\n```\n\nDevelopment\n-----------\n\nI would love to add **your** name as a collaborator here :) Please discuss bugs\nor new features here on the issues queue before spending time on them, because\nI'm working through an internal list of TODOs at the moment and want to avoid\nstepping on toes. Once I get the main feature list implemented, I'll maintain\na roadmap and wishlist here in the README so others can contribute more easily.\n\nRoadmap\n-------\n\n- Read All Key Types\n  - [x] String\n  - [x] List\n    - [ ] Change from `LRANGE -inf +inf` to an iterator, to handle really\n          large lists better.\n  - [x] Set\n    - [ ] Change from `SMEMBERS` to `SSCAN`, to handle large sets better.\n  - [x] Sorted Set\n    - [ ] Change from `ZRANGEBYSCORE` to `ZSCAN` or and interator-style range, to handle large sets better.\n  - [ ] Hash\n- Keyspace 'overview'\n  - [ ] Implement a collapsable tree-structure for browsing the keyspace\n    - Configurable delimeter per hosts\n- Manage Remote Hosts\n  - [ ] CRUD Hosts\n  - [ ] Import from ~/.ssh/config ?\n  - [ ] Support other auth schemes\n    - [x] ssh-agent tunnel\n    - others?\n- Console/REPL\n  - [ ] Add a 'console' tab that replecates the functionality of the redis-client REPL\n- Delete Data\n  - [ ] Delete keys\n  - [ ] Delete from lists\n  - [ ] Delete form sets/sorted-sets\n  - [ ] Delete hash keys\n  - [ ] Bulk Delete all of the above\n- Edit Data\n  - [ ] Edit string keys\n  - [ ] Edit lists, Add items\n  - [ ] Edit sets, Add items\n  - [ ] Edit hashes\n\n\n### How to Build\n\nFor development, you'll just need to clone this repo and run `make`.\n\nIf you want to build a release, you'll probably need to go edit the config\nand include your Apple developer ID, which will need to have been set up\nvia XCode. How to do that is beyond the scope of this project.\n\n\n- - -\n\n#### Developed by [TerraEclipse](https://github.com/TerraEclipse)\n\nTerra Eclipse, Inc. is a nationally recognized political technology and\nstrategy firm located in Santa Cruz, CA and Washington, D.C.\n"
  },
  {
    "path": "fonts/.gitkeep",
    "content": ""
  },
  {
    "path": "fonts/MaterialIcons/MaterialIcons.css",
    "content": "@font-face {\n  font-family: 'Material Icons';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Material Icons'),\n       local('MaterialIcons-Regular'),\n       url(MaterialIcons-Regular.woff2) format('woff2');\n}\n\n.material-icons {\n  font-family: 'Material Icons';\n  font-weight: normal;\n  font-style: normal;\n  font-size: 24px;  /* Preferred icon size */\n  display: inline-block;\n  width: 1em;\n  height: 1em;\n  line-height: 1;\n  text-transform: none;\n  letter-spacing: normal;\n  word-wrap: normal;\n\n  /* Support for all WebKit browsers. */\n  -webkit-font-smoothing: antialiased;\n  /* Support for Safari and Chrome. */\n  text-rendering: optimizeLegibility;\n\n  /* Support for Firefox. */\n  -moz-osx-font-smoothing: grayscale;\n\n  /* Support for IE. */\n  font-feature-settings: 'liga';\n}"
  },
  {
    "path": "fonts/Roboto/LICENSE.txt",
    "content": "\r\n                                 Apache License\r\n                           Version 2.0, January 2004\r\n                        http://www.apache.org/licenses/\r\n\r\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\r\n\r\n   1. Definitions.\r\n\r\n      \"License\" shall mean the terms and conditions for use, reproduction,\r\n      and distribution as defined by Sections 1 through 9 of this document.\r\n\r\n      \"Licensor\" shall mean the copyright owner or entity authorized by\r\n      the copyright owner that is granting the License.\r\n\r\n      \"Legal Entity\" shall mean the union of the acting entity and all\r\n      other entities that control, are controlled by, or are under common\r\n      control with that entity. For the purposes of this definition,\r\n      \"control\" means (i) the power, direct or indirect, to cause the\r\n      direction or management of such entity, whether by contract or\r\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\r\n      outstanding shares, or (iii) beneficial ownership of such entity.\r\n\r\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\r\n      exercising permissions granted by this License.\r\n\r\n      \"Source\" form shall mean the preferred form for making modifications,\r\n      including but not limited to software source code, documentation\r\n      source, and configuration files.\r\n\r\n      \"Object\" form shall mean any form resulting from mechanical\r\n      transformation or translation of a Source form, including but\r\n      not limited to compiled object code, generated documentation,\r\n      and conversions to other media types.\r\n\r\n      \"Work\" shall mean the work of authorship, whether in Source or\r\n      Object form, made available under the License, as indicated by a\r\n      copyright notice that is included in or attached to the work\r\n      (an example is provided in the Appendix below).\r\n\r\n      \"Derivative Works\" shall mean any work, whether in Source or Object\r\n      form, that is based on (or derived from) the Work and for which the\r\n      editorial revisions, annotations, elaborations, or other modifications\r\n      represent, as a whole, an original work of authorship. For the purposes\r\n      of this License, Derivative Works shall not include works that remain\r\n      separable from, or merely link (or bind by name) to the interfaces of,\r\n      the Work and Derivative Works thereof.\r\n\r\n      \"Contribution\" shall mean any work of authorship, including\r\n      the original version of the Work and any modifications or additions\r\n      to that Work or Derivative Works thereof, that is intentionally\r\n      submitted to Licensor for inclusion in the Work by the copyright owner\r\n      or by an individual or Legal Entity authorized to submit on behalf of\r\n      the copyright owner. For the purposes of this definition, \"submitted\"\r\n      means any form of electronic, verbal, or written communication sent\r\n      to the Licensor or its representatives, including but not limited to\r\n      communication on electronic mailing lists, source code control systems,\r\n      and issue tracking systems that are managed by, or on behalf of, the\r\n      Licensor for the purpose of discussing and improving the Work, but\r\n      excluding communication that is conspicuously marked or otherwise\r\n      designated in writing by the copyright owner as \"Not a Contribution.\"\r\n\r\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\r\n      on behalf of whom a Contribution has been received by Licensor and\r\n      subsequently incorporated within the Work.\r\n\r\n   2. Grant of Copyright License. Subject to the terms and conditions of\r\n      this License, each Contributor hereby grants to You a perpetual,\r\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r\n      copyright license to reproduce, prepare Derivative Works of,\r\n      publicly display, publicly perform, sublicense, and distribute the\r\n      Work and such Derivative Works in Source or Object form.\r\n\r\n   3. Grant of Patent License. Subject to the terms and conditions of\r\n      this License, each Contributor hereby grants to You a perpetual,\r\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r\n      (except as stated in this section) patent license to make, have made,\r\n      use, offer to sell, sell, import, and otherwise transfer the Work,\r\n      where such license applies only to those patent claims licensable\r\n      by such Contributor that are necessarily infringed by their\r\n      Contribution(s) alone or by combination of their Contribution(s)\r\n      with the Work to which such Contribution(s) was submitted. If You\r\n      institute patent litigation against any entity (including a\r\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\r\n      or a Contribution incorporated within the Work constitutes direct\r\n      or contributory patent infringement, then any patent licenses\r\n      granted to You under this License for that Work shall terminate\r\n      as of the date such litigation is filed.\r\n\r\n   4. Redistribution. You may reproduce and distribute copies of the\r\n      Work or Derivative Works thereof in any medium, with or without\r\n      modifications, and in Source or Object form, provided that You\r\n      meet the following conditions:\r\n\r\n      (a) You must give any other recipients of the Work or\r\n          Derivative Works a copy of this License; and\r\n\r\n      (b) You must cause any modified files to carry prominent notices\r\n          stating that You changed the files; and\r\n\r\n      (c) You must retain, in the Source form of any Derivative Works\r\n          that You distribute, all copyright, patent, trademark, and\r\n          attribution notices from the Source form of the Work,\r\n          excluding those notices that do not pertain to any part of\r\n          the Derivative Works; and\r\n\r\n      (d) If the Work includes a \"NOTICE\" text file as part of its\r\n          distribution, then any Derivative Works that You distribute must\r\n          include a readable copy of the attribution notices contained\r\n          within such NOTICE file, excluding those notices that do not\r\n          pertain to any part of the Derivative Works, in at least one\r\n          of the following places: within a NOTICE text file distributed\r\n          as part of the Derivative Works; within the Source form or\r\n          documentation, if provided along with the Derivative Works; or,\r\n          within a display generated by the Derivative Works, if and\r\n          wherever such third-party notices normally appear. The contents\r\n          of the NOTICE file are for informational purposes only and\r\n          do not modify the License. You may add Your own attribution\r\n          notices within Derivative Works that You distribute, alongside\r\n          or as an addendum to the NOTICE text from the Work, provided\r\n          that such additional attribution notices cannot be construed\r\n          as modifying the License.\r\n\r\n      You may add Your own copyright statement to Your modifications and\r\n      may provide additional or different license terms and conditions\r\n      for use, reproduction, or distribution of Your modifications, or\r\n      for any such Derivative Works as a whole, provided Your use,\r\n      reproduction, and distribution of the Work otherwise complies with\r\n      the conditions stated in this License.\r\n\r\n   5. Submission of Contributions. Unless You explicitly state otherwise,\r\n      any Contribution intentionally submitted for inclusion in the Work\r\n      by You to the Licensor shall be under the terms and conditions of\r\n      this License, without any additional terms or conditions.\r\n      Notwithstanding the above, nothing herein shall supersede or modify\r\n      the terms of any separate license agreement you may have executed\r\n      with Licensor regarding such Contributions.\r\n\r\n   6. Trademarks. This License does not grant permission to use the trade\r\n      names, trademarks, service marks, or product names of the Licensor,\r\n      except as required for reasonable and customary use in describing the\r\n      origin of the Work and reproducing the content of the NOTICE file.\r\n\r\n   7. Disclaimer of Warranty. Unless required by applicable law or\r\n      agreed to in writing, Licensor provides the Work (and each\r\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\r\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\r\n      implied, including, without limitation, any warranties or conditions\r\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\r\n      PARTICULAR PURPOSE. You are solely responsible for determining the\r\n      appropriateness of using or redistributing the Work and assume any\r\n      risks associated with Your exercise of permissions under this License.\r\n\r\n   8. Limitation of Liability. In no event and under no legal theory,\r\n      whether in tort (including negligence), contract, or otherwise,\r\n      unless required by applicable law (such as deliberate and grossly\r\n      negligent acts) or agreed to in writing, shall any Contributor be\r\n      liable to You for damages, including any direct, indirect, special,\r\n      incidental, or consequential damages of any character arising as a\r\n      result of this License or out of the use or inability to use the\r\n      Work (including but not limited to damages for loss of goodwill,\r\n      work stoppage, computer failure or malfunction, or any and all\r\n      other commercial damages or losses), even if such Contributor\r\n      has been advised of the possibility of such damages.\r\n\r\n   9. Accepting Warranty or Additional Liability. While redistributing\r\n      the Work or Derivative Works thereof, You may choose to offer,\r\n      and charge a fee for, acceptance of support, warranty, indemnity,\r\n      or other liability obligations and/or rights consistent with this\r\n      License. However, in accepting such obligations, You may act only\r\n      on Your own behalf and on Your sole responsibility, not on behalf\r\n      of any other Contributor, and only if You agree to indemnify,\r\n      defend, and hold each Contributor harmless for any liability\r\n      incurred by, or claims asserted against, such Contributor by reason\r\n      of your accepting any such warranty or additional liability.\r\n\r\n   END OF TERMS AND CONDITIONS\r\n\r\n   APPENDIX: How to apply the Apache License to your work.\r\n\r\n      To apply the Apache License to your work, attach the following\r\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\r\n      replaced with your own identifying information. (Don't include\r\n      the brackets!)  The text should be enclosed in the appropriate\r\n      comment syntax for the file format. We also recommend that a\r\n      file or class name and description of purpose be included on the\r\n      same \"printed page\" as the copyright notice for easier\r\n      identification within third-party archives.\r\n\r\n   Copyright [yyyy] [name of copyright owner]\r\n\r\n   Licensed under the Apache License, Version 2.0 (the \"License\");\r\n   you may not use this file except in compliance with the License.\r\n   You may obtain a copy of the License at\r\n\r\n       http://www.apache.org/licenses/LICENSE-2.0\r\n\r\n   Unless required by applicable law or agreed to in writing, software\r\n   distributed under the License is distributed on an \"AS IS\" BASIS,\r\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n   See the License for the specific language governing permissions and\r\n   limitations under the License.\r\n"
  },
  {
    "path": "fonts/Roboto/Roboto.css",
    "content": "@font-face {\n  font-family: 'Roboto';\n  src: url('Roboto-Regular.ttf') format('truetype');\n  font-weight: normal;\n  font-style: normal;\n}\n@font-face {\n  font-family: 'Roboto';\n  src: url('Roboto-Italic.ttf') format('truetype');\n  font-weight: normal;\n  font-style: italic;\n}\n@font-face {\n  font-family: 'Roboto';\n  src: url('Roboto-Bold.ttf') format('truetype');\n  font-weight: bold;\n  font-style: normal;\n}\n@font-face {\n  font-family: 'Roboto';\n  src: url('Roboto-BoldItalic.ttf') format('truetype');\n  font-weight: bold;\n  font-style: italic;\n}\n@font-face {\n  font-family: 'Roboto';\n  src: url('Roboto-Thin.ttf') format('truetype');\n  font-weight: 200;\n  font-style: normal;\n}\n@font-face {\n  font-family: 'Roboto';\n  src: url('Roboto-ThinItalic.ttf') format('truetype');\n  font-weight: 200;\n  font-style: italic;\n}\n@font-face {\n  font-family: 'Roboto';\n  src: url('Roboto-Light.ttf') format('truetype');\n  font-weight: 100;\n  font-style: normal;\n}\n@font-face {\n  font-family: 'Roboto';\n  src: url('Roboto-LightItalic.ttf') format('truetype');\n  font-weight: 100;\n  font-style: italic;\n}\n@font-face {\n  font-family: 'Roboto';\n  src: url('Roboto-Medium.ttf') format('truetype');\n  font-weight: 300;\n  font-style: normal;\n}\n@font-face {\n  font-family: 'Roboto';\n  src: url('Roboto-MediumItalic.ttf') format('truetype');\n  font-weight: 300;\n  font-style: italic;\n}"
  },
  {
    "path": "images/.gitkeep",
    "content": ""
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>RedisExplorer</title>\n    <link rel=\"stylesheet\" href=\"main.css\"/>\n  </head>\n  <body>\n    <script src=\"main.js\"></script>\n  </body>\n</html>"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"redis-explorer\",\n  \"version\": \"0.0.6\",\n  \"description\": \"A GUI for Redis\",\n  \"main\": \"browser.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/cpsubrian/redis-explorer.git\"\n  },\n  \"homepage\": \"https://github.com/cpsubrian/redis-explorer\",\n  \"keywords\": [\n    \"redis\"\n  ],\n  \"author\": \"Brian Link <cpsubrian@gmail.com>\",\n  \"licenses\": [\n    {\n      \"type\": \"Apache-2.0\",\n      \"url\": \"http://www.apache.org/licenses/LICENSE-2.0.html\"\n    }\n  ],\n  \"bugs\": {\n    \"url\": \"https://github.com/cpsubrian/redis-explorer/issues\"\n  },\n  \"scripts\": {\n    \"test\": \"echo 'No Tests'\"\n  },\n  \"standard\": {\n    \"parser\": \"babel-eslint\"\n  },\n  \"dependencies\": {\n    \"alt\": \"^0.17.1\",\n    \"autobind-decorator\": \"^1.2.0\",\n    \"babel\": \"^5.6.7\",\n    \"highlight.js\": \"^8.6.0\",\n    \"immutable\": \"^3.7.4\",\n    \"material-ui\": \"^0.9.1\",\n    \"mkdirp\": \"^0.5.1\",\n    \"pure-render-decorator\": \"^0.1.0\",\n    \"react\": \"^0.13.3\",\n    \"react-hotkeys\": \"^0.3.3\",\n    \"react-immutable-proptypes\": \"^1.0.0\",\n    \"react-router\": \"^0.13.3\",\n    \"react-tap-event-plugin\": \"^0.1.7\",\n    \"redis\": \"^0.12.1\",\n    \"redis-info\": \"^3.0.6\",\n    \"ssh-config\": \"^0.1.0\",\n    \"tunnel-ssh\": \"^1.0.2\",\n    \"underscore\": \"^1.8.3\"\n  },\n  \"devDependencies\": {\n    \"babel\": \"^5.5.8\",\n    \"babel-eslint\": \"^3.1.19\",\n    \"electron-prebuilt\": \"^0.29.2\",\n    \"grunt\": \"^0.4.5\",\n    \"grunt-babel\": \"^5.0.1\",\n    \"grunt-chmod\": \"^1.0.3\",\n    \"grunt-cli\": \"^0.1.13\",\n    \"grunt-contrib-clean\": \"^0.6.0\",\n    \"grunt-contrib-copy\": \"^0.8.0\",\n    \"grunt-contrib-less\": \"^1.0.1\",\n    \"grunt-contrib-watch-chokidar\": \"^1.0.0\",\n    \"grunt-download-electron\": \"^2.1.1\",\n    \"grunt-electron\": \"^2.0.0\",\n    \"grunt-electron-installer\": \"^0.33.0\",\n    \"grunt-if-missing\": \"^1.0.0\",\n    \"grunt-newer\": \"^1.1.1\",\n    \"grunt-rcedit\": \"^0.3.1\",\n    \"grunt-rename\": \"^0.1.4\",\n    \"grunt-shell-spawn\": \"^0.3.8\",\n    \"load-grunt-tasks\": \"^3.2.0\",\n    \"material-design-icons\": \"^2.0.0\",\n    \"shell-escape\": \"^0.2.0\",\n    \"source-map-support\": \"^0.3.1\",\n    \"standard\": \"^4.3.3\"\n  }\n}\n"
  },
  {
    "path": "resources/.gitkeep",
    "content": ""
  },
  {
    "path": "src/actions/browseActions.js",
    "content": "import alt from '../alt'\nimport db from '../utils/db'\n\n// 'Global' scan object.\nlet _scan\n\n// Browse Actions.\nclass BrowseActions {\n\n  /* General\n   ****************************************************************************/\n  resetKeys () {\n    this.dispatch()\n  }\n\n  setOffset (offset) {\n    this.dispatch(offset)\n  }\n\n  setMatch (match) {\n    this.dispatch(match)\n  }\n\n  toggleSelectedKey (key) {\n    this.dispatch(key)\n  }\n\n  toggleSelectedIndex (index) {\n    this.dispatch(index)\n  }\n\n  // `item` should be {key, index}\n  toggleSelected (item) {\n    this.dispatch(item)\n  }\n\n  /* Fetch Keys Lifecycle\n   ****************************************************************************/\n  fetchKeys (options = {}) {\n    this.dispatch()\n\n    // Stop an active scan.\n    if (_scan) _scan.stop()\n\n    // Create the scan.\n    options.match = options.match ? options.match + '*' : null\n    options.loadTypes = true\n    _scan = db.scan(options)\n\n    // Start the scan.\n    this.actions.fetchKeysNext()\n  }\n\n  fetchKeysNext () {\n    this.dispatch()\n\n    _scan.next((err, keys) => {\n      if (err) {\n        this.actions.fetchKeysFailed(err)\n      } else if (keys) {\n        this.actions.fetchKeysAdd(keys)\n      } else {\n        this.actions.fetchKeysFinished()\n      }\n    })\n  }\n\n  fetchKeysAdd (keys) {\n    this.dispatch(keys)\n    this.actions.fetchValues(keys)\n  }\n\n  fetchKeysFailed (err) {\n    this.dispatch(err)\n  }\n\n  fetchKeysFinished () {\n    this.dispatch()\n  }\n\n  /* Fetch Values Lifecycle\n   ****************************************************************************/\n  fetchValues (keys) {\n    this.dispatch(keys)\n\n    // Start loading the values.\n    db.fetchValues(keys, (err, values) => {\n      if (err) {\n        this.actions.fetchValuesFailed(err)\n      } else {\n        this.actions.fetchValuesAdd(values)\n      }\n    })\n  }\n\n  fetchValuesAdd (values) {\n    this.dispatch(values)\n  }\n\n  fetchValuesFailed (err) {\n    this.dispatch(err)\n  }\n}\n\nexport default alt.createActions(BrowseActions)\n"
  },
  {
    "path": "src/actions/hostsActions.js",
    "content": "import alt from '../alt'\nimport db from '../utils/db'\n\nclass HostsActions {\n\n  /* Connection Lifecycle\n   ****************************************************************************/\n  connectToHost (host) {\n    this.dispatch(host)\n\n    db.connect(host.toJS(), (err) => {\n      if (err) {\n        this.actions.connectToHostFailed(err)\n      } else {\n        this.actions.connectedToHost()\n      }\n    })\n  }\n\n  connectToHostFailed (err) {\n    this.dispatch(err)\n  }\n\n  connectedToHost () {\n    this.dispatch()\n  }\n\n  /* Fetch Info Lifecycle\n   ****************************************************************************/\n  fetchHostInfo (isRefresh) {\n    this.dispatch(isRefresh)\n\n    db.fetchInfo((err, info) => {\n      if (err) {\n        this.actions.fetchHostInfoFailed(err)\n      } else {\n        this.actions.fetchHostInfoFinished(info)\n      }\n    })\n  }\n\n  fetchHostInfoFinished (info) {\n    this.dispatch(info)\n  }\n\n  fetchHostInfoFailed (err) {\n    this.dispatch(err)\n  }\n\n}\n\nexport default alt.createActions(HostsActions)\n"
  },
  {
    "path": "src/alt.js",
    "content": "import Alt from 'alt'\n\n// Create alt instance.\nconst alt = new Alt()\n\n// Debug dispatcher.\nif (process.env.NODE_ENV === 'development') {\n  alt.dispatcher.register((dispatch) => {\n    console.log(dispatch.action, dispatch.data)\n  })\n}\n\nexport default alt\n"
  },
  {
    "path": "src/app.js",
    "content": "import remote from 'remote'\nimport React from 'react'\nimport router from './router'\nimport injectTapEventPlugin from 'react-tap-event-plugin'\n\n// Constants\nconst app = remote.require('app')\nconst Menu = remote.require('menu')\nconst shell = remote.require('shell')\nconst BrowserWindow = remote.require('browser-window')\nconst CMDORCTRL = (process.platform === 'win32') ? 'Ctrl' : 'Command'\n\n// Needed for onTouchTap\n// Can go away when react 1.0 release\n// Check this repo:\n// https://github.com/zilverline/react-tap-event-plugin\ninjectTapEventPlugin()\n\n// Run the router.\nrouter.run((Handler) => {\n  React.render(<Handler/>, document.body)\n})\n\n// Application Menu\nMenu.setApplicationMenu(Menu.buildFromTemplate([\n  {\n    label: 'RedisExplorer',\n    submenu: [\n      {\n        label: 'About RedisExplorer',\n        selector: 'orderFrontStandardAboutPanel:'\n      },\n      {\n        type: 'separator'\n      }, {\n        label: 'Hide RedisExplorer',\n        accelerator: CMDORCTRL + '+H',\n        selector: 'hide:'\n      },\n      {\n        label: 'Hide Others',\n        accelerator: CMDORCTRL + '+Shift+H',\n        selector: 'hideOtherApplications:'\n      },\n      {\n        label: 'Show All',\n        selector: 'unhideAllApplications:'\n      },\n      {\n        type: 'separator'\n      },\n      {\n        label: 'Quit',\n        accelerator: CMDORCTRL + '+Q',\n        click: () => app.quit()\n      }\n    ]\n  },\n  {\n    label: 'Edit',\n    submenu: [\n      {\n        label: 'Undo',\n        accelerator: CMDORCTRL + '+Z',\n        selector: 'undo:'\n      },\n      {\n        label: 'Redo',\n        accelerator: 'Shift+' + CMDORCTRL + '+Z',\n        selector: 'redo:'\n      },\n      {\n        type: 'separator'\n      },\n      {\n        label: 'Cut',\n        accelerator: CMDORCTRL + '+X',\n        selector: 'cut:'\n      },\n      {\n        label: 'Copy',\n        accelerator: CMDORCTRL + '+C',\n        selector: 'copy:'\n      },\n      {\n        label: 'Paste',\n        accelerator: CMDORCTRL + '+V',\n        selector: 'paste:'\n      },\n      {\n        label: 'Select All',\n        accelerator: CMDORCTRL + '+A',\n        selector: 'selectAll:'\n      }\n    ]\n  },\n  {\n    label: 'View',\n    submenu: [\n      {\n        label: 'Reload',\n        accelerator: CMDORCTRL + '+R',\n        click: () => {\n          var focusedWindow = BrowserWindow.getFocusedWindow()\n          if (focusedWindow) {\n            focusedWindow.reload()\n          }\n        }\n      },\n      {\n        label: 'Toggle Full Screen',\n        accelerator: 'Ctrl+Command+F',\n        click: () => {\n          var focusedWindow = BrowserWindow.getFocusedWindow()\n          if (focusedWindow) {\n            focusedWindow.setFullScreen(!focusedWindow.isFullScreen())\n          }\n        }\n      },\n      {\n        label: 'Toggle DevTools',\n        accelerator: 'Alt+' + CMDORCTRL + '+I',\n        click: () => remote.getCurrentWindow().toggleDevTools()\n      }\n    ]\n  },\n  {\n    label: 'Window',\n    submenu: [\n    {\n      label: 'Minimize',\n      accelerator: CMDORCTRL + '+M',\n      selector: 'performMiniaturize:'\n    },\n    {\n      label: 'Close',\n      accelerator: CMDORCTRL + '+W',\n      click: () => remote.getCurrentWindow().hide()\n    },\n    {\n      type: 'separator'\n    },\n    {\n      label: 'Bring All to Front',\n      selector: 'arrangeInFront:'\n    }\n    ]\n  },\n  {\n    label: 'Help',\n    submenu: [\n      {\n        label: 'Report Issue or Suggest Feedback',\n        click: () => shell.openExternal('https://github.com/cpsubrian/redis-explorer/issues/new')\n      }\n    ]\n  }\n]))\n"
  },
  {
    "path": "src/browser.js",
    "content": "import app from 'app'\nimport conf from './conf'\nimport settings from './utils/settings'\nimport BrowserWindow from 'browser-window'\n\n// Report crashes to our server.\n// require('crash-reporter').start()\n\n// Keep a reference of the window object, if you don't, the window will\n// be closed automatically when the javascript object is GCed.\nlet mainWindow = null\n\n// Quit when all windows are closed.\napp.on('window-all-closed', () => {\n  if (process.platform !== 'darwin') {\n    app.quit()\n  }\n})\n\n// Get save bounds or use fallback defaults.\nlet bounds = settings.get('bounds', {\n  x: 50,\n  y: 50,\n  width: 1100,\n  height: 800\n})\n\n// This method will be called when Electron has done everything\n// initialization and ready for creating browser windows.\napp.on('ready', () => {\n  // Create the browser window.\n  mainWindow = new BrowserWindow({\n    'x': bounds.x,\n    'y': bounds.y,\n    'width': bounds.width,\n    'height': bounds.height,\n    'min-width': 400,\n    'min-height': 260,\n    'standard-window': false,\n    'resizable': true,\n    'show': false\n  })\n\n  // Open dev tools. Probably should remove this when this is more production ready.\n  if (process.env.NODE_ENV === 'development') {\n    mainWindow.openDevTools()\n  }\n\n  // Load the index.html of the app.\n  mainWindow.loadUrl('file://' + __dirname + '/index.html')\n\n  // Show window after the url is loaded.\n  mainWindow.webContents.on('did-finish-load', () => {\n    mainWindow.setTitle(conf['BASENAME'])\n    mainWindow.show()\n    mainWindow.focus()\n  })\n\n  // Don't allow navigation to weird endpoints.\n  mainWindow.webContents.on('will-navigate', (e, url) => {\n    if (url.indexOf('build/index.html#') < 0) {\n      e.preventDefault()\n    }\n  })\n\n  // Save size and position of window.\n  mainWindow.on('resize', () => {\n    settings.set('bounds', mainWindow.getBounds())\n  })\n  mainWindow.on('move', () => {\n    settings.set('bounds', mainWindow.getBounds())\n  })\n\n  // Handle window close depending on platform.\n  app.on('window-all-closed', () => {\n    app.quit()\n  })\n})\n"
  },
  {
    "path": "src/components/Header.js",
    "content": "import React from 'react'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport autobind from 'autobind-decorator'\nimport {Link} from 'react-router'\nimport Icon from '../components/Icon'\nimport {Toolbar, ToolbarGroup} from 'material-ui'\nimport HostsNav from '../components/HostsNav'\n\n// @todo add pureRender after I figure out how to account for Router state\n// for <Link> changes.\n@autobind\nclass Header extends React.Component {\n\n  static propTypes = {\n    hosts: ImmutablePropTypes.list,\n    activeHost: ImmutablePropTypes.map,\n    connecting: React.PropTypes.bool,\n    connected: React.PropTypes.bool\n  }\n\n  onClickHostButton (e) {\n    e.preventDefault()\n    this.refs.hostsNav.toggle()\n  }\n\n  getHostButtonClass () {\n    if (this.props.connecting) {\n      return 'connecting'\n    } else if (this.props.connected) {\n      return 'connected'\n    } else {\n      return 'disconnected'\n    }\n  }\n\n  render () {\n    return (\n      <div className='header'>\n        <Toolbar className='toolbar'>\n          <ToolbarGroup key={0} float='left'>\n            <Link to='/' className='button'>\n              <Icon type='search'/> Browse\n            </Link>\n            <Link to='/info' className='button'>\n              <Icon type='info'/> Info\n            </Link>\n          </ToolbarGroup>\n          <ToolbarGroup key={1} float='right' className='no-drag'>\n            {this.props.activeHost ?\n              <Link to='/hosts' className={'button host-button ' + this.getHostButtonClass()} onClick={this.onClickHostButton}>\n                {this.props.activeHost.get('Host')} <Icon type='public'/>\n              </Link>\n            :/*else*/\n              <span>Not Connected</span>\n            }\n            <HostsNav ref='hostsNav' {...this.props}/>\n          </ToolbarGroup>\n        </Toolbar>\n      </div>\n    )\n  }\n}\n\nexport default Header\n"
  },
  {
    "path": "src/components/Highlight.js",
    "content": "import React from 'react'\nimport autobind from 'autobind-decorator'\nimport hljs from 'highlight.js'\n\n@autobind\nclass Highlight extends React.Component {\n\n  static propTypes = {\n    className: React.PropTypes.string,\n    innerHTML: React.PropTypes.bool,\n    children: React.PropTypes.node\n  }\n\n  static defaultProps = {\n    innerHTML: false,\n    className: ''\n  }\n\n  componentDidMount () {\n    this.highlightCode()\n  }\n\n  componentDidUpdate () {\n    this.highlightCode()\n  }\n\n  highlightCode () {\n    let nodes = React.findDOMNode(this).querySelectorAll('pre code')\n    if (nodes.length > 0) {\n      for (let i = 0; i < nodes.length; i = i + 1) {\n        hljs.highlightBlock(nodes[i])\n      }\n    }\n  }\n\n  render () {\n    if (this.props.innerHTML) {\n      return <div dangerouslySetInnerHTML={{__html: this.props.children}} className={this.props.className || null}></div>\n    } else {\n      return <pre><code className={this.props.className}>{this.props.children}</code></pre>\n    }\n  }\n}\n\nexport default Highlight\n"
  },
  {
    "path": "src/components/HostInfo.js",
    "content": "import React from 'react'\nimport pureRender from 'pure-render-decorator'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport {Paper} from 'material-ui'\n\n@pureRender\nclass HostInfo extends React.Component {\n\n  static propTypes = {\n    hostInfo: ImmutablePropTypes.map\n  }\n\n  static infoGroups = [\n    {\n      title: 'Server',\n      props: [\n        'redis_version',\n        'redis_git_sha1',\n        'redis_git_dirty',\n        'redis_build_id',\n        'redis_mode',\n        'os',\n        'arch_bits',\n        'multiplexing_api',\n        'gcc_version',\n        'process_id',\n        // 'run_id',\n        'tcp_port',\n        'uptime_in_seconds',\n        'uptime_in_days',\n        'hz',\n        'lru_clock',\n        'config_file'\n      ]\n    }, {\n      title: 'Clients',\n      props: [\n        'connected_clients',\n        'client_longest_output_list',\n        'client_biggest_input_buf',\n        'blocked_clients'\n      ]\n    }, {\n      title: 'Memory',\n      props: [\n        'used_memory',\n        'used_memory_human',\n        'used_memory_rss',\n        'used_memory_peak',\n        'used_memory_peak_human',\n        'used_memory_lua',\n        'mem_fragmentation_ratio',\n        'mem_allocator'\n      ]\n    }, {\n      title: 'Persistence',\n      props: [\n        'loading',\n        'rdb_changes_since_last_save',\n        'rdb_bgsave_in_progress',\n        'rdb_last_save_time',\n        'rdb_last_bgsave_status',\n        'rdb_last_bgsave_time_sec',\n        'rdb_current_bgsave_time_sec',\n        'aof_enabled',\n        'aof_rewrite_in_progress',\n        'aof_rewrite_scheduled',\n        'aof_last_rewrite_time_sec',\n        'aof_current_rewrite_time_sec',\n        'aof_last_bgrewrite_status',\n        'aof_last_write_status'\n      ]\n    }, {\n      title: 'Stats',\n      props: [\n        'total_connections_received',\n        'total_commands_processed',\n        'instantaneous_ops_per_sec',\n        'total_net_input_bytes',\n        'total_net_output_bytes',\n        'instantaneous_input_kbps',\n        'instantaneous_output_kbps',\n        'rejected_connections',\n        'sync_full',\n        'sync_partial_ok',\n        'sync_partial_err',\n        'expired_keys',\n        'evicted_keys',\n        'keyspace_hits',\n        'keyspace_misses',\n        'pubsub_channels',\n        'pubsub_patterns',\n        'latest_fork_usec'\n      ]\n    }, {\n      title: 'Replication',\n      props: [\n        'role',\n        'connected_slaves',\n        'master_repl_offset',\n        'repl_backlog_active',\n        'repl_backlog_size',\n        'repl_backlog_first_byte_offset',\n        'repl_backlog_histlen:0'\n      ]\n    }, {\n      title: 'CPU',\n      props: [\n        'used_cpu_sys',\n        'used_cpu_user',\n        'used_cpu_sys_children',\n        'used_cpu_user_children'\n      ]\n    }, {\n      title: 'Keyspace',\n      getInfo () {\n        let databases = this.props.hostInfo.get('databases')\n        if (databases) {\n          let dbs = []\n          let rows = []\n          for (let i = 0; i <= 16; i++) {\n            if (databases[i]) {\n              databases[i].num = i\n              dbs.push(databases[i])\n            }\n          }\n          dbs.forEach((db) => {\n            rows.push(\n              <tr key={'db-' + db.num + '-label'}>\n                <td className='prop' colSpan='2'>\n                  <strong>db{db.num}</strong>\n                </td>\n              </tr>\n            )\n            rows.push(\n              <tr key={'db-' + db.num + '-keys'}>\n                <td className='prop'>keys</td>\n                <td className='value'>{db.keys}</td>\n              </tr>\n            )\n            rows.push(\n              <tr key={'db-' + db.num + '-expires'}>\n                <td className='prop'>expires</td>\n                <td className='value'>{db.expires}</td>\n              </tr>\n            )\n          })\n          return (\n            <table>\n              {rows}\n            </table>\n          )\n        } else {\n          return null\n        }\n      }\n    }\n  ]\n\n  renderGroup (group) {\n    return (\n      <Paper key={'group-' + group.title} className='host-info-group'>\n        <h3>{group.title}</h3>\n        {group.getInfo ?\n          group.getInfo.call(this)\n        :/*else*/\n          <table>\n          {group.props.map((prop) => {\n            return (\n              <tr key={'prop-' + prop}>\n                <td className='prop'>{prop}</td>\n                <td className='value'>{this.props.hostInfo.get(prop)}</td>\n              </tr>\n            )\n          })}\n          </table>\n        }\n      </Paper>\n    )\n  }\n\n  render () {\n    return (\n      <div className='host-info'>\n        {HostInfo.infoGroups.map((group) => {\n          return this.renderGroup(group)\n        })}\n      </div>\n    )\n  }\n}\n\nexport default HostInfo\n"
  },
  {
    "path": "src/components/HostsNav.js",
    "content": "import React from 'react'\nimport autobind from 'autobind-decorator'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport hostsActions from '../actions/hostsActions'\nimport browseActions from '../actions/browseActions'\nimport Icon from '../components/Icon'\nimport {LeftNav, MenuItem} from 'material-ui'\n\n@autobind\nclass HostsNav extends React.Component {\n\n  static propTypes = {\n    hosts: ImmutablePropTypes.list,\n    activeHost: ImmutablePropTypes.map\n  }\n\n  shouldComponentUpdate (nextProps, nextState) {\n    return !(\n      nextProps.hosts === this.props.hosts &&\n      nextProps.activeHost.get('Host') === this.props.activeHost.get('Host')\n    )\n  }\n\n  toggle () {\n    this.refs.nav.toggle()\n  }\n\n  onChangeHost (e, i, menuItem) {\n    if (menuItem.host.get('Host') !== this.props.activeHost.get('Host')) {\n      browseActions.resetKeys()\n      hostsActions.connectToHost(menuItem.host)\n    }\n  }\n\n  render () {\n    let selectedIndex\n    let menuItems\n\n    menuItems = this.props.hosts.map((host, i) => {\n      if (this.props.activeHost === host) {\n        selectedIndex = i\n      }\n      return {\n        host: host,\n        text: <span className='host-option'><Icon type='public'/> {host.get('Host')}</span>\n      }\n    }).toArray()\n\n    // Splice in subheaders.\n    menuItems.splice(0, 0, {type: MenuItem.Types.SUBHEADER, text: 'Local' })\n    menuItems.splice(2, 0, {type: MenuItem.Types.SUBHEADER, text: 'Remote' })\n\n    // Adjust selected index.\n    if (selectedIndex === 0) {\n      selectedIndex = 1\n    } else {\n      selectedIndex += 2\n    }\n\n    return (\n      <LeftNav ref='nav'\n        docked={false}\n        openRight={true}\n        onChange={this.onChangeHost}\n        selectedIndex={selectedIndex}\n        menuItems={menuItems} />\n    )\n  }\n}\n\nexport default HostsNav\n"
  },
  {
    "path": "src/components/Icon.js",
    "content": "import React from 'react'\n\nclass Icon extends React.Component {\n\n  static propTypes = {\n    type: React.PropTypes.string.isRequired,\n    style: React.PropTypes.object,\n    title: React.PropTypes.string,\n    className: React.PropTypes.string\n  }\n\n  static defaultProps = {\n    style: {}\n  }\n\n  render () {\n    return (\n      <i className={'icon material-icons ' + (this.props.className || '')}\n         style={this.props.style}\n         title={this.props.title}\n      >{this.props.type}</i>\n    )\n  }\n}\n\nexport default Icon\n"
  },
  {
    "path": "src/components/KeyDetails.js",
    "content": "import React from 'react'\nimport pureRender from 'pure-render-decorator'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport HashDetails from '../components/details/HashDetails'\nimport ListDetails from '../components/details/ListDetails'\nimport SetDetails from '../components/details/SetDetails'\nimport SortedSetDetails from '../components/details/SortedSetDetails'\nimport StringDetails from '../components/details/StringDetails'\n\n@pureRender\nclass KeyDetails extends React.Component {\n\n  static propTypes = {\n    item: ImmutablePropTypes.map\n  }\n\n  render () {\n    switch (this.props.item.get('type')) {\n      case 'string':\n        return <StringDetails {...this.props}/>\n      case 'list':\n        return <ListDetails {...this.props}/>\n      case 'set':\n        return <SetDetails {...this.props}/>\n      case 'zset':\n        return <SortedSetDetails {...this.props}/>\n      case 'hash':\n        return <HashDetails {...this.props}/>\n    }\n  }\n}\n\nexport default KeyDetails\n"
  },
  {
    "path": "src/components/LoadingRow.js",
    "content": "import React from 'react'\n\nclass LoadingRow extends React.Component {\n\n  static propTypes = {\n    cols: React.PropTypes.number\n  }\n\n  static defaultProps = {\n    cols: 1\n  }\n\n  render () {\n    return (\n      <tr className='loading-row'>\n        <td colSpan={this.props.cols}>\n          Loading&hellip;\n        </td>\n      </tr>\n    )\n  }\n}\n\nexport default LoadingRow\n"
  },
  {
    "path": "src/components/ScrollList.js",
    "content": "import React from 'react'\nimport pureRender from 'pure-render-decorator'\nimport autobind from 'autobind-decorator'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\n\n@pureRender\n@autobind\nclass ScrollList extends React.Component {\n\n  static propTypes = {\n    items: ImmutablePropTypes.iterable,\n    itemHeight: React.PropTypes.number.isRequired,\n    renderRoot: React.PropTypes.func,\n    renderItem: React.PropTypes.func,\n    renderPlaceholder: React.PropTypes.func,\n    offset: React.PropTypes.number\n  }\n\n  static defaultProps = {\n    offset: 0,\n    limit: 60,\n    renderRoot: (props, children) => {\n      return (\n        <ul {...props}>\n          {children}\n        </ul>\n      )\n    },\n    renderItem: (props, item) => {\n      return (\n        <li {...props}>\n          {item}\n        </li>\n      )\n    },\n    renderPlaceholder: (props) => {\n      return <li {...props}></li>\n    }\n  }\n\n  state = {\n    offset: this.props.offset\n  }\n\n  componentDidMount () {\n    React.findDOMNode(this).scrollTop = this.state.offset * this.props.itemHeight\n  }\n\n  onScroll (e) {\n    let top = e.currentTarget.scrollTop\n    let count = Math.floor(top / this.props.itemHeight)\n    let newOffset\n\n    if (count !== this.state.offset) {\n      newOffset = count\n      this.setState({offset: newOffset})\n    }\n    if (this.props.scrollHandler) {\n      this.props.scrollHandler(e, newOffset)\n    }\n  }\n\n  getDisplayOffset () {\n    let offset = this.state.offset - Math.floor(this.props.limit / 3)\n    return (offset >= 0) ? offset : 0\n  }\n\n  ensureVisible (index) {\n    let container = React.findDOMNode(this)\n    let itemTop = index * this.props.itemHeight\n    let top = container.scrollTop\n    let height = container.offsetHeight\n    let count = Math.floor(height / this.props.itemHeight)\n\n    if (itemTop < top) {\n      container.scrollTop = (index - count + 2) * this.props.itemHeight\n    }\n    if ((itemTop + this.props.itemHeight) > (top + height - this.props.itemHeight)) {\n      container.scrollTop = index * this.props.itemHeight\n    }\n  }\n\n  renderItems () {\n    let offset = this.getDisplayOffset()\n    let slice = this.props.items.slice(offset, offset + this.props.limit)\n    let results = []\n    let baseStyles = {\n          visibility: 'hidden !important',\n          lineHeight: '0 !important',\n          padding: '0 !important',\n          margin: '0 !important',\n          border: 'none !important',\n          fontSize: '0 !important'\n        }\n\n    // Add top placeholder.\n    results.push(this.props.renderPlaceholder({\n      key: 'scroll-list-placeholder-top',\n      style: Object.assign({\n        height: (offset * this.props.itemHeight) + 'px'\n      }, baseStyles)\n    }))\n\n    // Add currently visible items.\n    let index = offset\n    slice.forEach((item, key) => {\n      results.push(this.props.renderItem({key}, item, index++))\n    })\n\n    // Add bottom placeholder.\n    results.push(this.props.renderPlaceholder({\n      key: 'scroll-list-placeholder-bottom',\n      style: Object.assign({\n        height: ((this.props.items.size - offset - slice.size) * this.props.itemHeight) + 'px'\n      }, baseStyles)\n    }))\n\n    return results\n  }\n\n  render () {\n    return this.props.renderRoot({\n      style: {\n        overflow: 'auto'\n      },\n      onScroll: this.onScroll\n    }, this.renderItems())\n  }\n}\n\nexport default ScrollList\n"
  },
  {
    "path": "src/components/SidePanel.js",
    "content": "import React from'react'\nimport KeyCode from'material-ui/lib/utils/key-code'\nimport StylePropable from'material-ui/lib/mixins/style-propable'\nimport AutoPrefix from'material-ui/lib/styles/auto-prefix'\nimport Transitions from'material-ui/lib/styles/transitions'\nimport WindowListenable from'material-ui/lib/mixins/window-listenable'\nimport Overlay from'material-ui/lib/overlay'\nimport Paper from'material-ui/lib/paper'\n\n/**\n * Based on material-ui LeftNav component.\n */\nconst SidePanel = React.createClass({\n\n  mixins: [StylePropable, WindowListenable],\n\n  contextTypes: {\n    muiTheme: React.PropTypes.object\n  },\n\n  propTypes: {\n    className: React.PropTypes.string,\n    docked: React.PropTypes.bool,\n    onOpen: React.PropTypes.func,\n    onClose: React.PropTypes.func,\n    openRight: React.PropTypes.bool,\n    width: React.PropTypes.number,\n    style: React.PropTypes.object,\n    children: React.PropTypes.node\n  },\n\n  windowListeners: {\n    'keyup': '_onWindowKeyUp'\n  },\n\n  getDefaultProps () {\n    return {\n      docked: true\n    }\n  },\n\n  getInitialState () {\n    return {\n      open: this.props.docked,\n      maybeSwiping: false,\n      swiping: false\n    }\n  },\n\n  componentDidMount () {\n    this._enableSwipeHandling()\n  },\n\n  componentDidUpdate (prevProps, prevState) {\n    this._enableSwipeHandling()\n  },\n\n  componentWillUnmount () {\n    this._disableSwipeHandling()\n  },\n\n  toggle () {\n    this.setState({ open: !this.state.open })\n    return this\n  },\n\n  close () {\n    this.setState({ open: false })\n    if (this.props.onClose) this.props.onClose()\n    return this\n  },\n\n  open () {\n    this.setState({ open: true })\n    if (this.props.onOpen) this.props.onOpen()\n    return this\n  },\n\n  getThemePalette () {\n    return this.context.muiTheme.palette\n  },\n\n  getTheme () {\n    return this.context.muiTheme.component.leftNav\n  },\n\n  getWidth () {\n    return this.props.width || this.getTheme().width\n  },\n\n  getStyles () {\n    var x = this._getTranslateMultiplier() * (this.state.open ? 0 : this._getMaxTranslateX()) + 'px'\n    var styles = {\n      root: {\n        height: '100%',\n        width: this.getWidth(),\n        position: 'fixed',\n        zIndex: 10,\n        left: 0,\n        top: 0,\n        transform: 'translate3d(' + x + ', 0, 0)',\n        transition: !this.state.swiping && Transitions.easeOut(),\n        backgroundColor: this.getTheme().color,\n        overflow: 'hidden'\n      },\n      rootWhenOpenRight: {\n        left: 'auto',\n        right: '0'\n      }\n    }\n    return styles\n  },\n\n  render () {\n    var overlay\n\n    var styles = this.getStyles()\n    if (!this.props.docked) {\n      overlay = (\n        <Overlay\n          ref='overlay'\n          show={this.state.open}\n          transitionEnabled={!this.state.swiping}\n          onTouchTap={this._onOverlayTouchTap}/>\n      )\n    }\n\n    return (\n      <div className={this.props.className}>\n        {overlay}\n        <Paper\n          ref='clickAwayableElement'\n          className='side-panel'\n          zDepth={2}\n          rounded={false}\n          transitionEnabled={!this.state.swiping}\n          style={this.mergeAndPrefix(\n            styles.root,\n            this.props.openRight && styles.rootWhenOpenRight,\n            this.props.style)}>\n          {this.props.children}\n        </Paper>\n      </div>\n    )\n  },\n\n  _onOverlayTouchTap () {\n    this.close()\n  },\n\n  _onWindowKeyUp (e) {\n    if (e.keyCode === KeyCode.ESC &&\n        !this.props.docked &&\n        this.state.open) {\n      this.close()\n    }\n  },\n\n  _getMaxTranslateX () {\n    return this.getWidth() + 10\n  },\n\n  _getTranslateMultiplier () {\n    return this.props.openRight ? 1 : -1\n  },\n\n  _enableSwipeHandling () {\n    if (this.state.open && !this.props.docked) {\n      document.body.addEventListener('touchstart', this._onBodyTouchStart)\n    } else {\n      this._disableSwipeHandling()\n    }\n  },\n\n  _disableSwipeHandling () {\n    document.body.removeEventListener('touchstart', this._onBodyTouchStart)\n  },\n\n  _onBodyTouchStart (e) {\n    var touchStartX = e.touches[0].pageX\n    var touchStartY = e.touches[0].pageY\n    this.setState({\n      maybeSwiping: true,\n      touchStartX: touchStartX,\n      touchStartY: touchStartY\n    })\n\n    document.body.addEventListener('touchmove', this._onBodyTouchMove)\n    document.body.addEventListener('touchend', this._onBodyTouchEnd)\n    document.body.addEventListener('touchcancel', this._onBodyTouchEnd)\n  },\n\n  _onBodyTouchMove (e) {\n    var currentX = e.touches[0].pageX\n    var currentY = e.touches[0].pageY\n\n    if (this.state.swiping) {\n      e.preventDefault()\n      var translateX = Math.min(\n        Math.max(\n          this._getTranslateMultiplier() * (currentX - this.state.swipeStartX),\n          0\n        ),\n        this._getMaxTranslateX()\n      )\n\n      var leftNav = React.findDOMNode(this.refs.clickAwayableElement)\n      leftNav.style[AutoPrefix.single('transform')] =\n        'translate3d(' + (this._getTranslateMultiplier() * translateX) + 'px, 0, 0)'\n      this.refs.overlay.setOpacity(1 - translateX / this._getMaxTranslateX())\n    } else if (this.state.maybeSwiping) {\n      var dXAbs = Math.abs(currentX - this.state.touchStartX)\n      var dYAbs = Math.abs(currentY - this.state.touchStartY)\n      // If the user has moved his thumb ten pixels in either direction,\n      // we can safely make an assumption about whether he was intending\n      // to swipe or scroll.\n      var threshold = 10\n\n      if (dXAbs > threshold && dYAbs <= threshold) {\n        this.setState({\n          swiping: true,\n          swipeStartX: currentX\n        })\n      } else if (dXAbs <= threshold && dYAbs > threshold) {\n        this._onBodyTouchEnd()\n      }\n    }\n  },\n\n  _onBodyTouchEnd () {\n    var shouldClose = false\n\n    if (this.state.swiping) shouldClose = true\n\n    this.setState({\n      maybeSwiping: false,\n      swiping: false\n    })\n\n    // We have to call close() after setting swiping to false,\n    // because only then CSS transition is enabled.\n    if (shouldClose) this.close()\n\n    document.body.removeEventListener('touchmove', this._onBodyTouchMove)\n    document.body.removeEventListener('touchend', this._onBodyTouchEnd)\n    document.body.removeEventListener('touchcancel', this._onBodyTouchEnd)\n  }\n})\n\nexport default SidePanel\n"
  },
  {
    "path": "src/components/TypeIcon.js",
    "content": "import React from 'react'\nimport Icon from '../components/Icon'\nimport keys from '../utils/keys'\n\nclass TypeIcon extends React.Component {\n\n  static propTypes = {\n    title: React.PropTypes.string,\n    type: React.PropTypes.string\n  }\n\n  render () {\n    let props = {\n      title: keys.getTypeName(this.props.type)\n    }\n    if (this.props.type === 'string') {\n      props.type = 'vpn_key'\n    }\n    if (this.props.type === 'list') {\n      props.type = 'format_list_numbered'\n    }\n    if (this.props.type === 'set') {\n      props.type = 'reorder'\n    }\n    if (this.props.type === 'zset') {\n      props.type = 'format_line_spacing'\n    }\n    if (this.props.type === 'hash') {\n      props.type = 'code'\n    }\n    return <Icon className='type-icon' {...props}/>\n  }\n}\n\nexport default TypeIcon\n"
  },
  {
    "path": "src/components/ValuesRow.js",
    "content": "import React from 'react'\nimport pureRender from 'pure-render-decorator'\nimport autobind from 'autobind-decorator'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport browseActions from '../actions/browseActions'\nimport TypeIcon from '../components/TypeIcon'\n\n@pureRender\n@autobind\nclass ValuesRow extends React.Component {\n\n  static propTypes = {\n    index: React.PropTypes.number,\n    style: React.PropTypes.object,\n    matchRegExp: React.PropTypes.object,\n    item: ImmutablePropTypes.map // @todo Enforce more specific shape here and elsewhere\n  }\n\n  onClick (e) {\n    browseActions.toggleSelected({\n      key: this.props.item.get('key'),\n      index: this.props.index\n    })\n  }\n\n  renderKey () {\n    let key = this.props.item.get('key')\n\n    // Highlight search pattern matches.\n    if (this.props.matchRegExp) {\n      return key\n        .split(this.props.matchRegExp)\n        .map((part, i) => {\n          if (i > 0 && i % 2) {\n            return <strong key={i}>{part}</strong>\n          } else {\n            return part\n          }\n        })\n    // Else, just spit out the key.\n    } else {\n      return key\n    }\n  }\n\n  renderValue () {\n    let type = this.props.item.get('type')\n    let value = this.props.item.get('value')\n\n    if (value) {\n      let teaser\n      let parts = []\n      let length = 0\n\n      switch (type) {\n        case 'list':\n        case 'set':\n          for (let i = 0; (length <= 400 && i < value.length); i++) {\n            parts.push(value[i])\n            length += value[i].length\n          }\n          teaser = parts.join(', ')\n          break\n        case 'zset':\n          for (let i = 0; (length <= 400 && i < value.length); i++) {\n            parts.push(value[i].value)\n            length += value[i].value.length\n          }\n          teaser = parts.join(', ')\n          break\n        default:\n          teaser = value\n          break\n      }\n      return teaser ? teaser.substr(0, 400) : null\n    } else {\n      return null\n    }\n  }\n\n  render () {\n    let {selected, type} = this.props.item.toJS()\n    let classes = 'values-row' + (selected ? ' selected' : '')\n    return (\n      <tr className={classes} style={this.props.style} onClick={this.onClick}>\n        <td className='key'>{this.renderKey()}</td>\n        <td className='value'>{this.renderValue()}</td>\n        <td className='type'><TypeIcon type={type}/></td>\n      </tr>\n    )\n  }\n}\n\nexport default ValuesRow\n"
  },
  {
    "path": "src/components/ValuesTable.js",
    "content": "import React from 'react'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport pureRender from 'pure-render-decorator'\nimport autobind from 'autobind-decorator'\nimport shell from 'shell'\nimport browseActions from '../actions/browseActions'\n\nimport {TextField} from 'material-ui'\nimport {HotKeys} from 'react-hotkeys'\nimport ScrollList from '../components/ScrollList'\nimport LoadingRow from '../components/LoadingRow'\nimport ValuesRow from '../components/ValuesRow'\n\n@pureRender\n@autobind\nclass ValuesTable extends React.Component {\n\n  static propTypes = {\n    keys: ImmutablePropTypes.orderedMap.isRequired,\n    loading: React.PropTypes.bool,\n    finished: React.PropTypes.bool,\n    offset: React.PropTypes.number,\n    itemHeight: React.PropTypes.number,\n    match: React.PropTypes.string,\n    matchRegExp: React.PropTypes.object,\n    selectedIndex: React.PropTypes.number\n  }\n\n  static defaultProps = {\n    loading: false,\n    offset: 0,\n    itemHeight: 30\n  }\n\n  componentDidUpdate (prevProps) {\n    if (this.refs.scrollList &&\n        (this.props.selectedIndex !== null) &&\n        (prevProps.selectedIndex !== this.props.selectedIndex)) {\n      this.refs.scrollList.ensureVisible(this.props.selectedIndex)\n    }\n  }\n\n  getHotKeys () {\n    return {\n      down: (event) => {\n        if ((this.props.selectedIndex !== null) && this.props.selectedIndex < (this.props.keys.size - 1)) {\n          browseActions.toggleSelectedIndex(this.props.selectedIndex + 1)\n        } else {\n          shell.beep()\n        }\n      },\n      up: (event) => {\n        if ((this.props.selectedIndex !== null) && this.props.selectedIndex > 0) {\n          browseActions.toggleSelectedIndex(this.props.selectedIndex - 1)\n        } else {\n          shell.beep()\n        }\n      },\n      esc: (event) => {\n        if (this.props.selectedIndex !== null) {\n          browseActions.toggleSelected()\n        } else {\n          shell.beep()\n        }\n      }\n    }\n  }\n\n  onSearchChange (e) {\n    browseActions.setMatch(e.currentTarget.value)\n  }\n\n  onScroll (e, newOffset) {\n    if (newOffset) {\n      browseActions.setOffset(newOffset)\n\n      if (!this.props.finished) {\n        let top = e.currentTarget.scrollTop\n        let sh = e.currentTarget.scrollHeight\n        let h = e.currentTarget.offsetHeight\n\n        // If we're nearing the bottom, load more keys.\n        if ((sh - top - h) <= (h * 2)) {\n          browseActions.fetchKeysNext()\n        }\n      }\n    }\n  }\n\n  renderRoot (props, children) {\n    return <tbody {...props}>{children}</tbody>\n  }\n\n  renderItem (props, item, index) {\n    return <ValuesRow matchRegExp={this.props.matchRegExp} item={item} index={index} {...props}/>\n  }\n\n  renderPlaceholder (props) {\n    return <tr key={props.key}><td colSpan='2' {...props}></td></tr>\n  }\n\n  render () {\n    return (\n      <HotKeys handlers={this.getHotKeys()}>\n        <div className='values-table'>\n          <table>\n            <thead>\n              <tr>\n                <th className='key' colSpan='3'>\n                  <TextField\n                    className='search'\n                    hintText='key:*:pattern'\n                    floatingLabelText='Search'\n                    value={this.props.match}\n                    onChange={this.onSearchChange}\n                    fullWidth />\n                </th>\n              </tr>\n            </thead>\n            {this.props.loading ?\n              <tbody>\n                <LoadingRow/>\n              </tbody>\n            :/*else*/\n              <ScrollList\n                ref='scrollList'\n                renderRoot={this.renderRoot}\n                renderItem={this.renderItem}\n                renderPlaceholder={this.renderPlaceholder}\n                items={this.props.keys}\n                itemHeight={this.props.itemHeight}\n                offset={this.props.offset}\n                scrollHandler={this.onScroll} />\n            }\n          </table>\n        </div>\n      </HotKeys>\n    )\n  }\n}\n\nexport default ValuesTable\n"
  },
  {
    "path": "src/components/details/Details.js",
    "content": "import React from 'react'\nimport keys from '../../utils/keys'\nimport TypeIcon from '../../components/TypeIcon'\n\nclass Details extends React.Component {\n\n  static propTypes = {\n    _key: React.PropTypes.string.isRequired,\n    type: React.PropTypes.string.isRequired,\n    value: React.PropTypes.node.isRequired,\n    buttons: React.PropTypes.node\n  }\n\n  render () {\n    return (\n      <div className='key-details'>\n        <div className='key'>\n          {this.props._key}\n        </div>\n        <div className='type'>\n          <TypeIcon type={this.props.type}/>\n          <span className='type-name'>{keys.getTypeName(this.props.type)}</span>\n          {this.props.buttons ?\n            <div className='buttons'>\n              {this.props.buttons}\n            </div>\n          : null}\n        </div>\n        <div className='value'>\n          {this.props.value}\n        </div>\n      </div>\n    )\n  }\n}\n\nexport default Details\n"
  },
  {
    "path": "src/components/details/HashDetails.js",
    "content": "import React from 'react'\nimport pureRender from 'pure-render-decorator'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport Details from '../../components/details/Details'\n\n@pureRender\nclass HashDetails extends React.Component {\n\n  static propTypes = {\n    item: ImmutablePropTypes.map\n  }\n\n  render () {\n    return (\n      <Details {...this.props.item.toJS()}/>\n    )\n  }\n}\n\nexport default HashDetails\n"
  },
  {
    "path": "src/components/details/ListDetails.js",
    "content": "import React from 'react'\nimport pureRender from 'pure-render-decorator'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport clipboard from 'clipboard'\nimport Details from '../../components/details/Details'\nimport Highlight from '../../components/Highlight'\nimport Icon from '../../components/Icon'\n\n@pureRender\nclass ListDetails extends React.Component {\n\n  static propTypes = {\n    item: ImmutablePropTypes.map\n  }\n\n  state = {\n    types: ['JSON', 'Raw'],\n    show: 'JSON',\n    hasJSON: false\n  }\n\n  renderValue () {\n    let values = this.props.item.get('value')\n    return values.map((value, i) => {\n      let result\n      try {\n        if (this.state.show === 'Raw') {\n          result = <pre><code>{value}</code></pre>\n        } else {\n          result = (\n            <Highlight className='json'>\n              {JSON.stringify(JSON.parse(value), null, 2)}\n            </Highlight>\n          )\n        }\n        this.state.hasJSON = true\n      } catch (e) {\n        result = <pre><code>{value}</code></pre>\n      }\n      return (\n        <div key={i} className='value-item'>\n          {result}\n          <div className='actions'>\n            <span className='index'>{i}</span>\n            <a href='#' onClick={(e) => clipboard.writeText(value)}>\n              <Icon type='content_paste'/>\n            </a>\n          </div>\n        </div>\n      )\n    })\n  }\n\n  renderButtons () {\n    let buttons = {}\n\n    // Create types buttons.\n    if (this.state.hasJSON) {\n      buttons.show = (\n        <div className='pill'>\n          {this.state.types.map((type) => {\n            return (\n              <a\n                key={type}\n                href='#'\n                className={(this.state.show === type) ? 'active' : null}\n                onClick={(e) => {\n                  e.preventDefault()\n                  this.setState({show: type})\n                }}\n              >\n                {type}\n              </a>\n            )\n          })}\n        </div>\n      )\n    }\n\n    return React.addons.createFragment(buttons)\n  }\n\n  render () {\n    let {_key, type} = this.props.item.toJS()\n    return (\n      <Details _key={_key} type={type} value={this.renderValue()} buttons={this.renderButtons()}/>\n    )\n  }\n}\n\nexport default ListDetails\n"
  },
  {
    "path": "src/components/details/SetDetails.js",
    "content": "import React from 'react'\nimport pureRender from 'pure-render-decorator'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport clipboard from 'clipboard'\nimport Details from '../../components/details/Details'\nimport Highlight from '../../components/Highlight'\nimport Icon from '../../components/Icon'\n\n@pureRender\nclass SetDetails extends React.Component {\n\n  static propTypes = {\n    item: ImmutablePropTypes.map\n  }\n\n  state = {\n    types: ['JSON', 'Raw'],\n    show: 'JSON',\n    hasJSON: false\n  }\n\n  renderValue () {\n    let values = this.props.item.get('value')\n    return values.map((value, i) => {\n      let result\n      try {\n        if (this.state.show === 'Raw') {\n          result = <pre><code>{value}</code></pre>\n        } else {\n          result = (\n            <Highlight className='json'>\n              {JSON.stringify(JSON.parse(value), null, 2)}\n            </Highlight>\n          )\n        }\n        this.state.hasJSON = true\n      } catch (e) {\n        result = <pre><code>{value}</code></pre>\n      }\n      return (\n        <div key={i} className='value-item'>\n          {result}\n          <div className='actions'>\n            <a href='#' onClick={(e) => clipboard.writeText(value)}>\n              <Icon type='content_paste'/>\n            </a>\n          </div>\n        </div>\n      )\n    })\n  }\n\n  renderButtons () {\n    let buttons = {}\n\n    // Create types buttons.\n    if (this.state.hasJSON) {\n      buttons.show = (\n        <div className='pill'>\n          {this.state.types.map((type) => {\n            return (\n              <a\n                key={type}\n                href='#'\n                className={(this.state.show === type) ? 'active' : null}\n                onClick={(e) => {\n                  e.preventDefault()\n                  this.setState({show: type})\n                }}\n              >\n                {type}\n              </a>\n            )\n          })}\n        </div>\n      )\n    }\n\n    return React.addons.createFragment(buttons)\n  }\n\n  render () {\n    let {_key, type} = this.props.item.toJS()\n    return (\n      <Details _key={_key} type={type} value={this.renderValue()} buttons={this.renderButtons()}/>\n    )\n  }\n}\n\nexport default SetDetails\n"
  },
  {
    "path": "src/components/details/SortedSetDetails.js",
    "content": "import React from 'react'\nimport pureRender from 'pure-render-decorator'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport clipboard from 'clipboard'\nimport Details from '../../components/details/Details'\nimport Highlight from '../../components/Highlight'\nimport Icon from '../../components/Icon'\n\n@pureRender\nclass SortedSetDetails extends React.Component {\n\n  static propTypes = {\n    item: ImmutablePropTypes.map\n  }\n\n  state = {\n    types: ['JSON', 'Raw'],\n    show: 'JSON',\n    hasJSON: false\n  }\n\n  renderValue () {\n    let values = this.props.item.get('value')\n    return values.map((value, i) => {\n      let result\n      try {\n        if (this.state.show === 'Raw') {\n          result = <pre><code>{value.value}</code></pre>\n        } else {\n          result = (\n            <Highlight className='json'>\n              {JSON.stringify(JSON.parse(value.value), null, 2)}\n            </Highlight>\n          )\n        }\n        this.state.hasJSON = true\n      } catch (e) {\n        result = <pre><code>{value.value}</code></pre>\n      }\n      return (\n        <div key={i} className='value-item'>\n          {result}\n          <div className='actions'>\n            <span className='index'>{value.score}</span>\n            <a href='#' onClick={(e) => clipboard.writeText(value.value)}>\n              <Icon type='content_paste'/>\n            </a>\n          </div>\n        </div>\n      )\n    })\n  }\n\n  renderButtons () {\n    let buttons = {}\n\n    // Create types buttons.\n    if (this.state.hasJSON) {\n      buttons.show = (\n        <div className='pill'>\n          {this.state.types.map((type) => {\n            return (\n              <a\n                key={type}\n                href='#'\n                className={(this.state.show === type) ? 'active' : null}\n                onClick={(e) => {\n                  e.preventDefault()\n                  this.setState({show: type})\n                }}\n              >\n                {type}\n              </a>\n            )\n          })}\n        </div>\n      )\n    }\n\n    return React.addons.createFragment(buttons)\n  }\n\n  render () {\n    let {_key, type} = this.props.item.toJS()\n    return (\n      <Details _key={_key} type={type} value={this.renderValue()} buttons={this.renderButtons()}/>\n    )\n  }\n}\n\nexport default SortedSetDetails\n"
  },
  {
    "path": "src/components/details/StringDetails.js",
    "content": "import React from 'react'\nimport pureRender from 'pure-render-decorator'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport clipboard from 'clipboard'\nimport Details from '../../components/details/Details'\nimport Highlight from '../../components/Highlight'\n\n@pureRender\nclass KeyDetails extends React.Component {\n\n  static propTypes = {\n    item: ImmutablePropTypes.map\n  }\n\n  state = {\n    types: ['JSON', 'Raw'],\n    show: 'JSON',\n    hasJSON: false\n  }\n\n  renderValue () {\n    let value = this.props.item.get('value')\n    try {\n      if (this.state.show === 'Raw') {\n        value = <pre><code>{value}</code></pre>\n      } else {\n        value = (\n          <Highlight className='json'>\n            {JSON.stringify(JSON.parse(value), null, 2)}\n          </Highlight>\n        )\n      }\n      this.state.hasJSON = true\n    } catch (e) {\n      value = <pre><code>{value}</code></pre>\n    }\n    return value\n  }\n\n  renderButtons () {\n    let buttons = {}\n    let value = this.props.item.get('value')\n\n    // Create types buttons.\n    if (this.state.hasJSON) {\n      buttons.show = (\n        <div className='pill'>\n          {this.state.types.map((type) => {\n            return (\n              <a\n                key={type}\n                href='#'\n                className={(this.state.show === type) ? 'active' : null}\n                onClick={(e) => {\n                  e.preventDefault()\n                  this.setState({show: type})\n                }}\n              >\n                {type}\n              </a>\n            )\n          })}\n        </div>\n      )\n    }\n\n    // Todo make this into a component maybe?\n    buttons.copy = (\n      <a href='#' onClick={(e) => clipboard.writeText(value)}>Copy</a>\n    )\n\n    return React.addons.createFragment(buttons)\n  }\n\n  render () {\n    return (\n      <Details {...this.props.item.toJS()}\n        value={this.renderValue()}\n        buttons={this.renderButtons()}\n      />\n    )\n  }\n}\n\nexport default KeyDetails\n"
  },
  {
    "path": "src/components/handlers/BrowseHandler.js",
    "content": "import React from 'react'\nimport autobind from 'autobind-decorator'\nimport connectToStores from 'alt/utils/connectToStores'\nimport pureRender from 'pure-render-decorator'\nimport throttle from '../../utils/throttle'\nimport hostsStore from '../../stores/hostsStore'\nimport browseStore from '../../stores/browseStore'\nimport browseActions from '../../actions/browseActions'\nimport {IconButton} from 'material-ui'\nimport ValuesTable from '../../components/ValuesTable'\nimport KeyDetails from '../../components/KeyDetails'\nimport SidePanel from '../../components/SidePanel'\nimport Icon from '../../components/Icon'\n\n/**\n * Wrap the Browse component so we can handle transitions.\n */\n@pureRender\nclass BrowseHandler extends React.Component {\n\n  static willTransitionTo () {\n    let state = Object.assign({}, hostsStore.getState(), browseStore.getState())\n    let {loaded, connected, match} = state\n    if (!loaded && connected) {\n      browseActions.fetchKeys({match})\n    }\n  }\n\n  render () {\n    return <Browse/>\n  }\n}\n\n/**\n * Browse component.\n */\n@connectToStores\n@pureRender\n@autobind\nclass Browse extends React.Component {\n\n  static propTypes = {\n    loaded: React.PropTypes.bool,\n    connected: React.PropTypes.bool,\n    match: React.PropTypes.string,\n    selectedKey: React.PropTypes.object\n  }\n\n  static getStores () {\n    return [hostsStore, browseStore]\n  }\n\n  static getPropsFromStores () {\n    return Object.assign({}, hostsStore.getState(), browseStore.getState())\n  }\n\n  state = {\n    keyDetailsDocked: !!this.props.selectedKey\n  }\n\n  componentWillReceiveProps (nextProps) {\n    // Match text changed.\n    if (this.props.match !== nextProps.match) {\n      this.fetchKeys({match: nextProps.match})\n    }\n    // Just connected to a new host.\n    if (!this.props.connected && nextProps.connected) {\n      this.fetchKeys({match: this.props.match})\n    }\n    // A new key was selected.\n    if (nextProps.selectedKey && !this.state.keyDetailsDocked) {\n      this.toggleKeyDetails()\n    }\n    // A key was unselected.\n    if (this.props.selectedKey && !nextProps.selectedKey) {\n      this.toggleKeyDetails()\n    }\n  }\n\n  @throttle(250)\n  fetchKeys (options = {}) {\n    browseActions.fetchKeys.defer(options)\n  }\n\n  toggleKeyDetails () {\n    this.refs.keyDetailsPanel.toggle()\n    this.setState({\n      keyDetailsDocked: !this.state.keyDetailsDocked\n    })\n  }\n\n  onClickClose (e) {\n    browseActions.toggleSelected()\n  }\n\n  render () {\n    return (\n      <div className='browse'>\n        <ValuesTable {...this.props}/>\n        <SidePanel\n          ref='keyDetailsPanel'\n          className='key-details-panel'\n          openRight={true}\n          docked={this.state.keyDetailsDocked}\n          width={550}>\n          <IconButton className='close' onClick={this.onClickClose}>\n            <Icon type='cancel'/>\n          </IconButton>\n          {this.props.selectedKey ?\n            <KeyDetails ref='keyDetails' item={this.props.selectedKey}/>\n          :/*else*/\n            null\n          }\n        </SidePanel>\n      </div>\n    )\n  }\n}\n\nexport default BrowseHandler\n"
  },
  {
    "path": "src/components/handlers/InfoHandler.js",
    "content": "import React from 'react'\nimport ImmutablePropTypes from 'react-immutable-proptypes'\nimport autobind from 'autobind-decorator'\nimport pureRender from 'pure-render-decorator'\nimport connectToStores from 'alt/utils/connectToStores'\nimport throttle from '../../utils/throttle'\nimport hostsStore from '../../stores/hostsStore'\nimport hostsActions from '../../actions/hostsActions'\nimport HostInfo from '../../components/HostInfo'\n\n/**\n * Wrap the Info component so we can handle transisions.\n */\n@pureRender\nclass InfoHandler extends React.Component {\n\n  static willTransitionTo () {\n    let {hostInfo, connected} = hostsStore.getState()\n    if (!hostInfo && connected) {\n      hostsActions.fetchHostInfo()\n    }\n  }\n\n  render () {\n    return <Info />\n  }\n}\n\n/**\n * Info component.\n */\n@connectToStores\n@pureRender\n@autobind\nclass Info extends React.Component {\n\n  static propTypes = {\n    connected: React.PropTypes.bool,\n    hostInfoLoading: React.PropTypes.bool,\n    hostInfo: ImmutablePropTypes.map\n  }\n\n  static getStores () {\n    return [hostsStore]\n  }\n\n  static getPropsFromStores () {\n    return hostsStore.getState()\n  }\n\n  componentDidMount () {\n    this.refreshInterval = setInterval(() => this.fetchHostInfo(true), 1000)\n  }\n\n  componentWillUnmount () {\n    clearInterval(this.refreshInterval)\n  }\n\n  componentWillReceiveProps (nextProps) {\n    // Connected to a new server.\n    if (this.props.connected !== nextProps.connected) {\n      this.fetchHostInfo()\n    }\n  }\n\n  @throttle(250)\n  fetchHostInfo (isRefresh) {\n    hostsActions.fetchHostInfo.defer(isRefresh)\n  }\n\n  render () {\n    return (\n      <div className='info'>\n        {!this.props.hostInfo ?\n          (this.props.hostInfoLoading ?\n            <p>Loading Info</p>\n          : null)\n        :/*else*/\n          <HostInfo {...this.props}/>\n        }\n      </div>\n    )\n  }\n}\n\nexport default InfoHandler\n"
  },
  {
    "path": "src/components/handlers/MainHandler.js",
    "content": "import React from 'react'\nimport autobind from 'autobind-decorator'\nimport connectToStores from 'alt/utils/connectToStores'\nimport hostsStore from '../../stores/hostsStore'\nimport hostsActions from '../../actions/hostsActions'\nimport {RouteHandler} from 'react-router'\nimport mui from 'material-ui'\nimport Header from '../../components/Header'\n\n// Create an mui theme manager.\nconst themeManager = new mui.Styles.ThemeManager()\n\n/**\n * Wrap the Main component so we can handle transitions.\n */\nclass MainHandler extends React.Component {\n\n  static willTransitionTo () {\n    hostsActions.connectToHost(hostsStore.getState().activeHost)\n  }\n\n  render () {\n    return <Main />\n  }\n}\n\n/**\n * Main component.\n */\n@connectToStores\n@autobind\nclass Main extends React.Component {\n\n  static propTypes = {\n    activeHost: React.PropTypes.object\n  }\n\n  static getStores () {\n    return [hostsStore]\n  }\n\n  static getPropsFromStores () {\n    return hostsStore.getState()\n  }\n\n  static childContextTypes = {\n    muiTheme: React.PropTypes.object\n  }\n\n  getChildContext () {\n    return {\n      muiTheme: themeManager.getCurrentTheme()\n    }\n  }\n\n  render () {\n    return (\n      <div className='main'>\n        <Header {...this.props}/>\n        <RouteHandler/>\n      </div>\n    )\n  }\n}\n\nexport default MainHandler\nexport {themeManager}\n"
  },
  {
    "path": "src/components/handlers/NotFoundHandler.js",
    "content": "import React from 'react'\n\nclass NotFoundHandler extends React.Component {\n\n  render () {\n    return (\n      <div className='not-found'>\n        <h1>Sorry, Page Not Found</h1>\n      </div>\n    )\n  }\n}\n\nexport default NotFoundHandler\n"
  },
  {
    "path": "src/conf.js",
    "content": "var conf = {}\n\nconf.BASENAME = 'RedisExplorer'\nconf.APPNAME = conf.BASENAME\nconf.DEVELPER_ID = 'Brian Link'\nconf.COMPANY = 'Terra Eclipse, Inc.'\nconf.ICON = 'resources/redisexplorer'\nconf.ICON_URL = 'https://raw.githubusercontent.com/cpsubrian/redisexplorer/master/' + conf.ICON + '.ico'\nconf.BUNDLE_ID = 'com.terraeclipse.redisexplorer'\nconf.OSX_OUT = './dist/' + conf.APPNAME + '-darwin-x64'\nconf.OSX_FILENAME = conf.OSX_OUT + '/' + conf.APPNAME + '.app'\n\nmodule.exports = conf\n"
  },
  {
    "path": "src/main.js",
    "content": "/**\n * Entry point for babel.\n */\n\nimport 'babel/polyfill'\nimport './app'\n"
  },
  {
    "path": "src/router.js",
    "content": "import React from 'react'\nimport Router, {Route, NotFoundRoute} from 'react-router'\n\nimport MainHandler from './components/handlers/MainHandler'\nimport NotFoundHandler from './components/handlers/NotFoundHandler'\nimport BrowseHandler from './components/handlers/BrowseHandler'\nimport InfoHandler from './components/handlers/InfoHandler'\n\nconst routes = (\n  <Route handler={MainHandler}>\n    <Route name='browse' path='/' handler={BrowseHandler}/>\n    <Route name='info' path='/info' handler={InfoHandler}/>\n    <NotFoundRoute handler={NotFoundHandler}/>\n  </Route>\n)\n\nconst router = Router.create({\n  routes: routes\n})\n\nexport default router\nexport {routes, router}\n"
  },
  {
    "path": "src/stores/browseStore.js",
    "content": "import alt from '../alt'\nimport _ from 'underscore'\nimport Immutable from 'immutable'\nimport immutable from 'alt/utils/ImmutableUtil'\nimport regex from '../utils/regex'\nimport settings from '../utils/settings'\nimport browseActions from '../actions/browseActions'\n\n@immutable\nclass BrowseStore {\n\n  constructor () {\n    // Bind actions.\n    this.bindActions(browseActions)\n\n    // Initialize state.\n    this.keys = Immutable.OrderedMap()\n    this.keysIndex = Immutable.List()\n    this.selectedKey = null\n    this.selectedIndex = null\n    this.loading = false\n    this.loaded = false\n    this.finished = false\n    this.error = null\n    this.offset = 0\n    this.match = settings.get('browse:match', null)\n    this.matchRegExp = null\n  }\n\n  /* General\n   ****************************************************************************/\n  onResetKeys () {\n    this.keys = this.keys.clear()\n    this.keysIndex = this.keysIndex.clear()\n    this.selectedKey = null\n    this.selectedIndex = null\n    this.loading = false\n    this.loaded = false\n    this.finished = false\n    this.error = null\n    this.offset = 0\n    this.match = null\n    this.matchRegExp = null\n    settings.set('browse:match', null)\n  }\n\n  onSetOffset (offset) {\n    this.offset = offset\n  }\n\n  onSetMatch (match) {\n    this.matchRegExp = null\n    if (match && match.length) {\n      this.match = match\n    } else {\n      this.match = null\n    }\n    settings.set('browse:match', match)\n  }\n\n  onToggleSelectedKey (key) {\n    if (key) {\n      let index = this.keysIndex.indexOf(key)\n      this.onToggleSelected({key, index})\n    } else {\n      this.onToggleSelected()\n    }\n  }\n\n  onToggleSelectedIndex (index) {\n    let key = this.keysIndex.get(index)\n    this.onToggleSelected({key, index})\n  }\n\n  onToggleSelected (item = {}) {\n    let {key, index} = item\n    key = (typeof key !== 'undefined') ? key : this.selectedKey.get('key')\n    index = (typeof index !== 'undefined') ? index : this.selectedIndex\n    this.keys = this.keys.withMutations((keys) => {\n      if (keys.get(key).get('selected')) {\n        keys.update(key, (item) => item.set('selected', false))\n        this.selectedKey = null\n        this.selectedIndex = null\n      } else {\n        if (this.selectedKey) {\n          keys.update(this.selectedKey.get('key'), (item) => item.set('selected', false))\n        }\n        keys.update(key, (item) => item.set('selected', true))\n        this.selectedKey = keys.get(key)\n        this.selectedIndex = index\n      }\n    })\n  }\n\n  /* Fetch Keys Lifecycle\n   ****************************************************************************/\n  onFetchKeys () {\n    this.keys = this.keys.clear()\n    this.keysIndex = this.keysIndex.clear()\n    this.selectedKey = null\n    this.selectedIndex = null\n    this.loading = true\n    this.loaded = false\n    this.finished = false\n    this.error = null\n    this.offset = 0\n\n    // Compute match regular expression.\n    if (this.match && !this.matchRegExp) {\n      this.matchRegExp = regex.fromGlob(this.match)\n    }\n  }\n\n  onFetchKeysAdd (newKeys) {\n    this.loading = false\n    this.loaded = true\n    this.keys = this.keys.withMutations((keys) => {\n      this.keysIndex = this.keysIndex.withMutations((index) => {\n        newKeys.forEach((key) => {\n          if (!keys.has(key.key)) {\n            index.push(key.key)\n          }\n          keys.set(key.key, Immutable.Map(key))\n        })\n      })\n    })\n  }\n\n  onFetchKeysFailed (err) {\n    this.loading = false\n    this.loaded = true\n    this.error = err\n  }\n\n  onFetchKeysFinished () {\n    this.loading = false\n    this.loaded = true\n    this.finished = true\n  }\n\n  /* Fetch Values Lifecycle\n   ****************************************************************************/\n  onFetchValues (keys) {\n    // @todo Maybe set a loading flag on each key?\n  }\n\n  onFetchValuesAdd (values) {\n    this.keys = this.keys.withMutations((keys) => {\n      _.each(values, (value, key) => {\n        keys.update(key, (item) => item.set('value', value))\n      })\n    })\n  }\n\n  onFetchValuesFailed (err) {\n    this.error = err\n  }\n}\n\nexport default alt.createStore(BrowseStore)\n"
  },
  {
    "path": "src/stores/hostsStore.js",
    "content": "import alt from '../alt'\nimport path from 'path'\nimport fs from 'fs'\nimport _ from 'underscore'\nimport Immutable from 'immutable'\nimport immutable from 'alt/utils/ImmutableUtil'\nimport sshConfig from 'ssh-config'\nimport hostsActions from '../actions/hostsActions'\nimport settings from '../utils/settings'\n\n@immutable\nclass HostsStore {\n\n  constructor () {\n    // Bind actions.\n    this.bindActions(hostsActions)\n\n    // Initialize state.\n    this.error = null\n    this.connected = false\n    this.connecting = false\n    this.hostInfoLoading = false\n    this.hostInfoError = null\n    this.hostInfo = null\n    this.hosts = Immutable.List()\n\n    // Load hosts from .ssh.\n    let configPath = path.join(\n      process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'],\n      '.ssh',\n      'config'\n    )\n    let hostsFile = fs.readFileSync(configPath, 'utf8')\n    if (hostsFile && hostsFile.length) {\n      let config = sshConfig.parse(hostsFile)\n\n      this.hosts = this.hosts.withMutations((hosts) => {\n        _.each(config, (host) => {\n          hosts.push(Immutable.Map(host))\n        })\n      })\n    }\n\n    // Alphabetize.\n    this.hosts = this.hosts.sortBy((host) => host.get('Host').toLowerCase())\n\n    // Add localhost and set active host.\n    this.hosts = this.hosts.unshift(Immutable.Map({\n      Host: 'localhost',\n      Hostname: 'localhost'\n    }))\n\n    // Set active host.\n    let name = settings.get('hosts:active', 'localhost')\n    this.activeHost = this.hosts.find((host) => host.get('Host') === name)\n  }\n\n  /* Connection Lifecycle\n   ****************************************************************************/\n  onConnectToHost (host) {\n    if ((!this.connected && !this.connecting) || (this.activeHost !== host)) {\n      this.activeHost = host\n      this.connected = false\n      this.connecting = true\n      this.hostInfoLoading = false\n      this.hostInfoError = null\n      this.hostInfo = null\n      settings.set('hosts:active', host.get('Host'))\n    }\n  }\n\n  onConnectToHostFailed (err) {\n    this.error = err\n    this.connecting = false\n  }\n\n  onConnectedToHost () {\n    this.connected = true\n    this.connecting = false\n  }\n\n  /* Fetch Host Info Lifecycle\n   ****************************************************************************/\n  onFetchHostInfo (isRefresh) {\n    if (!isRefresh) {\n      this.loadingHostInfo = true\n      this.hostInfo = null\n    }\n    this.hostInfoError = null\n  }\n\n  onFetchHostInfoFailed (err) {\n    this.hostInfoLoading = false\n    this.hostInfoError = err\n  }\n\n  onFetchHostInfoFinished (info) {\n    this.hostInfoLoading = false\n    this.hostInfoError = null\n    this.hostInfo = Immutable.Map(info)\n  }\n}\n\nexport default alt.createStore(HostsStore)\n"
  },
  {
    "path": "src/utils/db.js",
    "content": "import redis from 'redis'\nimport redisInfo from 'redis-info'\nimport tunnel from 'tunnel-ssh'\nimport _ from 'underscore'\n\nconst DST_PORT = 6379\nconst LOCAL_PORT = 8379\nconst DEFAULT_COUNT = 250\n\n// Abstraction around redis.\nclass DB {\n\n  // Clean up open connections, then initiate a new host connection.\n  connect (host, cb) {\n    if (this.client) {\n      this.client.end()\n    }\n    if (this.tunnel) {\n      this.tunnel.close((err) => {\n        if (err && err.message !== 'Not running') return cb(err)\n        this.connectToHost(host, cb)\n      })\n    } else {\n      this.connectToHost(host, cb)\n    }\n  }\n\n  // Connect to localhost or remote.\n  connectToHost (host, cb) {\n    if (host.Host === 'localhost') {\n      setImmediate(() => {\n        this.client = redis.createClient()\n        this.client.on('ready', () => {\n          cb()\n        })\n      })\n    } else {\n      this.connectToRemoteHost(host, cb)\n    }\n  }\n\n  // Connect to a remote host using privateKey auth via ssh-agent.\n  connectToRemoteHost (host, cb) {\n    let config = {\n      host: host.Hostname,\n      dstPort: DST_PORT,\n      localPort: LOCAL_PORT,\n      username: host.User,\n      agent: process.env.SSH_AUTH_SOCK\n    }\n    this.tunnel = tunnel(config, (err) => {\n      if (err) return cb(err)\n      this.client = redis.createClient(config.localPort)\n      this.client.on('ready', () => {\n        cb()\n      })\n    })\n  }\n\n  // An iterator-like api to scan keys.\n  scan (options = {}) {\n    let db = this\n\n    options.cmd = 'SCAN'\n    options.count = options.count || DEFAULT_COUNT\n\n    let _scan = {\n      options: options,\n      cursor: 0,\n      stopped: false,\n      results: [],\n      // Perform the next iteration of the scan.\n      next: (cb) => {\n        let args = []\n        if (_scan.stopped) return\n        if (_scan.cursor !== false) {\n          args.push(_scan.cursor)\n          if (options.key) {\n            args.push(options.key)\n          }\n          if (options.count) {\n            args.push('COUNT')\n            args.push(options.count)\n          }\n          if (options.match) {\n            args.push('MATCH')\n            args.push(options.match)\n          }\n          args.push(_scan.process.bind(db, cb))\n          db.client[options.cmd].apply(db.client, args)\n        } else {\n          if (cb) cb(null, false)\n        }\n      },\n      // Collect results.\n      process: (cb, err, results) => {\n        if (err) return cb(err)\n        if (_scan.stopped) return\n        let [cursor, keys] = results\n        _scan.cursor = (cursor === '0') ? false : cursor\n        _scan.results = _scan.results.concat(keys.map(key => {\n          return {key: key, _key: key}\n        }))\n        // Make sure we have at least options.count results.\n        if (!_scan.cursor || (_scan.results.length >= options.count)) {\n          ((results) => {\n            _scan.results = []\n            _scan.postProcess(cb, results)\n          })(_scan.results)\n        } else {\n          _scan.next(cb)\n        }\n      },\n      // Lookup data types of the matching keys.\n      postProcess: (cb, results) => {\n        if (_scan.stopped) return\n        if (!results.length || !_scan.options.loadTypes) {\n          return cb(null, results)\n        }\n        db.client.multi(results.map((result) => {\n          return ['TYPE', result.key]\n        })).exec((err, types) => {\n          if (err) return cb(err)\n          types.forEach((type, i) => {\n            results[i].type = type\n          })\n          cb(null, results)\n        })\n      },\n      stop: () => {\n        _scan.stopped = true\n      }\n    }\n    return _scan\n  }\n\n  // Fetch and parse the redis server info.\n  fetchInfo (cb) {\n    this.client.INFO((err, result) => {\n      if (err) return cb(err)\n      try {\n        let info = redisInfo.parse(result)\n        return cb(null, info)\n      } catch (e) {\n        return cb(e)\n      }\n    })\n  }\n\n  // Load values for a set of keys ({key, type}).\n  fetchValues (keys, cb) {\n    // Collect keys by type.\n    let types = keys.reduce((memo, key) => {\n      memo[key.type] = memo[key.type] || []\n      memo[key.type].push(key.key)\n      return memo\n    }, {})\n\n    // Create tasks.\n    let tasks = _.map(types, (keys, type) => {\n      switch (type) {\n        case 'string':\n          return this.fetchStringValues(keys)\n        case 'list':\n          return this.fetchListValues(keys)\n        case 'set':\n          return this.fetchSetValues(keys)\n        case 'zset':\n          return this.fetchSortedSetValues(keys)\n        case 'hash':\n          return this.fetchHashValues(keys)\n        default:\n          return Promise.reject(new Error('Unknown type: ' + type))\n      }\n    })\n\n    Promise.all(tasks).then((values) => {\n      cb(null, Object.assign.apply(null, values))\n    }, (err) => {\n      cb(err)\n    })\n  }\n\n  // Fetch string values for an array of keys.\n  fetchStringValues (keys) {\n    return new Promise((resolve, reject) => {\n      this.client.multi(keys.map((key) => {\n        return ['GET', key]\n      })).exec((err, results) => {\n        if (err) return reject(err)\n        resolve(keys.reduce((memo, key, i) => {\n          memo[key] = results[i]\n          return memo\n        }, {}))\n      })\n    })\n  }\n\n  // Fetch list values for an array of keys.\n  fetchListValues (keys) {\n    return new Promise((resolve, reject) => {\n      this.client.multi(keys.map((key) => {\n        return ['LRANGE', key, 0, -1]\n      })).exec((err, results) => {\n        if (err) return reject(err)\n        resolve(keys.reduce((memo, key, i) => {\n          memo[key] = results[i]\n          return memo\n        }, {}))\n      })\n    })\n  }\n\n  // Fetch set values for an array of keys.\n  fetchSetValues (keys) {\n    return new Promise((resolve, reject) => {\n      this.client.multi(keys.map((key) => {\n        return ['SMEMBERS', key]\n      })).exec((err, results) => {\n        if (err) return reject(err)\n        resolve(keys.reduce((memo, key, i) => {\n          memo[key] = results[i]\n          return memo\n        }, {}))\n      })\n    })\n  }\n\n  // Fetch sorted-set values for an array of keys.\n  fetchSortedSetValues (keys) {\n    return new Promise((resolve, reject) => {\n      this.client.multi(keys.map((key) => {\n        return ['ZRANGEBYSCORE', key, '-inf', '+inf', 'WITHSCORES']\n      })).exec((err, results) => {\n        if (err) return reject(err)\n        resolve(keys.reduce((memo, key, i) => {\n          memo[key] = []\n          results[i].forEach((val, j) => {\n            if (j % 2 === 0 || j === 0) {\n              memo[key].push({\n                value: val,\n                score: parseFloat(results[i][j + 1])\n              })\n            }\n          })\n          return memo\n        }, {}))\n      })\n    })\n  }\n\n  // Fetch hash values for an array of keys.\n  fetchHashValues (keys) {\n    return Promise.resolve(keys.reduce((memo, key) => {\n      memo[key] = '[value]'\n      return memo\n    }, {}))\n  }\n}\n\nexport default (new DB())\n"
  },
  {
    "path": "src/utils/keys.js",
    "content": "const keysUtils = {\n\n  getTypeName (type) {\n    switch (type) {\n      case 'string': return 'String'\n      case 'list': return 'List'\n      case 'set': return 'Set'\n      case 'zset': return 'Sorted Set'\n      case 'hash': return 'Hash'\n    }\n  }\n}\n\nexport default keysUtils\n"
  },
  {
    "path": "src/utils/perf.js",
    "content": "import React from 'react/addons'\nexport default React.addons.Perf\n"
  },
  {
    "path": "src/utils/regex.js",
    "content": "const regex = {\n\n  escape (s) {\n    return s.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&')\n  },\n\n  fromGlob (s) {\n    let converted = s\n      .split('*')\n      .map((part) => {\n        return regex.escape(part)\n      })\n      .map((part) => {\n        return '(' + part + ')'\n      })\n      .join('(.*)')\n\n    return new RegExp('^' + converted, 'g')\n  }\n}\n\nexport default regex\n"
  },
  {
    "path": "src/utils/settings.js",
    "content": "import fs from 'fs'\nimport path from 'path'\nimport mkdirp from 'mkdirp'\nimport conf from '../conf'\nimport throttle from '../utils/throttle'\n\nlet basePath = path.join(\n      process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'],\n      'Library',\n      'Application\\ Support',\n      conf['APPNAME']\n    )\nlet settingsPath = path.join(basePath, 'settings.json')\n\n// Create basePath if it doesn't exist.\nmkdirp.sync(basePath)\n\n// Load settings from the file.\nlet cache = {}\ntry {\n  cache = JSON.parse(fs.readFileSync(settingsPath))\n} catch (e) {}\n\n// Settings API.\nconst settings = {\n\n  get (key, fallback) {\n    return cache[key] || fallback\n  },\n\n  set (key, value) {\n    cache[key] = value\n    settings.write()\n  },\n\n  @throttle(1000)\n  write () {\n    fs.writeFileSync(settingsPath, JSON.stringify(cache))\n  }\n}\n\nexport default settings\n"
  },
  {
    "path": "src/utils/spawn.js",
    "content": "/**\n * ES7-like async via a wrapped generator function.\n * Inside the function you can yield Promises.\n */\nfunction spawn (genF) {\n  return new Promise((resolve, reject) => {\n    let gen = genF()\n    let step = (nextF) => {\n      let next\n      try {\n        next = nextF()\n      } catch(e) {\n        // Finished with failure, reject the promise.\n        reject(e)\n        return\n      }\n      if (next.done) {\n        // Finished with success, resolve the promise.\n        resolve(next.value)\n        return\n      }\n      // Not finished, chain off the yielded promise and `step` again.\n      Promise\n        .cast(next.value)\n        .then((v) => {\n          step(() => gen.next(v))\n        }, (e) => {\n          step(() => gen.throw(e))\n        })\n    }\n    step(() => gen.next(undefined))\n  })\n}\n\nexport default spawn\n"
  },
  {
    "path": "src/utils/throttle.js",
    "content": "import _ from 'underscore'\n\n/**\n * Throttle decorator.\n *\n * Use like:\n *\n *   @throttle(250)\n *   myFunction () {\n *     // Probably does something.\n *   }\n */\nfunction throttle (delay, options = {}) {\n  return function throttleDecorator (target, name, descriptor) {\n    descriptor.value = _.throttle(descriptor.value, delay, options)\n    return descriptor\n  }\n}\n\nexport default throttle\n"
  },
  {
    "path": "styles/.gitkeep",
    "content": ""
  },
  {
    "path": "styles/components/header.less",
    "content": ".header {\n  .icon {\n    vertical-align: middle;\n  }\n  .toolbar {\n    padding: 0 !important;\n  }\n  .button {\n    display: inline-block;\n    padding: 0 @space;\n    color: @grey-600;\n    line-height: @toolbarHeight;\n    text-decoration: none;\n\n    &:hover {\n      color: @black;\n      text-decoration: none;\n    }\n    &.active {\n      color: @pink;\n    }\n    .icon {\n      vertical-align: middle;\n      margin-right: 4px;\n    }\n  }\n  .host-button {\n    &.disconnected i {\n      color: @grey-600;\n    }\n    &.connecting i {\n      color: @pink-100;\n    }\n    &.connected i {\n      color: @pink;\n    }\n  }\n  .host-option {\n    font-size: 14px;\n\n    .icon {\n      font-size: 16px;\n      color: @grey-400;\n    }\n  }\n}"
  },
  {
    "path": "styles/components/key-details.less",
    "content": ".key-details-panel {\n  .side-panel {\n    position: relative;\n\n    .close {\n      position: absolute !important;\n      top: 0;\n      right: 0;\n      color: @cyan-600;\n\n      &:hover {\n        color: @cyan-300;\n      }\n    }\n\n    .key-details {\n      display: flex;\n      flex-direction: column;\n      height: 100%;\n      background-color: @grey-50;\n      color: @cyan-900;\n      font-size: 12px;\n\n      & > * {\n        box-sizing: border-box;\n      }\n      .key {\n        min-height: 48px;\n        padding: 15px 48px 12px 24px;\n        background: @cyan-700;\n        border-bottom: 1px solid @cyan-600;\n        color: @cyan-100;\n        white-space: pre-wrap;\n        word-wrap: break-word;\n        font-size: 16px;\n      }\n      .type {\n        padding: 12px 24px;\n        background: @cyan-800;\n        border-bottom: 1px solid @cyan-700;\n        color: @cyan-400;\n        position: relative;\n\n        .type-icon {\n          font-size: 16px;\n          margin-right: 6px;\n          vertical-align: middle;\n        }\n        .type-name {\n          vertical-align: middle;\n        }\n        .buttons {\n          position: absolute;\n          top: 11px;\n          right: 24px;\n\n          & > * {\n            margin-left: 8px;\n            float: left;\n          }\n          a {\n            font-size: 10px;\n            line-height: 1em;\n            display: inline-block;\n            padding: 4px 8px;\n            color: @cyan-900;\n            text-decoration: none;\n            background: @cyan-600;\n            border-radius: 4px;\n            transition: background 300ms;\n          }\n          a:hover {\n            background: @cyan-500;\n          }\n          a.active, a:active {\n            color: @cyan-50;\n            background: @cyan-400;\n          }\n          .pill {\n            a {\n              margin-left: 1px;\n              border-radius: 0;\n            }\n            a:first-child {\n              border-top-left-radius: 4px;\n              border-bottom-left-radius: 4px;\n            }\n            a:last-child {\n              border-top-right-radius: 4px;\n              border-bottom-right-radius: 4px;\n            }\n          }\n        }\n      }\n      .value {\n        flex: 1;\n        overflow: auto;\n        padding: 24px;\n        position: relative;\n\n        .value-item {\n          position: relative;\n        }\n        .value-item .actions {\n          display: inline-block;\n          position: absolute;\n          top: 1px;\n          right: 1px;\n          padding: 2px 6px;\n          font-size: 9px;\n          line-height: 12px;\n          background: @cyan-50;\n          color: @white;\n          border-bottom-left-radius: 5px;\n        }\n        .value-item:hover .actions {\n          background-color: @cyan-300;\n        }\n        .value-item .actions a {\n          display: inline-block;\n          margin-left: 6px;\n          color: @white;\n          font-size: 12px;\n          vertical-align: middle;\n        }\n        .value-item .actions a:first-child {\n          margin-left: 0;\n        }\n        .value-item .actions a i {\n          font-size: 12px;\n        }\n        .value-item .actions a:hover {\n          color: @cyan-800;\n        }\n        .value-item .actions a:active {\n          color: @cyan-900;\n        }\n\n        pre {\n          display: block;\n          padding: 0;\n          margin: 0 0 12px 0;\n          white-space: pre-wrap;\n          word-wrap: break-word;\n        }\n        pre:last-child {\n          margin-bottom: 0;\n        }\n        pre code {\n          display: block;\n          padding: 12px;\n          background: @white !important;\n          border: 1px solid @grey-100;\n          border-bottom-color: @grey-200;\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "styles/components/values-table.less",
    "content": ".values-table {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n\n  table {\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n  }\n  thead {\n    flex: 0 0 auto;\n    display: block;\n    .zDepth(1);\n\n    tr {\n      display: flex;\n      flex-direction: row;\n    }\n    th {\n      flex: 1;\n      padding: 0 @space;\n      text-align: left;\n      background: @grey-100;\n      border-bottom: none;\n    }\n    .search {\n      margin-top: -12px;\n      margin-bottom: -9px;\n    }\n  }\n  tbody {\n    flex: 1 1 auto;\n    display: block;\n\n    tr {\n      display: flex;\n      flex-direction: row;\n\n      td {\n        border-bottom: 1px solid @grey-100;\n        padding: @space/2 @space;\n        height: (30 - @space);\n        line-height: (30 - @space);\n        font-size: 12px;\n      }\n      td.key {\n        color: @grey-800;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        white-space: nowrap;\n\n        strong {\n          color: @cyan-600;\n        }\n      }\n      td.value {\n        flex: 1;\n        color: @grey-300;\n        padding-left: 0;\n        padding-right: 0;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        white-space: nowrap;\n      }\n      td.type i {\n        font-size: 16px;\n        vertical-align: middle;\n        color: @blue-grey-200;\n      }\n    }\n    tr:hover {\n      td {\n        cursor: pointer;\n        background: @grey-50;\n      }\n      td.key {\n        color: @grey-900;\n      }\n      td.value {\n        color: @grey-600;\n      }\n    }\n    tr.selected {\n      td {\n        background: @cyan-50;\n      }\n      td.key {\n        color: @cyan-900;\n      }\n      td.value {\n        color: @cyan-700;\n      }\n    }\n  }\n}"
  },
  {
    "path": "styles/handlers/browse.less",
    "content": ".main {\n  .browse {\n    flex: 1;\n    position: relative;\n  }\n}"
  },
  {
    "path": "styles/handlers/info.less",
    "content": ".main {\n  .info {\n    flex: 1;\n    position: relative;\n    overflow-y: auto;\n    background: @grey-100;\n  }\n  .host-info {\n    padding: 24px;\n    -webkit-column-count: 3;\n    -webkit-column-gap: 24px;\n\n    .host-info-group {\n      box-sizing: border-box;\n      display: inline-block;\n      padding: 12px;\n      margin-bottom: 24px;\n      width: 100%;\n\n      h3 {\n        margin: 0 0 4px 0;\n        padding: 0;\n        font-size: 12px;\n        color: @cyan;\n      }\n      table {\n        width: 100%;\n        font-size: 11px;\n\n        tr {\n          td {\n            padding: 2px 0;\n            white-space: pre-wrap;\n            word-wrap: break-word;\n\n            strong {\n              color: @cyan-200;\n              margin-top: 5px;\n            }\n          }\n          td.prop {\n            padding-right: 12px;\n            color: @grey-500;\n          }\n          td.value {\n\n          }\n        }\n        tr:nth-child(2n) {\n          td {\n            background: #fbfbfb;\n          }\n        }\n      }\n    }\n  }\n}"
  },
  {
    "path": "styles/handlers/main.less",
    "content": ".main {\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n  position: relative;\n\n  .header {\n    flex: 0 0 @toolbarHeight;\n    .zDepth(2);\n  }\n}"
  },
  {
    "path": "styles/index.less",
    "content": "// Fonts\n@import url(fonts/Roboto/Roboto.css);\n@import url(fonts/MaterialIcons/MaterialIcons.css);\n\n// Utils\n@import \"utils/variables.less\";\n@import \"utils/material-colors.less\";\n@import \"utils/mixins.less\";\n@import \"utils/highlight.less\";\n\n// Handlers\n@import \"handlers/main.less\";\n@import \"handlers/browse.less\";\n@import \"handlers/info.less\";\n\n// Components\n@import \"components/header.less\";\n@import \"components/key-details.less\";\n@import \"components/values-table.less\";\n\n// Reset html/body\nhtml, body {\n  height: 100%;\n  width: 100%;\n  margin: 0;\n  padding: 0;\n  overflow: hidden;\n  background: none;\n  //-webkit-user-select: none;\n  //-webkit-user-drag: none;\n  font-family: @font-regular;\n  cursor: default;\n  -webkit-font-smoothing: subpixel-antialiased;\n  text-rendering: optimizelegibility;\n  //-webkit-font-smoothing: antialiased;\n  img {\n    pointer-events: none;\n  }\n}"
  },
  {
    "path": "styles/utils/highlight.less",
    "content": "/*\n\ngithub.com style (c) Vasily Polovnyov <vast@whiteants.net>\n\n*/\n\n.hljs {\n  display: block;\n  overflow-x: auto;\n  padding: 0.5em;\n  color: #333;\n  background: #f8f8f8;\n  -webkit-text-size-adjust: none;\n}\n\n.hljs-comment,\n.diff .hljs-header {\n  color: #998;\n  font-style: italic;\n}\n\n.hljs-keyword,\n.css .rule .hljs-keyword,\n.hljs-winutils,\n.nginx .hljs-title,\n.hljs-subst,\n.hljs-request,\n.hljs-status {\n  color: #333;\n  font-weight: bold;\n}\n\n.hljs-number,\n.hljs-hexcolor,\n.ruby .hljs-constant {\n  color: #008080;\n}\n\n.hljs-string,\n.hljs-tag .hljs-value,\n.hljs-doctag,\n.tex .hljs-formula {\n  color: #d14;\n}\n\n.hljs-title,\n.hljs-id,\n.scss .hljs-preprocessor {\n  color: #900;\n  font-weight: bold;\n}\n\n.hljs-list .hljs-keyword,\n.hljs-subst {\n  font-weight: normal;\n}\n\n.hljs-class .hljs-title,\n.hljs-type,\n.vhdl .hljs-literal,\n.tex .hljs-command {\n  color: #458;\n  font-weight: bold;\n}\n\n.hljs-tag,\n.hljs-tag .hljs-title,\n.hljs-rule .hljs-property,\n.django .hljs-tag .hljs-keyword {\n  color: #000080;\n  font-weight: normal;\n}\n\n.hljs-attribute,\n.hljs-variable,\n.lisp .hljs-body,\n.hljs-name {\n  color: #008080;\n}\n\n.hljs-regexp {\n  color: #009926;\n}\n\n.hljs-symbol,\n.ruby .hljs-symbol .hljs-string,\n.lisp .hljs-keyword,\n.clojure .hljs-keyword,\n.scheme .hljs-keyword,\n.tex .hljs-special,\n.hljs-prompt {\n  color: #990073;\n}\n\n.hljs-built_in {\n  color: #0086b3;\n}\n\n.hljs-preprocessor,\n.hljs-pragma,\n.hljs-pi,\n.hljs-doctype,\n.hljs-shebang,\n.hljs-cdata {\n  color: #999;\n  font-weight: bold;\n}\n\n.hljs-deletion {\n  background: #fdd;\n}\n\n.hljs-addition {\n  background: #dfd;\n}\n\n.diff .hljs-change {\n  background: #0086b3;\n}\n\n.hljs-chunk {\n  color: #aaa;\n}"
  },
  {
    "path": "styles/utils/material-colors.less",
    "content": "// ==========================================================================\n//\n// Name:           UI Color Palette\n// Description:    The color palette of material design.\n// Version:        2.1.0\n//\n// Author:         Denis Malinochkin\n// Git:            https://github.com/mrmlnc/material-color\n//\n// twitter:        @mrmlnc\n//\n// ==========================================================================\n\n// Palettes\n// --------------------------------------------------------------------------\n\n// Red\n@list-red:                             #ffebee, #ffcdd2, #ef9a9a, #e57373, #ef5350,\n                                       #f44336, #e53935, #d32f2f, #c62828, #b71c1c,\n                                       #ff8a80, #ff5252, #ff1744, #d50000;\n\n// Pink\n@list-pink:                            #fce4ec, #f8bbd0, #f48fb1, #f06292, #ec407a,\n                                       #e91e63, #d81b60, #c2185b, #ad1457, #880e4f,\n                                       #ff80ab, #ff4081, #f50057, #c51162;\n\n// Purple\n@list-purple:                          #f3e5f5, #e1bee7, #ce93d8, #ba68c8, #ab47bc,\n                                       #9c27b0, #8e24aa, #7b1fa2, #6a1b9a, #4a148c,\n                                       #ea80fc, #e040fb, #d500f9, #aa00ff;\n\n// Deep Purple\n@list-deep-purple:                     #ede7f6, #d1c4e9, #b39ddb, #9575cd, #7e57c2,\n                                       #673ab7, #5e35b1, #512da8, #4527a0, #311b92,\n                                       #b388ff, #7c4dff, #651fff, #6200ea;\n\n// Indigo\n@list-indigo:                          #e8eaf6, #c5cae9, #9fa8da, #7986cb, #5c6bc0,\n                                       #3f51b5, #3949ab, #303f9f, #283593, #1a237e,\n                                       #8c9eff, #536dfe, #3d5afe, #304ffe;\n\n// Blue\n@list-blue:                            #e3f2fd, #bbdefb, #90caf9, #64b5f6, #42a5f5,\n                                       #2196f3, #1e88e5, #1976d2, #1565c0, #0d47a1,\n                                       #82b1ff, #448aff, #2979ff, #2962ff;\n\n// Light Blue\n@list-light-blue:                      #e1f5fe, #b3e5fc, #81d4fa, #4fc3f7, #29b6f6,\n                                       #03a9f4, #039be5, #0288d1, #0277bd, #01579b,\n                                       #80d8ff, #40c4ff, #00b0ff, #0091ea;\n\n// Cyan\n@list-cyan:                            #e0f7fa, #b2ebf2, #80deea, #4dd0e1, #26c6da,\n                                       #00bcd4, #00acc1, #0097a7, #00838f, #006064,\n                                       #84ffff, #18ffff, #00e5ff, #00b8d4;\n\n// Teal\n@list-teal:                            #e0f2f1, #b2dfdb, #80cbc4, #4db6ac, #26a69a,\n                                       #009688, #00897b, #00796b, #00695c, #004d40,\n                                       #a7ffeb, #64ffda, #1de9b6, #00bfa5;\n\n// Green\n@list-green:                           #e8f5e9, #c8e6c9, #a5d6a7, #81c784, #66bb6a,\n                                       #4caf50, #43a047, #388e3c, #2e7d32, #1b5e20,\n                                       #b9f6ca, #69f0ae, #00e676, #00c853;\n\n// Light Green\n@list-light-green:                     #f1f8e9, #dcedc8, #c5e1a5, #aed581, #9ccc65,\n                                       #8bc34a, #7cb342, #689f38, #558b2f, #33691e,\n                                       #ccff90, #b2ff59, #76ff03, #64dd17;\n\n// Lime\n@list-lime:                            #f9fbe7, #f0f4c3, #e6ee9c, #dce775, #d4e157,\n                                       #cddc39, #c0ca33, #afb42b, #9e9d24, #827717,\n                                       #f4ff81, #eeff41, #c6ff00, #aeea00;\n\n// Yellow\n@list-yellow:                          #fffde7, #fff9c4, #fff59d, #fff176, #ffee58,\n                                       #ffeb3b, #fdd835, #fbc02d, #f9a825, #f57f17,\n                                       #ffff8d, #ffff00, #ffea00, #ffd600;\n\n// Amber\n@list-amber:                           #fff8e1, #ffecb3, #ffe082, #ffd54f, #ffca28,\n                                       #ffc107, #ffb300, #ffa000, #ff8f00, #ff6f00,\n                                       #ffe57f, #ffd740, #ffc400, #ffab00;\n\n// Orange\n@list-orange:                          #fff3e0, #ffe0b2, #ffcc80, #ffb74d, #ffa726,\n                                       #ff9800, #fb8c00, #f57c00, #ef6c00, #e65100,\n                                       #ffd180, #ffab40, #ff9100, #ff6d00;\n\n// Deep Orange\n@list-deep-orange:                     #fbe9e7, #ffccbc, #ffab91, #ff8a65, #ff7043,\n                                       #ff5722, #f4511e, #e64a19, #d84315, #bf360c,\n                                       #ff9e80, #ff6e40, #ff3d00, #dd2c00;\n\n// Brown\n@list-brown:                           #efebe9, #d7ccc8, #bcaaa4, #a1887f, #8d6e63,\n                                       #795548, #6d4c41, #5d4037, #4e342e, #3e2723;\n\n// Grey\n@list-grey:                            #fafafa, #f5f5f5, #eeeeee, #e0e0e0, #bdbdbd,\n                                       #9e9e9e, #757575, #616161, #424242, #212121;\n\n// Blue Grey\n@list-blue-grey:                       #eceff1, #cfd8dc, #b0bec5, #90a4ae, #78909c,\n                                       #607d8b, #546e7a, #455a64, #37474f, #263238;\n\n\n\n// Definitions\n// --------------------------------------------------------------------------\n\n// Red\n@red:                              extract(@list-red, 6);\n\n@red-50:                           extract(@list-red, 1);\n@red-100:                          extract(@list-red, 2);\n@red-200:                          extract(@list-red, 3);\n@red-300:                          extract(@list-red, 4);\n@red-400:                          extract(@list-red, 5);\n@red-500:                          extract(@list-red, 6);\n@red-600:                          extract(@list-red, 7);\n@red-700:                          extract(@list-red, 8);\n@red-800:                          extract(@list-red, 9);\n@red-900:                          extract(@list-red, 10);\n@red-A100:                         extract(@list-red, 11);\n@red-A200:                         extract(@list-red, 12);\n@red-A400:                         extract(@list-red, 13);\n@red-A700:                         extract(@list-red, 14);\n\n\n// Pink\n@pink:                             extract(@list-pink, 6);\n\n@pink-50:                          extract(@list-pink, 1);\n@pink-100:                         extract(@list-pink, 2);\n@pink-200:                         extract(@list-pink, 3);\n@pink-300:                         extract(@list-pink, 4);\n@pink-400:                         extract(@list-pink, 5);\n@pink-500:                         extract(@list-pink, 6);\n@pink-600:                         extract(@list-pink, 7);\n@pink-700:                         extract(@list-pink, 8);\n@pink-800:                         extract(@list-pink, 9);\n@pink-900:                         extract(@list-pink, 10);\n@pink-A100:                        extract(@list-pink, 11);\n@pink-A200:                        extract(@list-pink, 12);\n@pink-A400:                        extract(@list-pink, 13);\n@pink-A700:                        extract(@list-pink, 14);\n\n\n// Purple\n@purple:                           extract(@list-purple, 6);\n\n@purple-50:                        extract(@list-purple, 1);\n@purple-100:                       extract(@list-purple, 2);\n@purple-200:                       extract(@list-purple, 3);\n@purple-300:                       extract(@list-purple, 4);\n@purple-400:                       extract(@list-purple, 5);\n@purple-500:                       extract(@list-purple, 6);\n@purple-600:                       extract(@list-purple, 7);\n@purple-700:                       extract(@list-purple, 8);\n@purple-800:                       extract(@list-purple, 9);\n@purple-900:                       extract(@list-purple, 10);\n@purple-A100:                      extract(@list-purple, 11);\n@purple-A200:                      extract(@list-purple, 12);\n@purple-A400:                      extract(@list-purple, 13);\n@purple-A700:                      extract(@list-purple, 14);\n\n\n// Deep Purple\n@deep-purple:                      extract(@list-deep-purple, 6);\n\n@deep-purple-50:                   extract(@list-deep-purple, 1);\n@deep-purple-100:                  extract(@list-deep-purple, 2);\n@deep-purple-200:                  extract(@list-deep-purple, 3);\n@deep-purple-300:                  extract(@list-deep-purple, 4);\n@deep-purple-400:                  extract(@list-deep-purple, 5);\n@deep-purple-500:                  extract(@list-deep-purple, 6);\n@deep-purple-600:                  extract(@list-deep-purple, 7);\n@deep-purple-700:                  extract(@list-deep-purple, 8);\n@deep-purple-800:                  extract(@list-deep-purple, 9);\n@deep-purple-900:                  extract(@list-deep-purple, 10);\n@deep-purple-A100:                 extract(@list-deep-purple, 11);\n@deep-purple-A200:                 extract(@list-deep-purple, 12);\n@deep-purple-A400:                 extract(@list-deep-purple, 13);\n@deep-purple-A700:                 extract(@list-deep-purple, 14);\n\n\n// Indigo\n@indigo:                           extract(@list-indigo, 6);\n\n@indigo-50:                        extract(@list-indigo, 1);\n@indigo-100:                       extract(@list-indigo, 2);\n@indigo-200:                       extract(@list-indigo, 3);\n@indigo-300:                       extract(@list-indigo, 4);\n@indigo-400:                       extract(@list-indigo, 5);\n@indigo-500:                       extract(@list-indigo, 6);\n@indigo-600:                       extract(@list-indigo, 7);\n@indigo-700:                       extract(@list-indigo, 8);\n@indigo-800:                       extract(@list-indigo, 9);\n@indigo-900:                       extract(@list-indigo, 10);\n@indigo-A100:                      extract(@list-indigo, 11);\n@indigo-A200:                      extract(@list-indigo, 12);\n@indigo-A400:                      extract(@list-indigo, 13);\n@indigo-A700:                      extract(@list-indigo, 14);\n\n\n// Blue\n@blue:                             extract(@list-blue, 6);\n\n@blue-50:                          extract(@list-blue, 1);\n@blue-100:                         extract(@list-blue, 2);\n@blue-200:                         extract(@list-blue, 3);\n@blue-300:                         extract(@list-blue, 4);\n@blue-400:                         extract(@list-blue, 5);\n@blue-500:                         extract(@list-blue, 6);\n@blue-600:                         extract(@list-blue, 7);\n@blue-700:                         extract(@list-blue, 8);\n@blue-800:                         extract(@list-blue, 9);\n@blue-900:                         extract(@list-blue, 10);\n@blue-A100:                        extract(@list-blue, 11);\n@blue-A200:                        extract(@list-blue, 12);\n@blue-A400:                        extract(@list-blue, 13);\n@blue-A700:                        extract(@list-blue, 14);\n\n\n// Light Blue\n@light-blue:                       extract(@list-light-blue, 6);\n\n@light-blue-50:                    extract(@list-light-blue, 1);\n@light-blue-100:                   extract(@list-light-blue, 2);\n@light-blue-200:                   extract(@list-light-blue, 3);\n@light-blue-300:                   extract(@list-light-blue, 4);\n@light-blue-400:                   extract(@list-light-blue, 5);\n@light-blue-500:                   extract(@list-light-blue, 6);\n@light-blue-600:                   extract(@list-light-blue, 7);\n@light-blue-700:                   extract(@list-light-blue, 8);\n@light-blue-800:                   extract(@list-light-blue, 9);\n@light-blue-900:                   extract(@list-light-blue, 10);\n@light-blue-A100:                  extract(@list-light-blue, 11);\n@light-blue-A200:                  extract(@list-light-blue, 12);\n@light-blue-A400:                  extract(@list-light-blue, 13);\n@light-blue-A700:                  extract(@list-light-blue, 14);\n\n\n// Cyan\n@cyan:                             extract(@list-cyan, 6);\n\n@cyan-50:                          extract(@list-cyan, 1);\n@cyan-100:                         extract(@list-cyan, 2);\n@cyan-200:                         extract(@list-cyan, 3);\n@cyan-300:                         extract(@list-cyan, 4);\n@cyan-400:                         extract(@list-cyan, 5);\n@cyan-500:                         extract(@list-cyan, 6);\n@cyan-600:                         extract(@list-cyan, 7);\n@cyan-700:                         extract(@list-cyan, 8);\n@cyan-800:                         extract(@list-cyan, 9);\n@cyan-900:                         extract(@list-cyan, 10);\n@cyan-A100:                        extract(@list-cyan, 11);\n@cyan-A200:                        extract(@list-cyan, 12);\n@cyan-A400:                        extract(@list-cyan, 13);\n@cyan-A700:                        extract(@list-cyan, 14);\n\n\n// Teal\n@teal:                             extract(@list-teal, 6);\n\n@teal-50:                          extract(@list-teal, 1);\n@teal-100:                         extract(@list-teal, 2);\n@teal-200:                         extract(@list-teal, 3);\n@teal-300:                         extract(@list-teal, 4);\n@teal-400:                         extract(@list-teal, 5);\n@teal-500:                         extract(@list-teal, 6);\n@teal-600:                         extract(@list-teal, 7);\n@teal-700:                         extract(@list-teal, 8);\n@teal-800:                         extract(@list-teal, 9);\n@teal-900:                         extract(@list-teal, 10);\n@teal-A100:                        extract(@list-teal, 11);\n@teal-A200:                        extract(@list-teal, 12);\n@teal-A400:                        extract(@list-teal, 13);\n@teal-A700:                        extract(@list-teal, 14);\n\n\n// Green\n@green:                            extract(@list-green, 6);\n\n@green-50:                         extract(@list-green, 1);\n@green-100:                        extract(@list-green, 2);\n@green-200:                        extract(@list-green, 3);\n@green-300:                        extract(@list-green, 4);\n@green-400:                        extract(@list-green, 5);\n@green-500:                        extract(@list-green, 6);\n@green-600:                        extract(@list-green, 7);\n@green-700:                        extract(@list-green, 8);\n@green-800:                        extract(@list-green, 9);\n@green-900:                        extract(@list-green, 10);\n@green-A100:                       extract(@list-green, 11);\n@green-A200:                       extract(@list-green, 12);\n@green-A400:                       extract(@list-green, 13);\n@green-A700:                       extract(@list-green, 14);\n\n\n// Light Green\n@light-green:                      extract(@list-light-green, 6);\n\n@light-green-50:                   extract(@list-light-green, 1);\n@light-green-100:                  extract(@list-light-green, 2);\n@light-green-200:                  extract(@list-light-green, 3);\n@light-green-300:                  extract(@list-light-green, 4);\n@light-green-400:                  extract(@list-light-green, 5);\n@light-green-500:                  extract(@list-light-green, 6);\n@light-green-600:                  extract(@list-light-green, 7);\n@light-green-700:                  extract(@list-light-green, 8);\n@light-green-800:                  extract(@list-light-green, 9);\n@light-green-900:                  extract(@list-light-green, 10);\n@light-green-A100:                 extract(@list-light-green, 11);\n@light-green-A200:                 extract(@list-light-green, 12);\n@light-green-A400:                 extract(@list-light-green, 13);\n@light-green-A700:                 extract(@list-light-green, 14);\n\n\n// Lime\n@lime:                             extract(@list-lime, 6);\n\n@lime-50:                          extract(@list-lime, 1);\n@lime-100:                         extract(@list-lime, 2);\n@lime-200:                         extract(@list-lime, 3);\n@lime-300:                         extract(@list-lime, 4);\n@lime-400:                         extract(@list-lime, 5);\n@lime-500:                         extract(@list-lime, 6);\n@lime-600:                         extract(@list-lime, 7);\n@lime-700:                         extract(@list-lime, 8);\n@lime-800:                         extract(@list-lime, 9);\n@lime-900:                         extract(@list-lime, 10);\n@lime-A100:                        extract(@list-lime, 11);\n@lime-A200:                        extract(@list-lime, 12);\n@lime-A400:                        extract(@list-lime, 13);\n@lime-A700:                        extract(@list-lime, 14);\n\n\n//Yellow\n@yellow:                           extract(@list-yellow, 6);\n\n@yellow-50:                        extract(@list-yellow, 1);\n@yellow-100:                       extract(@list-yellow, 2);\n@yellow-200:                       extract(@list-yellow, 3);\n@yellow-300:                       extract(@list-yellow, 4);\n@yellow-400:                       extract(@list-yellow, 5);\n@yellow-500:                       extract(@list-yellow, 6);\n@yellow-600:                       extract(@list-yellow, 7);\n@yellow-700:                       extract(@list-yellow, 8);\n@yellow-800:                       extract(@list-yellow, 9);\n@yellow-900:                       extract(@list-yellow, 10);\n@yellow-A100:                      extract(@list-yellow, 11);\n@yellow-A200:                      extract(@list-yellow, 12);\n@yellow-A400:                      extract(@list-yellow, 13);\n@yellow-A700:                      extract(@list-yellow, 14);\n\n\n// Amber\n@amber:                            extract(@list-amber, 6);\n\n@amber-50:                         extract(@list-amber, 1);\n@amber-100:                        extract(@list-amber, 2);\n@amber-200:                        extract(@list-amber, 3);\n@amber-300:                        extract(@list-amber, 4);\n@amber-400:                        extract(@list-amber, 5);\n@amber-500:                        extract(@list-amber, 6);\n@amber-600:                        extract(@list-amber, 7);\n@amber-700:                        extract(@list-amber, 8);\n@amber-800:                        extract(@list-amber, 9);\n@amber-900:                        extract(@list-amber, 10);\n@amber-A100:                       extract(@list-amber, 11);\n@amber-A200:                       extract(@list-amber, 12);\n@amber-A400:                       extract(@list-amber, 13);\n@amber-A700:                       extract(@list-amber, 14);\n\n\n// Orange\n@orange:                           extract(@list-orange, 6);\n\n@orange-50:                        extract(@list-orange, 1);\n@orange-100:                       extract(@list-orange, 2);\n@orange-200:                       extract(@list-orange, 3);\n@orange-300:                       extract(@list-orange, 4);\n@orange-400:                       extract(@list-orange, 5);\n@orange-500:                       extract(@list-orange, 6);\n@orange-600:                       extract(@list-orange, 7);\n@orange-700:                       extract(@list-orange, 8);\n@orange-800:                       extract(@list-orange, 9);\n@orange-900:                       extract(@list-orange, 10);\n@orange-A100:                      extract(@list-orange, 11);\n@orange-A200:                      extract(@list-orange, 12);\n@orange-A400:                      extract(@list-orange, 13);\n@orange-A700:                      extract(@list-orange, 14);\n\n\n// Deep Orange\n@deep-orange:                      extract(@list-deep-orange, 6);\n\n@deep-orange-50:                   extract(@list-deep-orange, 1);\n@deep-orange-100:                  extract(@list-deep-orange, 2);\n@deep-orange-200:                  extract(@list-deep-orange, 3);\n@deep-orange-300:                  extract(@list-deep-orange, 4);\n@deep-orange-400:                  extract(@list-deep-orange, 5);\n@deep-orange-500:                  extract(@list-deep-orange, 6);\n@deep-orange-600:                  extract(@list-deep-orange, 7);\n@deep-orange-700:                  extract(@list-deep-orange, 8);\n@deep-orange-800:                  extract(@list-deep-orange, 9);\n@deep-orange-900:                  extract(@list-deep-orange, 10);\n@deep-orange-A100:                 extract(@list-deep-orange, 11);\n@deep-orange-A200:                 extract(@list-deep-orange, 12);\n@deep-orange-A400:                 extract(@list-deep-orange, 13);\n@deep-orange-A700:                 extract(@list-deep-orange, 14);\n\n\n// Brown\n@brown:                            extract(@list-brown, 6);\n\n@brown-50:                         extract(@list-brown, 1);\n@brown-100:                        extract(@list-brown, 2);\n@brown-200:                        extract(@list-brown, 3);\n@brown-300:                        extract(@list-brown, 4);\n@brown-400:                        extract(@list-brown, 5);\n@brown-500:                        extract(@list-brown, 6);\n@brown-600:                        extract(@list-brown, 7);\n@brown-700:                        extract(@list-brown, 8);\n@brown-800:                        extract(@list-brown, 9);\n@brown-900:                        extract(@list-brown, 10);\n\n\n// Grey\n@grey:                             extract(@list-grey, 6);\n\n@grey-50:                          extract(@list-grey, 1);\n@grey-100:                         extract(@list-grey, 2);\n@grey-200:                         extract(@list-grey, 3);\n@grey-300:                         extract(@list-grey, 4);\n@grey-400:                         extract(@list-grey, 5);\n@grey-500:                         extract(@list-grey, 6);\n@grey-600:                         extract(@list-grey, 7);\n@grey-700:                         extract(@list-grey, 8);\n@grey-800:                         extract(@list-grey, 9);\n@grey-900:                         extract(@list-grey, 10);\n\n\n// Blue Grey\n@blue-grey:                        extract(@list-blue-grey, 6);\n\n@blue-grey-50:                     extract(@list-blue-grey, 1);\n@blue-grey-100:                    extract(@list-blue-grey, 2);\n@blue-grey-200:                    extract(@list-blue-grey, 3);\n@blue-grey-300:                    extract(@list-blue-grey, 4);\n@blue-grey-400:                    extract(@list-blue-grey, 5);\n@blue-grey-500:                    extract(@list-blue-grey, 6);\n@blue-grey-600:                    extract(@list-blue-grey, 7);\n@blue-grey-700:                    extract(@list-blue-grey, 8);\n@blue-grey-800:                    extract(@list-blue-grey, 9);\n@blue-grey-900:                    extract(@list-blue-grey, 10);\n\n\n\n// UI Color Application\n// --------------------------------------------------------------------------\n\n// Typography\n@ui-display-4:                     @grey-600;\n@ui-display-3:                     @grey-600;\n@ui-display-2:                     @grey-600;\n@ui-display-1:                     @grey-600;\n@ui-headline:                      @grey-900;\n@ui-title:                         @grey-900;\n@ui-subhead-1:                     @grey-900;\n@ui-body-2:                        @grey-900;\n@ui-body-1:                        @grey-900;\n@ui-caption:                       @grey-600;\n@ui-menu:                          @grey-900;\n@ui-button:                        @grey-900;"
  },
  {
    "path": "styles/utils/mixins.less",
    "content": "// Paper-like shadows based on 'zDepth'.\n.zDepth(0) {\n  box-shadow: none;\n  z-index: 0;\n}\n.zDepth(1) {\n  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.05), 0 1px 4px rgba(0, 0, 0, 0.12);\n  z-index: 1;\n}\n.zDepth(2) {\n  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.10), 0 3px 10px rgba(0, 0, 0, 0.18);\n  z-index: 2;\n}\n.zDepth(3) {\n  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15), 0 6px 10px rgba(0, 0, 0, 0.23);\n  z-index: 3;\n}\n.zDepth(4) {\n  box-shadow: 0 14px 45px rgba(0, 0, 0, 0.20), 0 10px 18px rgba(0, 0, 0, 0.22);\n  z-index: 4;\n}\n.zDepth(5) {\n  box-shadow: 0 19px 60px rgba(0, 0, 0, 0.25), 0 15px 20px rgba(0, 0, 0, 0.22);\n  z-index: 5;\n}"
  },
  {
    "path": "styles/utils/variables.less",
    "content": "// Fonts\n@font-regular: 'Roboto';\n@font-code: Menlo, Consolas;\n\n// Sizes\n@toolbarHeight: 56px;\n@space: 16px;\n\n// Extra Colors\n@black: #000;\n@white: #fff;"
  }
]