[
  {
    "path": ".babelrc",
    "content": "{\n  \"ignore\": [\n    \"buffer\"\n  ],\n  \"plugins\": [\n    \"@babel/plugin-proposal-object-rest-spread\",\n    \"@babel/plugin-syntax-dynamic-import\",\n    \"@babel/plugin-proposal-class-properties\"\n  ],\n  \"presets\": [\n    [\n      \"@babel/preset-env\",\n      {\n        \"targets\": {\n          \"chrome\": \"69\"\n        }\n      }\n    ],\n    \"@babel/preset-react\"\n  ]\n}\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [luin]\ncustom: https://apps.apple.com/us/app/medis-2-gui-for-redis/id1579200037\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n.DS_Store\ndist\nnpm-debug.log\n*.provisionprofile\n.awcache\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "<a name=\"0.6.1\"></a>\n## [0.6.1](https://github.com/luin/medis/compare/v0.5.0...v0.6.1) (2017-02-19)\n\n\n### Bug Fixes\n\n* detect database number for Heroku Redis ([f2c6d7e](https://github.com/luin/medis/commit/f2c6d7e)), closes [#55](https://github.com/luin/medis/issues/55) [#52](https://github.com/luin/medis/issues/52)\n* UI for edit button ([3599392](https://github.com/luin/medis/commit/3599392))\n* zset delete wrong element when sorting desc ([3d3f29a](https://github.com/luin/medis/commit/3d3f29a)), closes [#60](https://github.com/luin/medis/issues/60)\n\n### Features\n\n* support search/find within a key ([9ecce73](https://github.com/luin/medis/commit/9ecce73)), closes [#61](https://github.com/luin/medis/issues/61)\n\n\n\n<a name=\"0.6.0\"></a>\n# [0.6.0](https://github.com/luin/medis/compare/v0.5.0...v0.6.0) (2017-02-19)\n\n\n### Bug Fixes\n\n* detect database number for Heroku Redis ([f2c6d7e](https://github.com/luin/medis/commit/f2c6d7e)), closes [#55](https://github.com/luin/medis/issues/55) [#52](https://github.com/luin/medis/issues/52)\n* UI for edit button ([3599392](https://github.com/luin/medis/commit/3599392))\n* zset delete wrong element when sorting desc ([3d3f29a](https://github.com/luin/medis/commit/3d3f29a)), closes [#60](https://github.com/luin/medis/issues/60)\n\n### Features\n\n* support search/find within a key ([9ecce73](https://github.com/luin/medis/commit/9ecce73)), closes [#61](https://github.com/luin/medis/issues/61)\n\n\n\n<a name=\"0.5.0\"></a>\n# [0.5.0](https://github.com/luin/medis/compare/v0.3.0...v0.5.0) (2016-12-04)\n\n\n### Bug Fixes\n\n* check err first before update the database status ([dd46cc3](https://github.com/luin/medis/commit/dd46cc3))\n* clear the state before leaving the favorite page ([498a077](https://github.com/luin/medis/commit/498a077))\n* don't show error multiple times when lost connection to SSH tunnel ([2b732bd](https://github.com/luin/medis/commit/2b732bd))\n* fix psubscribe not working. Close #32 ([586a943](https://github.com/luin/medis/commit/586a943)), closes [#32](https://github.com/luin/medis/issues/32)\n* provide details error when connection is failed ([99d2757](https://github.com/luin/medis/commit/99d2757))\n* tweak config panel style ([d92faf2](https://github.com/luin/medis/commit/d92faf2))\n* ui issues when switching between tabs ([330f52f](https://github.com/luin/medis/commit/330f52f)), closes [#1](https://github.com/luin/medis/issues/1)\n\n### Features\n\n* add support for SSL connection. ([ca29384](https://github.com/luin/medis/commit/ca29384)), closes [#41](https://github.com/luin/medis/issues/41)\n* allow quick connecting by double clicking ([53a284e](https://github.com/luin/medis/commit/53a284e))\n* support Elastic Cache Redis & RedisLabs for selecting database ([18e5629](https://github.com/luin/medis/commit/18e5629))\n* support to duplicate favorites ([c2bc438](https://github.com/luin/medis/commit/c2bc438)), closes [#30](https://github.com/luin/medis/issues/30)\n* use Consolas font instead ([bd9d1c9](https://github.com/luin/medis/commit/bd9d1c9)), closes [#2](https://github.com/luin/medis/issues/2) [#39](https://github.com/luin/medis/issues/39)\n\n\n\n<a name=\"0.5.0\"></a>\n# [0.5.0](https://github.com/luin/medis/compare/v0.3.0...v0.5.0) (2016-12-04)\n\n\n### Bug Fixes\n\n* check err first before update the database status ([dd46cc3](https://github.com/luin/medis/commit/dd46cc3))\n* clear the state before leaving the favorite page ([498a077](https://github.com/luin/medis/commit/498a077))\n* don't show error multiple times when lost connection to SSH tunnel ([2b732bd](https://github.com/luin/medis/commit/2b732bd))\n* fix psubscribe not working. Close #32 ([586a943](https://github.com/luin/medis/commit/586a943)), closes [#32](https://github.com/luin/medis/issues/32)\n* provide details error when connection is failed ([99d2757](https://github.com/luin/medis/commit/99d2757))\n* tweak config panel style ([d92faf2](https://github.com/luin/medis/commit/d92faf2))\n* ui issues when switching between tabs ([330f52f](https://github.com/luin/medis/commit/330f52f)), closes [#1](https://github.com/luin/medis/issues/1)\n\n### Features\n\n* add support for SSL connection. ([ca29384](https://github.com/luin/medis/commit/ca29384)), closes [#41](https://github.com/luin/medis/issues/41)\n* allow quick connecting by double clicking ([53a284e](https://github.com/luin/medis/commit/53a284e))\n* support Elastic Cache Redis & RedisLabs for selecting database ([18e5629](https://github.com/luin/medis/commit/18e5629))\n* support to duplicate favorites ([c2bc438](https://github.com/luin/medis/commit/c2bc438)), closes [#30](https://github.com/luin/medis/issues/30)\n* use Consolas font instead ([bd9d1c9](https://github.com/luin/medis/commit/bd9d1c9)), closes [#2](https://github.com/luin/medis/issues/2) [#39](https://github.com/luin/medis/issues/39)\n\n\n\n<a name=\"0.3.0\"></a>\n# [0.3.0](https://github.com/luin/medis/compare/v0.2.1...v0.3.0) (2016-03-25)\n\n\n### Bug Fixes\n\n* **windows:** hide app menu in Windows version ([d31bd6c](https://github.com/luin/medis/commit/d31bd6c))\n\n### Features\n\n* support inputing spaces in terminal ([04e7bcf](https://github.com/luin/medis/commit/04e7bcf)), closes [#24](https://github.com/luin/medis/issues/24)\n\n\n\n<a name=\"0.2.1\"></a>\n## [0.2.1](https://github.com/luin/medis/compare/v0.2.0...v0.2.1) (2016-02-01)\n\n\n### Bug Fixes\n\n* **ssh:** fix ssh password being ignored ([4dfdbcd](https://github.com/luin/medis/commit/4dfdbcd)), closes [#13](https://github.com/luin/medis/issues/13)\n\n\n\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016-2022 Zihua Li\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Medis\n\n### Notice: We just released Medis 2! 🚀🚀🚀\n\nCompared to Medis (this repo), Medis 2 provides more delightful features, such as **tree view** (yes, finally!), streams, alert mode, **dark mode**, and more. Besides that, Medis 2 is rewritten from the beginning with native technology, making it more morden, beautiful, and fast!\n\nWhat's more, **Medis 2 is free 💰 to download**! Don't hesitate, download it from the App Store now and try it out!\n\n[![Download on the App Store](http://getmedis.com/download.svg)](https://apps.apple.com/us/app/medis-2-gui-for-redis/id1579200037?mt=12)\n\n_(or searching \"Medis 2\" on macOS App Store if the above link doesn't work for you. Also, you can download the app directly from the [official website](https://getmedis.com/))_\n\n![Medis](http://getmedis.com/screen.png)\n\n---\n\nMedis is a beautiful, easy-to-use Redis management application built on the modern web with [Electron](https://github.com/atom/electron), [React](https://facebook.github.io/react/), and [Redux](https://github.com/rackt/redux). It's powered by many awesome Node.js modules, especially [ioredis](https://github.com/luin/ioredis) and [ssh2](https://github.com/mscdex/ssh2).\n\n[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)\n\nMedis starts with all the basic features you need:\n\n- Keys viewing/editing\n- SSH Tunnel for connecting with remote servers\n- Terminal for executing custom commands\n- Config viewing/editing\n\nIt also supports many advanced features:\n\n- JSON/MessagePack format viewing/editing and built-in highlighting/validator\n- Working with millions keys and key members without blocking the redis server\n- Pattern manager for easy selecting a sub group of keys.\n\n**Note**: Medis only supports Redis >= 2.8 version because `SCAN` command was introduced since 2.8. `SCAN` is very useful to get key list without blocking the server, which is crucial to the production environment. Because the latest stable is 5.0 and 2.6 is a very old version, Medis doesn't support it.\n\n<hr />\n\n## Download Medis on Windows\n\nYou can download compiled installer of Medis for Windows from the below page\n[download page](https://github.com/classfellow/medis/releases/tag/win)\n\n## Download Medis on Mac\n\nYou can download compiled versions of Medis for Mac OS X from [the release page](https://github.com/luin/medis/releases).\n\n## Running Locally\n\n1. Install dependencies\n\n```\n    $ npm install\n```\n\n2. Compile assets:\n\n```\n    $ npm run pack\n```\n\n3. Run with Electron:\n\n```\n    $ npm start\n```\n\n## Connect to Heroku\n\nMedis can connect to Heroku Redis addon to manage your data. You just need to call `heroku redis:credentials --app APP` to get your redis credential:\n\n```shell\n$ heroku redis:credentials --app YOUR_APP\nredis://x:PASSWORD@HOST:PORT\n```\n\nAnd then input `HOST`, `PORT` and `PASSWORD` to the connection tab.\n\n## I Love This. How do I Help?\n\n- Simply star this repository :-)\n- Help us spread the world on Facebook and Twitter\n- Contribute Code! We're developers! (See Roadmap below)\n- Medis is available on the Mac App Store as a paid software. I'll be very grateful if you'd like to buy it to encourage me to continue maintaining Medis. There are no additional features comparing with the open-sourced version, except the fact that you can enjoy auto updating that brought by the Mac App Store. <br> [![Download on the App Store](http://getmedis.com/download.svg)](https://apps.apple.com/us/app/medis-2-gui-for-redis/id1579200037?mt=12)\n\n## Roadmap\n\n- Windows and Linux version (with electron-packager)\n- Support for SaaS Redis services\n- Lua script editor\n- Cluster management\n- GEO keys supporting\n\n## Contributors\n\n<table><tr><td width=\"20%\"><a href=\"https://github.com/luin\"><img src=\"https://avatars1.githubusercontent.com/u/635902?v=3\" /></a><p align=\"center\">luin</p></td><td width=\"20%\"><a href=\"https://github.com/kvnsmth\"><img src=\"https://avatars0.githubusercontent.com/u/127?v=3\" /></a><p align=\"center\">kvnsmth</p></td><td width=\"20%\"><a href=\"https://github.com/dpde\"><img src=\"https://avatars2.githubusercontent.com/u/485645?v=3\" /></a><p align=\"center\">dpde</p></td><td width=\"20%\"><a href=\"https://github.com/ogasawaraShinnosuke\"><img src=\"https://avatars1.githubusercontent.com/u/5368888?v=3\" /></a><p align=\"center\">ogasawaraShinnosuke</p></td><td width=\"20%\"><a href=\"https://github.com/naholyr\"><img src=\"https://avatars1.githubusercontent.com/u/214067?v=3\" /></a><p align=\"center\">naholyr</p></td></tr><tr><td width=\"20%\"><a href=\"https://github.com/hlobil\"><img src=\"https://avatars2.githubusercontent.com/u/484499?v=3\" /></a><p align=\"center\">hlobil</p></td><td width=\"20%\"><a href=\"https://github.com/Janpot\"><img src=\"https://avatars1.githubusercontent.com/u/2109932?v=3\" /></a><p align=\"center\">Janpot</p></td></table>\n\n## License\n\nMIT\n"
  },
  {
    "path": "bin/pack.js",
    "content": "const packager = require('electron-packager')\nconst path = require('path')\nconst pkg = require('../package')\nconst flat = require('electron-osx-sign').flat\n\nconst resourcesPath = path.join(__dirname, '..', 'resources')\n\npackager({\n  dir: path.join(__dirname, '..'),\n  appCopyright: '© 2019, Zihua Li',\n  asar: true,\n  overwrite: true,\n  electronVersion: pkg.electronVersion,\n  icon: path.join(resourcesPath, 'icns', 'MyIcon'),\n  out: path.join(__dirname, '..', 'dist', 'out'),\n  platform: 'mas',\n  appBundleId: `li.zihua.${pkg.name}`,\n  appCategoryType: 'public.app-category.developer-tools',\n  osxSign: {\n    type: process.env.NODE_ENV === 'production' ? 'distribution' : 'development',\n    entitlements: path.join(resourcesPath, 'parent.plist'),\n    'entitlements-inherit': path.join(resourcesPath, 'child.plist')\n  }\n}).then((res) => {\n  const app = path.join(res[0], `${pkg.productName}.app`)\n  console.log('flating...', app)\n  flat({ app }, function done (err) {\n    if (err) {\n      throw err\n    }\n    process.exit(0);\n  })\n})\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"medis\",\n  \"description\": \"GUI for Redis\",\n  \"productName\": \"Medis\",\n  \"version\": \"1.0.3\",\n  \"electronVersion\": \"4.0.0\",\n  \"license\": \"MIT\",\n  \"author\": \"luin <i@zihua.li> (http://zihua.li)\",\n  \"main\": \"dist/main\",\n  \"scripts\": {\n    \"build\": \"rm -rf dist && webpack\",\n    \"watch\": \"WEBPACK_WATCH=true npm run build\",\n    \"start\": \"electron .\",\n    \"pack\": \"NODE_ENV=production npm run build && node bin/pack.js\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/luin/medis.git\"\n  },\n  \"dependencies\": {\n    \"electron\": \"^4.2.2\",\n    \"electron-context-menu\": \"^0.12.1\",\n    \"fixed-data-table-contextmenu\": \"^1.7.2\",\n    \"ioredis\": \"^4.9.3\",\n    \"jquery\": \"^3.4.1\",\n    \"jquery.terminal\": \"^2.5.1\",\n    \"lodash.escape\": \"^4.0.1\",\n    \"lodash.sortedindexby\": \"^4.6.0\",\n    \"medis-react-codemirror\": \"^1.1.1\",\n    \"redis-commands\": \"^1.5.0\",\n    \"ssh2\": \"^0.8.9\",\n    \"xterm\": \"^3.13.1\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.4.4\",\n    \"@babel/plugin-proposal-class-properties\": \"^7.4.4\",\n    \"@babel/plugin-proposal-object-rest-spread\": \"^7.4.4\",\n    \"@babel/plugin-syntax-dynamic-import\": \"^7.2.0\",\n    \"@babel/preset-env\": \"^7.4.4\",\n    \"@babel/preset-react\": \"^7.0.0\",\n    \"@types/jquery\": \"^3.3.29\",\n    \"@types/react\": \"^16.8.17\",\n    \"@types/react-dom\": \"^16.8.4\",\n    \"@types/redux-actions\": \"^2.6.1\",\n    \"awesome-typescript-loader\": \"^5.2.1\",\n    \"babel-loader\": \"^8.0.6\",\n    \"codemirror\": \"^5.46.0\",\n    \"css-loader\": \"^2.1.1\",\n    \"electron-osx-sign\": \"^0.4.4\",\n    \"electron-packager\": \"^13.1.1\",\n    \"file-loader\": \"^3.0.1\",\n    \"html-webpack-plugin\": \"^3.2.0\",\n    \"human-format\": \"^0.10.1\",\n    \"immutable\": \"^3.8.1\",\n    \"json-editor\": \"^0.7.23\",\n    \"jsonlint\": \"^1.6.2\",\n    \"jsx-loader\": \"^0.13.2\",\n    \"lint\": \"^1.1.2\",\n    \"lodash.clone\": \"^4.5.0\",\n    \"lodash.zip\": \"^4.2.0\",\n    \"mini-css-extract-plugin\": \"^0.6.0\",\n    \"minimatch\": \"^3.0.4\",\n    \"msgpack5\": \"^4.2.1\",\n    \"node-sass\": \"^4.12.0\",\n    \"prop-types\": \"^15.7.2\",\n    \"react\": \"^16.8.6\",\n    \"react-addons-css-transition-group\": \"^15.5.2\",\n    \"react-document-title\": \"^2.0.1\",\n    \"react-dom\": \"^16.8.6\",\n    \"react-redux\": \"^7.0.3\",\n    \"react-sortable-hoc\": \"^1.9.1\",\n    \"react-split-pane\": \"^0.1.87\",\n    \"redis-splitargs\": \"^1.0.1\",\n    \"redux\": \"^4.0.1\",\n    \"redux-actions\": \"^2.6.5\",\n    \"reselect\": \"^4.0.0\",\n    \"sass-loader\": \"^7.1.0\",\n    \"sortablejs\": \"^1.9.0\",\n    \"typescript\": \"^3.4.5\",\n    \"url-loader\": \"^1.1.2\",\n    \"webpack\": \"^4.31.0\",\n    \"webpack-bundle-analyzer\": \"^3.3.2\",\n    \"webpack-cli\": \"^3.3.2\"\n  }\n}\n"
  },
  {
    "path": "resources/child.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n  <dict>\n    <key>com.apple.security.app-sandbox</key>\n    <true/>\n    <key>com.apple.security.inherit</key>\n    <true/>\n  </dict>\n</plist>\n"
  },
  {
    "path": "resources/icns/generate",
    "content": "mkdir MyIcon.iconset\nsips -z 16 16     Icon1024.png --out MyIcon.iconset/icon_16x16.png\nsips -z 32 32     Icon1024.png --out MyIcon.iconset/icon_16x16@2x.png\nsips -z 32 32     Icon1024.png --out MyIcon.iconset/icon_32x32.png\nsips -z 64 64     Icon1024.png --out MyIcon.iconset/icon_32x32@2x.png\nsips -z 128 128   Icon1024.png --out MyIcon.iconset/icon_128x128.png\nsips -z 256 256   Icon1024.png --out MyIcon.iconset/icon_128x128@2x.png\nsips -z 256 256   Icon1024.png --out MyIcon.iconset/icon_256x256.png\nsips -z 512 512   Icon1024.png --out MyIcon.iconset/icon_256x256@2x.png\nsips -z 512 512   Icon1024.png --out MyIcon.iconset/icon_512x512.png\ncp Icon1024.png MyIcon.iconset/icon_512x512@2x.png\niconutil -c icns MyIcon.iconset\nrm -R MyIcon.iconset\n"
  },
  {
    "path": "resources/parent.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n  <dict>\n    <key>com.apple.security.app-sandbox</key>\n    <true/>\n    <key>com.apple.security.files.user-selected.read-only</key>\n    <true/>\n    <key>com.apple.security.network.client</key>\n    <true/>\n    <key>com.apple.security.network.server</key>\n    <true/>\n  </dict>\n</plist>\n"
  },
  {
    "path": "src/main/index.ts",
    "content": "import {app, Menu, ipcMain} from 'electron'\nimport windowManager from './windowManager'\nimport menu from './menu'\nconst contextMenu = require('electron-context-menu');\n\ncontextMenu({\n  // showInspectElement: true,\n})\n\nipcMain.on('create patternManager', function (event, arg) {\n  windowManager.create('patternManager', arg)\n})\n\nipcMain.on('dispatch', function (event, action, arg) {\n  windowManager.dispatch(action, arg)\n})\n\n// Quit when all windows are closed.\napp.on('window-all-closed', function () {\n  if (process.platform !== 'darwin') {\n    app.quit()\n  }\n})\n\n// This method will be called when Electron has finished\n// initialization and is ready to create browser windows.\napp.on('ready', function () {\n  Menu.setApplicationMenu(menu)\n  windowManager.create()\n\n  app.on('activate', function (_, hasVisibleWindows) {\n    if (!hasVisibleWindows) {\n      windowManager.create()\n    }\n  })\n})\n"
  },
  {
    "path": "src/main/menu.ts",
    "content": "import {app, Menu, MenuItemConstructorOptions} from 'electron'\nimport windowManager from './windowManager'\n\nconst menuTemplates: MenuItemConstructorOptions[] = [{\n  label: 'File',\n  submenu: [{\n    label: 'New Connection Window',\n    accelerator: 'CmdOrCtrl+N',\n    click() {\n      windowManager.create()\n    }\n  }, {\n    label: 'New Connection Tab',\n    accelerator: 'CmdOrCtrl+T',\n    click() {\n      windowManager.current.webContents.send('action', 'createInstance')\n    }\n  }, {\n    type: 'separator'\n  }, {\n    label: 'Close Window',\n    accelerator: 'Shift+CmdOrCtrl+W',\n    click() {\n      windowManager.current.close()\n    }\n  }, {\n    label: 'Close Tab',\n    accelerator: 'CmdOrCtrl+W',\n    click() {\n      windowManager.current.webContents.send('action', 'delInstance')\n    }\n  }]\n}, {\n  label: 'Edit',\n  submenu: [{\n    label: 'Undo',\n    accelerator: 'CmdOrCtrl+Z',\n    role: 'undo'\n  }, {\n    label: 'Redo',\n    accelerator: 'Shift+CmdOrCtrl+Z',\n    role: 'redo'\n  }, {\n    type: 'separator'\n  }, {\n    label: 'Cut',\n    accelerator: 'CmdOrCtrl+X',\n    role: 'cut'\n  }, {\n    label: 'Copy',\n    accelerator: 'CmdOrCtrl+C',\n    role: 'copy'\n  }, {\n    label: 'Paste',\n    accelerator: 'CmdOrCtrl+V',\n    role: 'paste'\n  }, {\n    label: 'Select All',\n    accelerator: 'CmdOrCtrl+A',\n    role: 'selectall'\n  }]\n}, {\n  label: 'View',\n  submenu: [{\n    label: 'Reload',\n    accelerator: 'CmdOrCtrl+R',\n    click(item, focusedWindow) {\n      if (focusedWindow) {\n        focusedWindow.reload()\n      }\n    }\n  }, {\n    label: 'Toggle Full Screen',\n    accelerator: (function () {\n      if (process.platform === 'darwin') {\n        return 'Ctrl+Command+F'\n      }\n      return 'F11'\n    })(),\n    click(item, focusedWindow) {\n      if (focusedWindow) {\n        focusedWindow.setFullScreen(!focusedWindow.isFullScreen())\n      }\n    }\n  }, {\n    label: 'Toggle Developer Tools',\n    accelerator: (function () {\n      if (process.platform === 'darwin') {\n        return 'Alt+Command+I'\n      }\n      return 'Ctrl+Shift+I'\n    })(),\n    click(item, focusedWindow) {\n      if (focusedWindow) {\n        focusedWindow.webContents.toggleDevTools()\n      }\n    }\n  }]\n}, {\n  label: 'Window',\n  role: 'window',\n  submenu: [{\n    label: 'Minimize',\n    accelerator: 'CmdOrCtrl+M',\n    role: 'minimize'\n  }, {\n    label: 'Close',\n    accelerator: 'CmdOrCtrl+W',\n    role: 'close'\n  }]\n}, {\n  label: 'Help',\n  role: 'help',\n  submenu: [{\n    label: 'Report an Issue...',\n    click() {\n      require('shell').openExternal('mailto:medis@zihua.li')\n    }\n  }, {\n    label: 'Learn More',\n    click() {\n      require('shell').openExternal('http://getmedis.com')\n    }\n  }]\n}]\n\nlet baseIndex = 0\nif (process.platform == 'darwin') {\n  baseIndex = 1\n  menuTemplates.unshift({\n    label: app.getName(),\n    submenu: [{\n      label: 'About ' + app.getName(),\n      role: 'about'\n    }, {\n      type: 'separator'\n    }, {\n      label: 'Services',\n      role: 'services',\n      submenu: []\n    }, {\n      type: 'separator'\n    }, {\n      label: 'Hide ' + app.getName(),\n      accelerator: 'Command+H',\n      role: 'hide'\n    }, {\n      label: 'Hide Others',\n      accelerator: 'Command+Shift+H',\n      role: 'hideothers'\n    }, {\n      label: 'Show All',\n      role: 'unhide'\n    }, {\n      type: 'separator'\n    }, {\n      label: 'Quit',\n      accelerator: 'Command+Q',\n      click() {\n        app.quit()\n      }\n    }]\n  })\n}\n\nconst menu = Menu.buildFromTemplate(menuTemplates)\n\nif (process.env.NODE_ENV === 'production') {\n  const {submenu} = (menu.items[baseIndex + 2] as any)\n  submenu.items[0].visible = false\n  submenu.items[2].visible = false\n}\n\nconst {submenu} = (menu.items[baseIndex + 0] as any)\nwindowManager.on('blur', function () {\n  submenu.items[3].enabled = false\n  submenu.items[4].enabled = false\n})\n\nwindowManager.on('focus', function () {\n  const {submenu} = (menu.items[baseIndex + 0] as any)\n  submenu.items[3].enabled = true\n  submenu.items[4].enabled = true\n})\n\nexport default menu\n"
  },
  {
    "path": "src/main/windowManager.ts",
    "content": "import {app, BrowserWindow, BrowserWindowConstructorOptions} from 'electron'\nimport path from 'path'\nimport EventEmitter from 'events'\n\nclass WindowManager extends EventEmitter {\n  windows = new Set<BrowserWindow>()\n\n  constructor() {\n    super()\n    app.on('browser-window-blur', this.emit.bind(this, 'blur'))\n    app.on('browser-window-focus', this.emit.bind(this, 'focus'))\n  }\n\n  get current() {\n    return BrowserWindow.getFocusedWindow() || this.create()\n  }\n\n  create(type = 'main', arg?: any): BrowserWindow {\n    const option: BrowserWindowConstructorOptions = {\n      backgroundColor: '#ececec',\n      webPreferences: {\n        nodeIntegration: true\n      }\n    }\n    if (type === 'main') {\n      option.width = 960\n      option.height = 600\n      option.show = false\n      option.minWidth = 840\n      option.minHeight = 400\n    } else if (type === 'patternManager') {\n      option.width = 600\n      option.height = 300\n      option.title = 'Manage Patterns'\n      option.resizable = true\n      option.fullscreen = false\n    }\n\n    let start: number\n    const newWindow = new BrowserWindow(option)\n    if (!option.show) {\n      newWindow.once('ready-to-show', () => {\n        console.log('start time: ', Date.now() - start)\n        newWindow.show()\n      })\n    }\n\n    start = Date.now()\n    newWindow.loadFile(path.resolve(__dirname, `../renderer/${type}.html`), {query: {arg}})\n\n    this._register(newWindow)\n\n    return newWindow\n  }\n\n  _register(win: BrowserWindow): void {\n    this.windows.add(win)\n    win.on('closed', () => {\n      this.windows.delete(win)\n      if (!BrowserWindow.getFocusedWindow()) {\n        this.emit('blur')\n      }\n    })\n    this.emit('focus')\n  }\n\n  dispatch(action: string, args: any) {\n    this.windows.forEach(win => {\n      if (win && win.webContents) {\n        win.webContents.send('action', action, args)\n      }\n    })\n  }\n}\n\nexport default new WindowManager()\n"
  },
  {
    "path": "src/renderer/photon/css/photon.css",
    "content": "/*!\n * =====================================================\n * Photon v0.1.0\n * Copyright 2015 Connor Sears\n * Licensed under MIT (https://github.com/connors/proton/blob/master/LICENSE)\n *\n * v0.1.0 designed by @connors.\n * =====================================================\n */\n\n@charset \"UTF-8\";\naudio,\ncanvas,\nprogress,\nvideo {\n  vertical-align: baseline;\n}\n\naudio:not([controls]) {\n  display: none;\n}\n\na:active,\na:hover {\n  outline: 0;\n}\n\nabbr[title] {\n  border-bottom: 1px dotted;\n}\n\nb,\nstrong {\n  font-weight: bold;\n}\n\ndfn {\n  font-style: italic;\n}\n\nh1 {\n  font-size: 2em;\n  margin: 0.67em 0;\n}\n\nsmall {\n  font-size: 80%;\n}\n\nsub,\nsup {\n  font-size: 75%;\n  line-height: 0;\n  position: relative;\n  vertical-align: baseline;\n}\n\nsup {\n  top: -0.5em;\n}\n\nsub {\n  bottom: -0.25em;\n}\n\npre {\n  overflow: auto;\n}\n\ncode,\nkbd,\npre,\nsamp {\n  font-family: monospace, monospace;\n  font-size: 1em;\n}\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  color: inherit;\n  font: inherit;\n  margin: 0;\n}\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n  height: auto;\n}\n\ninput[type=\"search\"] {\n  -webkit-appearance: textfield;\n  box-sizing: content-box;\n}\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n\nfieldset {\n  border: 1px solid #c0c0c0;\n  margin: 0 2px;\n  padding: 0.35em 0.625em 0.75em;\n}\n\nlegend {\n  border: 0;\n  padding: 0;\n}\n\ntable {\n  border-collapse: collapse;\n  border-spacing: 0;\n}\n\ntd,\nth {\n  padding: 0;\n}\n\n* {\n  cursor: default;\n  -webkit-user-drag: text;\n  -webkit-user-select: none;\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n}\n\nhtml {\n  height: 100%;\n  width: 100%;\n  overflow: hidden;\n}\n\nbody {\n  height: 100%;\n  padding: 0;\n  margin: 0;\n  font-family: system, -apple-system, \".SFNSDisplay-Regular\", \"Helvetica Neue\", Helvetica, \"Segoe UI\", sans-serif;\n  font-size: 13px;\n  line-height: 1.6;\n  color: #333;\n  background-color: transparent;\n}\n\nhr {\n  margin: 15px 0;\n  overflow: hidden;\n  background: transparent;\n  border: 0;\n  border-bottom: 1px solid #ddd;\n}\n\nh1, h2, h3, h4, h5, h6 {\n  margin-top: 20px;\n  margin-bottom: 10px;\n  font-weight: 500;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\nh1 {\n  font-size: 36px;\n}\n\nh2 {\n  font-size: 30px;\n}\n\nh3 {\n  font-size: 24px;\n}\n\nh4 {\n  font-size: 18px;\n}\n\nh5 {\n  font-size: 14px;\n}\n\nh6 {\n  font-size: 12px;\n}\n\n.window {\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  display: flex;\n  flex-direction: column;\n  background-color: #ececec;\n}\n\n.window-content {\n  position: relative;\n  overflow-y: auto;\n  display: flex;\n  flex: 1;\n}\n\n.selectable-text {\n  cursor: text;\n  -webkit-user-select: text;\n}\n\n.text-center {\n  text-align: center;\n}\n\n.text-right {\n  text-align: right;\n}\n\n.text-left {\n  text-align: left;\n}\n\n.pull-left {\n  float: left;\n}\n\n.pull-right {\n  float: right;\n}\n\n.padded {\n  padding: 10px;\n}\n\n.padded-less {\n  padding: 5px;\n}\n\n.padded-more {\n  padding: 20px;\n}\n\n.padded-vertically {\n  padding-top: 10px;\n  padding-bottom: 10px;\n}\n\n.padded-vertically-less {\n  padding-top: 5px;\n  padding-bottom: 5px;\n}\n\n.padded-vertically-more {\n  padding-top: 20px;\n  padding-bottom: 20px;\n}\n\n.padded-horizontally {\n  padding-right: 10px;\n  padding-left: 10px;\n}\n\n.padded-horizontally-less {\n  padding-right: 5px;\n  padding-left: 5px;\n}\n\n.padded-horizontally-more {\n  padding-right: 20px;\n  padding-left: 20px;\n}\n\n.padded-top {\n  padding-top: 10px;\n}\n\n.padded-top-less {\n  padding-top: 5px;\n}\n\n.padded-top-more {\n  padding-top: 20px;\n}\n\n.padded-bottom {\n  padding-bottom: 10px;\n}\n\n.padded-bottom-less {\n  padding-bottom: 5px;\n}\n\n.padded-bottom-more {\n  padding-bottom: 20px;\n}\n\n.sidebar {\n  background-color: #f5f5f4;\n}\n\n.clearfix:before, .clearfix:after {\n  display: table;\n  content: \" \";\n}\n.clearfix:after {\n  clear: both;\n}\n\n.btn {\n  display: inline-block;\n  padding: 3px 8px;\n  margin-bottom: 0;\n  font-size: 12px;\n  line-height: 1.4;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: middle;\n  cursor: default;\n  background-image: none;\n  border: 1px solid transparent;\n  border-radius: 4px;\n  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.06);\n  -webkit-app-region: no-drag;\n}\n.btn:focus {\n  outline: none;\n  box-shadow: none;\n}\n\n.btn-mini {\n  padding: 2px 6px;\n}\n\n.btn-large {\n  padding: 6px 12px;\n}\n\n.btn-form {\n  padding-right: 20px;\n  padding-left: 20px;\n}\n\n.btn-default {\n  color: #333;\n  border-top-color: #c2c0c2;\n  border-right-color: #c2c0c2;\n  border-bottom-color: #a19fa1;\n  border-left-color: #c2c0c2;\n  background-color: #fcfcfc;\n  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fcfcfc), color-stop(100%, #f1f1f1));\n  background-image: -webkit-linear-gradient(top, #fcfcfc 0%, #f1f1f1 100%);\n  background-image: linear-gradient(to bottom, #fcfcfc 0%, #f1f1f1 100%);\n}\n.btn-default:active {\n  background-color: #ddd;\n  background-image: none;\n}\n\n.btn-primary,\n.btn-positive,\n.btn-negative,\n.btn-warning {\n  color: #fff;\n  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);\n}\n\n.btn-primary {\n  border-color: #388df8;\n  border-bottom-color: #0866dc;\n  background-color: #6eb4f7;\n  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #6eb4f7), color-stop(100%, #1a82fb));\n  background-image: -webkit-linear-gradient(top, #6eb4f7 0%, #1a82fb 100%);\n  background-image: linear-gradient(to bottom, #6eb4f7 0%, #1a82fb 100%);\n}\n.btn-primary:active {\n  background-color: #3e9bf4;\n  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #3e9bf4), color-stop(100%, #0469de));\n  background-image: -webkit-linear-gradient(top, #3e9bf4 0%, #0469de 100%);\n  background-image: linear-gradient(to bottom, #3e9bf4 0%, #0469de 100%);\n}\n\n.btn-positive {\n  border-color: #29a03b;\n  border-bottom-color: #248b34;\n  background-color: #5bd46d;\n  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bd46d), color-stop(100%, #29a03b));\n  background-image: -webkit-linear-gradient(top, #5bd46d 0%, #29a03b 100%);\n  background-image: linear-gradient(to bottom, #5bd46d 0%, #29a03b 100%);\n}\n.btn-positive:active {\n  background-color: #34c84a;\n  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #34c84a), color-stop(100%, #248b34));\n  background-image: -webkit-linear-gradient(top, #34c84a 0%, #248b34 100%);\n  background-image: linear-gradient(to bottom, #34c84a 0%, #248b34 100%);\n}\n\n.btn-negative {\n  border-color: #fb2f29;\n  border-bottom-color: #fb1710;\n  background-color: #fd918d;\n  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fd918d), color-stop(100%, #fb2f29));\n  background-image: -webkit-linear-gradient(top, #fd918d 0%, #fb2f29 100%);\n  background-image: linear-gradient(to bottom, #fd918d 0%, #fb2f29 100%);\n}\n.btn-negative:active {\n  background-color: #fc605b;\n  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fc605b), color-stop(100%, #fb1710));\n  background-image: -webkit-linear-gradient(top, #fc605b 0%, #fb1710 100%);\n  background-image: linear-gradient(to bottom, #fc605b 0%, #fb1710 100%);\n}\n\n.btn-warning {\n  border-color: #fcaa0e;\n  border-bottom-color: #ee9d02;\n  background-color: #fece72;\n  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fece72), color-stop(100%, #fcaa0e));\n  background-image: -webkit-linear-gradient(top, #fece72 0%, #fcaa0e 100%);\n  background-image: linear-gradient(to bottom, #fece72 0%, #fcaa0e 100%);\n}\n.btn-warning:active {\n  background-color: #fdbc40;\n  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fdbc40), color-stop(100%, #ee9d02));\n  background-image: -webkit-linear-gradient(top, #fdbc40 0%, #ee9d02 100%);\n  background-image: linear-gradient(to bottom, #fdbc40 0%, #ee9d02 100%);\n}\n\n.btn .icon {\n  float: left;\n  width: 14px;\n  height: 14px;\n  margin-top: 1px;\n  margin-bottom: 1px;\n  color: #737475;\n  font-size: 14px;\n  line-height: 1;\n}\n\n.btn .icon-text {\n  margin-right: 5px;\n}\n\n.btn-dropdown:after {\n  font-family: \"photon-entypo\";\n  margin-left: 5px;\n  content: \"\";\n}\n\n.btn-group {\n  position: relative;\n  display: inline-block;\n  vertical-align: middle;\n  -webkit-app-region: no-drag;\n}\n.btn-group .btn {\n  position: relative;\n  float: left;\n}\n.btn-group .btn:focus, .btn-group .btn:active {\n  z-index: 2;\n}\n.btn-group .btn.active {\n  z-index: 3;\n}\n\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n  margin-left: -1px;\n}\n.btn-group > .btn:first-child {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.btn-group > .btn:last-child {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group > .btn:not(:first-child):not(:last-child) {\n  border-radius: 0;\n}\n.btn-group .btn + .btn {\n  border-left: 1px solid #c2c0c2;\n}\n.btn-group .btn + .btn.active {\n  border-left: 0;\n}\n.btn-group .active {\n  color: #fff;\n  border: 1px solid transparent;\n  background-color: #6d6c6d;\n  background-image: none;\n}\n.btn-group .active .icon {\n  color: #fff;\n}\n\n.toolbar {\n  min-height: 22px;\n  box-shadow: inset 0 1px 0 #f5f4f5;\n  background-color: #e8e6e8;\n  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #e8e6e8), color-stop(100%, #d1cfd1));\n  background-image: -webkit-linear-gradient(top, #e8e6e8 0%, #d1cfd1 100%);\n  background-image: linear-gradient(to bottom, #e8e6e8 0%, #d1cfd1 100%);\n}\n.toolbar:before, .toolbar:after {\n  display: table;\n  content: \" \";\n}\n.toolbar:after {\n  clear: both;\n}\n\n.toolbar-header {\n  border-bottom: 1px solid #c2c0c2;\n}\n.toolbar-header .title {\n  margin-top: 1px;\n}\n\n.toolbar-footer {\n  border-top: 1px solid #c2c0c2;\n  -webkit-app-region: drag;\n}\n\n.toolbar-dark {\n  box-shadow: none;\n  background-color: #57acf5;\n}\n.toolbar-dark .title {\n  text-shadow: none;\n  color: #fff;\n}\n\n.title {\n  margin: 0;\n  font-size: 12px;\n  font-weight: 400;\n  text-align: center;\n  color: #555;\n  cursor: default;\n}\n\n.toolbar-borderless {\n  border-top: 0;\n  border-bottom: 0;\n}\n\n.toolbar-actions {\n  margin-top: 4px;\n  margin-bottom: 3px;\n  padding-right: 3px;\n  padding-left: 3px;\n  padding-bottom: 3px;\n  -webkit-app-region: drag;\n}\n.toolbar-actions:before, .toolbar-actions:after {\n  display: table;\n  content: \" \";\n}\n.toolbar-actions:after {\n  clear: both;\n}\n.toolbar-actions > .btn,\n.toolbar-actions > .btn-group {\n  margin-left: 4px;\n  margin-right: 4px;\n}\n\nlabel {\n  display: inline-block;\n  font-size: 13px;\n  margin-bottom: 5px;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\ninput[type=\"search\"] {\n  box-sizing: border-box;\n}\n\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n  margin: 4px 0 0;\n  line-height: normal;\n}\n\n.form-control {\n  display: block;\n  width: 100%;\n  min-height: 25px;\n  padding: 5px 10px;\n  font-size: 13px;\n  line-height: 1.6;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-radius: 4px;\n  outline: none;\n}\n.form-control:focus {\n  border-color: #6db3fd;\n  box-shadow: 3px 3px 0 #6db3fd, -3px -3px 0 #6db3fd, -3px 3px 0 #6db3fd, 3px -3px 0 #6db3fd;\n}\n\ntextarea {\n  height: auto;\n}\n\n.form-group {\n  margin-bottom: 10px;\n}\n\n.radio,\n.checkbox {\n  position: relative;\n  display: block;\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\n.radio label,\n.checkbox label {\n  padding-left: 20px;\n  margin-bottom: 0;\n  font-weight: normal;\n}\n\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n  position: absolute;\n  margin-left: -20px;\n  margin-top: 4px;\n}\n\n.form-actions .btn {\n  margin-right: 10px;\n}\n.form-actions .btn:last-child {\n  margin-right: 0;\n}\n\n.pane-group {\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  display: flex;\n}\n\n.pane {\n  position: relative;\n  overflow-y: auto;\n  flex: 1;\n  border-left: 1px solid #ddd;\n}\n.pane:first-child {\n  border-left: 0;\n}\n\n.pane-sm {\n  max-width: 220px;\n  min-width: 150px;\n}\n\n.pane-mini {\n  width: 80px;\n  flex: none;\n}\n\n.pane-one-fourth {\n  width: 25%;\n  flex: none;\n}\n\n.pane-one-third {\n  width: 33.3%;\n}\n\nimg {\n  -webkit-user-drag: text;\n}\n\n.img-circle {\n  border-radius: 50%;\n}\n\n.img-rounded {\n  border-radius: 4px;\n}\n\n.list-group {\n  width: 100%;\n  list-style: none;\n  margin: 0;\n  padding: 0;\n}\n.list-group * {\n  margin: 0;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.list-group-item {\n  padding: 10px;\n  font-size: 12px;\n  color: #414142;\n  border-top: 1px solid #ddd;\n}\n.list-group-item:first-child {\n  border-top: 0;\n}\n.list-group-item:active, .list-group-item.selected {\n  color: #fff;\n  background-color: #116cd6;\n}\n\n.list-group-header {\n  padding: 10px;\n}\n\n.media-object {\n  margin-top: 3px;\n}\n\n.media-object.pull-left {\n  margin-right: 10px;\n}\n\n.media-object.pull-right {\n  margin-left: 10px;\n}\n\n.media-body {\n  overflow: hidden;\n}\n\n.nav-group {\n  font-size: 14px;\n}\n\n.nav-group-item {\n  padding: 2px 10px 2px 25px;\n  display: block;\n  color: #333;\n  text-decoration: none;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n.nav-group-item:active, .nav-group-item.active {\n  background-color: #dcdfe1;\n}\n.nav-group-item .icon {\n  width: 19px;\n  height: 18px;\n  float: left;\n  color: #737475;\n  margin-top: -3px;\n  margin-right: 7px;\n  font-size: 18px;\n  text-align: center;\n}\n\n.nav-group-title {\n  margin: 0;\n  padding: 10px 10px 2px;\n  font-size: 12px;\n  font-weight: 500;\n  color: #666666;\n}\n\n@font-face {\n  font-family: \"photon-entypo\";\n  src: url(\"../fonts/photon-entypo.eot\");\n  src: url(\"../fonts/photon-entypo.eot?#iefix\") format(\"eot\"), url(\"../fonts/photon-entypo.woff\") format(\"woff\"), url(\"../fonts/photon-entypo.ttf\") format(\"truetype\");\n  font-weight: normal;\n  font-style: normal;\n}\n.icon:before {\n  position: relative;\n  display: inline-block;\n  font-family: \"photon-entypo\";\n  speak: none;\n  font-size: 100%;\n  font-style: normal;\n  font-weight: normal;\n  font-variant: normal;\n  text-transform: none;\n  line-height: 1;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n.icon-note:before {\n  content: '\\e800';\n}\n\n/* '' */\n.icon-note-beamed:before {\n  content: '\\e801';\n}\n\n/* '' */\n.icon-music:before {\n  content: '\\e802';\n}\n\n/* '' */\n.icon-search:before {\n  content: '\\e803';\n}\n\n/* '' */\n.icon-flashlight:before {\n  content: '\\e804';\n}\n\n/* '' */\n.icon-mail:before {\n  content: '\\e805';\n}\n\n/* '' */\n.icon-heart:before {\n  content: '\\e806';\n}\n\n/* '' */\n.icon-heart-empty:before {\n  content: '\\e807';\n}\n\n/* '' */\n.icon-star:before {\n  content: '\\e808';\n}\n\n/* '' */\n.icon-star-empty:before {\n  content: '\\e809';\n}\n\n/* '' */\n.icon-user:before {\n  content: '\\e80a';\n}\n\n/* '' */\n.icon-users:before {\n  content: '\\e80b';\n}\n\n/* '' */\n.icon-user-add:before {\n  content: '\\e80c';\n}\n\n/* '' */\n.icon-video:before {\n  content: '\\e80d';\n}\n\n/* '' */\n.icon-picture:before {\n  content: '\\e80e';\n}\n\n/* '' */\n.icon-camera:before {\n  content: '\\e80f';\n}\n\n/* '' */\n.icon-layout:before {\n  content: '\\e810';\n}\n\n/* '' */\n.icon-menu:before {\n  content: '\\e811';\n}\n\n/* '' */\n.icon-check:before {\n  content: '\\e812';\n}\n\n/* '' */\n.icon-cancel:before {\n  content: '\\e813';\n}\n\n/* '' */\n.icon-cancel-circled:before {\n  content: '\\e814';\n}\n\n/* '' */\n.icon-cancel-squared:before {\n  content: '\\e815';\n}\n\n/* '' */\n.icon-plus:before {\n  content: '\\e816';\n}\n\n/* '' */\n.icon-plus-circled:before {\n  content: '\\e817';\n}\n\n/* '' */\n.icon-plus-squared:before {\n  content: '\\e818';\n}\n\n/* '' */\n.icon-minus:before {\n  content: '\\e819';\n}\n\n/* '' */\n.icon-minus-circled:before {\n  content: '\\e81a';\n}\n\n/* '' */\n.icon-minus-squared:before {\n  content: '\\e81b';\n}\n\n/* '' */\n.icon-help:before {\n  content: '\\e81c';\n}\n\n/* '' */\n.icon-help-circled:before {\n  content: '\\e81d';\n}\n\n/* '' */\n.icon-info:before {\n  content: '\\e81e';\n}\n\n/* '' */\n.icon-info-circled:before {\n  content: '\\e81f';\n}\n\n/* '' */\n.icon-back:before {\n  content: '\\e820';\n}\n\n/* '' */\n.icon-home:before {\n  content: '\\e821';\n}\n\n/* '' */\n.icon-link:before {\n  content: '\\e822';\n}\n\n/* '' */\n.icon-attach:before {\n  content: '\\e823';\n}\n\n/* '' */\n.icon-lock:before {\n  content: '\\e824';\n}\n\n/* '' */\n.icon-lock-open:before {\n  content: '\\e825';\n}\n\n/* '' */\n.icon-eye:before {\n  content: '\\e826';\n}\n\n/* '' */\n.icon-tag:before {\n  content: '\\e827';\n}\n\n/* '' */\n.icon-bookmark:before {\n  content: '\\e828';\n}\n\n/* '' */\n.icon-bookmarks:before {\n  content: '\\e829';\n}\n\n/* '' */\n.icon-flag:before {\n  content: '\\e82a';\n}\n\n/* '' */\n.icon-thumbs-up:before {\n  content: '\\e82b';\n}\n\n/* '' */\n.icon-thumbs-down:before {\n  content: '\\e82c';\n}\n\n/* '' */\n.icon-download:before {\n  content: '\\e82d';\n}\n\n/* '' */\n.icon-upload:before {\n  content: '\\e82e';\n}\n\n/* '' */\n.icon-upload-cloud:before {\n  content: '\\e82f';\n}\n\n/* '' */\n.icon-reply:before {\n  content: '\\e830';\n}\n\n/* '' */\n.icon-reply-all:before {\n  content: '\\e831';\n}\n\n/* '' */\n.icon-forward:before {\n  content: '\\e832';\n}\n\n/* '' */\n.icon-quote:before {\n  content: '\\e833';\n}\n\n/* '' */\n.icon-code:before {\n  content: '\\e834';\n}\n\n/* '' */\n.icon-export:before {\n  content: '\\e835';\n}\n\n/* '' */\n.icon-pencil:before {\n  content: '\\e836';\n}\n\n/* '' */\n.icon-feather:before {\n  content: '\\e837';\n}\n\n/* '' */\n.icon-print:before {\n  content: '\\e838';\n}\n\n/* '' */\n.icon-retweet:before {\n  content: '\\e839';\n}\n\n/* '' */\n.icon-keyboard:before {\n  content: '\\e83a';\n}\n\n/* '' */\n.icon-comment:before {\n  content: '\\e83b';\n}\n\n/* '' */\n.icon-chat:before {\n  content: '\\e83c';\n}\n\n/* '' */\n.icon-bell:before {\n  content: '\\e83d';\n}\n\n/* '' */\n.icon-attention:before {\n  content: '\\e83e';\n}\n\n/* '' */\n.icon-alert:before {\n  content: '\\e83f';\n}\n\n/* '' */\n.icon-vcard:before {\n  content: '\\e840';\n}\n\n/* '' */\n.icon-address:before {\n  content: '\\e841';\n}\n\n/* '' */\n.icon-location:before {\n  content: '\\e842';\n}\n\n/* '' */\n.icon-map:before {\n  content: '\\e843';\n}\n\n/* '' */\n.icon-direction:before {\n  content: '\\e844';\n}\n\n/* '' */\n.icon-compass:before {\n  content: '\\e845';\n}\n\n/* '' */\n.icon-cup:before {\n  content: '\\e846';\n}\n\n/* '' */\n.icon-trash:before {\n  content: '\\e847';\n}\n\n/* '' */\n.icon-doc:before {\n  content: '\\e848';\n}\n\n/* '' */\n.icon-docs:before {\n  content: '\\e849';\n}\n\n/* '' */\n.icon-doc-landscape:before {\n  content: '\\e84a';\n}\n\n/* '' */\n.icon-doc-text:before {\n  content: '\\e84b';\n}\n\n/* '' */\n.icon-doc-text-inv:before {\n  content: '\\e84c';\n}\n\n/* '' */\n.icon-newspaper:before {\n  content: '\\e84d';\n}\n\n/* '' */\n.icon-book-open:before {\n  content: '\\e84e';\n}\n\n/* '' */\n.icon-book:before {\n  content: '\\e84f';\n}\n\n/* '' */\n.icon-folder:before {\n  content: '\\e850';\n}\n\n/* '' */\n.icon-archive:before {\n  content: '\\e851';\n}\n\n/* '' */\n.icon-box:before {\n  content: '\\e852';\n}\n\n/* '' */\n.icon-rss:before {\n  content: '\\e853';\n}\n\n/* '' */\n.icon-phone:before {\n  content: '\\e854';\n}\n\n/* '' */\n.icon-cog:before {\n  content: '\\e855';\n}\n\n/* '' */\n.icon-tools:before {\n  content: '\\e856';\n}\n\n/* '' */\n.icon-share:before {\n  content: '\\e857';\n}\n\n/* '' */\n.icon-shareable:before {\n  content: '\\e858';\n}\n\n/* '' */\n.icon-basket:before {\n  content: '\\e859';\n}\n\n/* '' */\n.icon-bag:before {\n  content: '\\e85a';\n}\n\n/* '' */\n.icon-calendar:before {\n  content: '\\e85b';\n}\n\n/* '' */\n.icon-login:before {\n  content: '\\e85c';\n}\n\n/* '' */\n.icon-logout:before {\n  content: '\\e85d';\n}\n\n/* '' */\n.icon-mic:before {\n  content: '\\e85e';\n}\n\n/* '' */\n.icon-mute:before {\n  content: '\\e85f';\n}\n\n/* '' */\n.icon-sound:before {\n  content: '\\e860';\n}\n\n/* '' */\n.icon-volume:before {\n  content: '\\e861';\n}\n\n/* '' */\n.icon-clock:before {\n  content: '\\e862';\n}\n\n/* '' */\n.icon-hourglass:before {\n  content: '\\e863';\n}\n\n/* '' */\n.icon-lamp:before {\n  content: '\\e864';\n}\n\n/* '' */\n.icon-light-down:before {\n  content: '\\e865';\n}\n\n/* '' */\n.icon-light-up:before {\n  content: '\\e866';\n}\n\n/* '' */\n.icon-adjust:before {\n  content: '\\e867';\n}\n\n/* '' */\n.icon-block:before {\n  content: '\\e868';\n}\n\n/* '' */\n.icon-resize-full:before {\n  content: '\\e869';\n}\n\n/* '' */\n.icon-resize-small:before {\n  content: '\\e86a';\n}\n\n/* '' */\n.icon-popup:before {\n  content: '\\e86b';\n}\n\n/* '' */\n.icon-publish:before {\n  content: '\\e86c';\n}\n\n/* '' */\n.icon-window:before {\n  content: '\\e86d';\n}\n\n/* '' */\n.icon-arrow-combo:before {\n  content: '\\e86e';\n}\n\n/* '' */\n.icon-down-circled:before {\n  content: '\\e86f';\n}\n\n/* '' */\n.icon-left-circled:before {\n  content: '\\e870';\n}\n\n/* '' */\n.icon-right-circled:before {\n  content: '\\e871';\n}\n\n/* '' */\n.icon-up-circled:before {\n  content: '\\e872';\n}\n\n/* '' */\n.icon-down-open:before {\n  content: '\\e873';\n}\n\n/* '' */\n.icon-left-open:before {\n  content: '\\e874';\n}\n\n/* '' */\n.icon-right-open:before {\n  content: '\\e875';\n}\n\n/* '' */\n.icon-up-open:before {\n  content: '\\e876';\n}\n\n/* '' */\n.icon-down-open-mini:before {\n  content: '\\e877';\n}\n\n/* '' */\n.icon-left-open-mini:before {\n  content: '\\e878';\n}\n\n/* '' */\n.icon-right-open-mini:before {\n  content: '\\e879';\n}\n\n/* '' */\n.icon-up-open-mini:before {\n  content: '\\e87a';\n}\n\n/* '' */\n.icon-down-open-big:before {\n  content: '\\e87b';\n}\n\n/* '' */\n.icon-left-open-big:before {\n  content: '\\e87c';\n}\n\n/* '' */\n.icon-right-open-big:before {\n  content: '\\e87d';\n}\n\n/* '' */\n.icon-up-open-big:before {\n  content: '\\e87e';\n}\n\n/* '' */\n.icon-down:before {\n  content: '\\e87f';\n}\n\n/* '' */\n.icon-left:before {\n  content: '\\e880';\n}\n\n/* '' */\n.icon-right:before {\n  content: '\\e881';\n}\n\n/* '' */\n.icon-up:before {\n  content: '\\e882';\n}\n\n/* '' */\n.icon-down-dir:before {\n  content: '\\e883';\n}\n\n/* '' */\n.icon-left-dir:before {\n  content: '\\e884';\n}\n\n/* '' */\n.icon-right-dir:before {\n  content: '\\e885';\n}\n\n/* '' */\n.icon-up-dir:before {\n  content: '\\e886';\n}\n\n/* '' */\n.icon-down-bold:before {\n  content: '\\e887';\n}\n\n/* '' */\n.icon-left-bold:before {\n  content: '\\e888';\n}\n\n/* '' */\n.icon-right-bold:before {\n  content: '\\e889';\n}\n\n/* '' */\n.icon-up-bold:before {\n  content: '\\e88a';\n}\n\n/* '' */\n.icon-down-thin:before {\n  content: '\\e88b';\n}\n\n/* '' */\n.icon-left-thin:before {\n  content: '\\e88c';\n}\n\n/* '' */\n.icon-right-thin:before {\n  content: '\\e88d';\n}\n\n/* '' */\n.icon-up-thin:before {\n  content: '\\e88e';\n}\n\n/* '' */\n.icon-ccw:before {\n  content: '\\e88f';\n}\n\n/* '' */\n.icon-cw:before {\n  content: '\\e890';\n}\n\n/* '' */\n.icon-arrows-ccw:before {\n  content: '\\e891';\n}\n\n/* '' */\n.icon-level-down:before {\n  content: '\\e892';\n}\n\n/* '' */\n.icon-level-up:before {\n  content: '\\e893';\n}\n\n/* '' */\n.icon-shuffle:before {\n  content: '\\e894';\n}\n\n/* '' */\n.icon-loop:before {\n  content: '\\e895';\n}\n\n/* '' */\n.icon-switch:before {\n  content: '\\e896';\n}\n\n/* '' */\n.icon-play:before {\n  content: '\\e897';\n}\n\n/* '' */\n.icon-stop:before {\n  content: '\\e898';\n}\n\n/* '' */\n.icon-pause:before {\n  content: '\\e899';\n}\n\n/* '' */\n.icon-record:before {\n  content: '\\e89a';\n}\n\n/* '' */\n.icon-to-end:before {\n  content: '\\e89b';\n}\n\n/* '' */\n.icon-to-start:before {\n  content: '\\e89c';\n}\n\n/* '' */\n.icon-fast-forward:before {\n  content: '\\e89d';\n}\n\n/* '' */\n.icon-fast-backward:before {\n  content: '\\e89e';\n}\n\n/* '' */\n.icon-progress-0:before {\n  content: '\\e89f';\n}\n\n/* '' */\n.icon-progress-1:before {\n  content: '\\e8a0';\n}\n\n/* '' */\n.icon-progress-2:before {\n  content: '\\e8a1';\n}\n\n/* '' */\n.icon-progress-3:before {\n  content: '\\e8a2';\n}\n\n/* '' */\n.icon-target:before {\n  content: '\\e8a3';\n}\n\n/* '' */\n.icon-palette:before {\n  content: '\\e8a4';\n}\n\n/* '' */\n.icon-list:before {\n  content: '\\e8a5';\n}\n\n/* '' */\n.icon-list-add:before {\n  content: '\\e8a6';\n}\n\n/* '' */\n.icon-signal:before {\n  content: '\\e8a7';\n}\n\n/* '' */\n.icon-trophy:before {\n  content: '\\e8a8';\n}\n\n/* '' */\n.icon-battery:before {\n  content: '\\e8a9';\n}\n\n/* '' */\n.icon-back-in-time:before {\n  content: '\\e8aa';\n}\n\n/* '' */\n.icon-monitor:before {\n  content: '\\e8ab';\n}\n\n/* '' */\n.icon-mobile:before {\n  content: '\\e8ac';\n}\n\n/* '' */\n.icon-network:before {\n  content: '\\e8ad';\n}\n\n/* '' */\n.icon-cd:before {\n  content: '\\e8ae';\n}\n\n/* '' */\n.icon-inbox:before {\n  content: '\\e8af';\n}\n\n/* '' */\n.icon-install:before {\n  content: '\\e8b0';\n}\n\n/* '' */\n.icon-globe:before {\n  content: '\\e8b1';\n}\n\n/* '' */\n.icon-cloud:before {\n  content: '\\e8b2';\n}\n\n/* '' */\n.icon-cloud-thunder:before {\n  content: '\\e8b3';\n}\n\n/* '' */\n.icon-flash:before {\n  content: '\\e8b4';\n}\n\n/* '' */\n.icon-moon:before {\n  content: '\\e8b5';\n}\n\n/* '' */\n.icon-flight:before {\n  content: '\\e8b6';\n}\n\n/* '' */\n.icon-paper-plane:before {\n  content: '\\e8b7';\n}\n\n/* '' */\n.icon-leaf:before {\n  content: '\\e8b8';\n}\n\n/* '' */\n.icon-lifebuoy:before {\n  content: '\\e8b9';\n}\n\n/* '' */\n.icon-mouse:before {\n  content: '\\e8ba';\n}\n\n/* '' */\n.icon-briefcase:before {\n  content: '\\e8bb';\n}\n\n/* '' */\n.icon-suitcase:before {\n  content: '\\e8bc';\n}\n\n/* '' */\n.icon-dot:before {\n  content: '\\e8bd';\n}\n\n/* '' */\n.icon-dot-2:before {\n  content: '\\e8be';\n}\n\n/* '' */\n.icon-dot-3:before {\n  content: '\\e8bf';\n}\n\n/* '' */\n.icon-brush:before {\n  content: '\\e8c0';\n}\n\n/* '' */\n.icon-magnet:before {\n  content: '\\e8c1';\n}\n\n/* '' */\n.icon-infinity:before {\n  content: '\\e8c2';\n}\n\n/* '' */\n.icon-erase:before {\n  content: '\\e8c3';\n}\n\n/* '' */\n.icon-chart-pie:before {\n  content: '\\e8c4';\n}\n\n/* '' */\n.icon-chart-line:before {\n  content: '\\e8c5';\n}\n\n/* '' */\n.icon-chart-bar:before {\n  content: '\\e8c6';\n}\n\n/* '' */\n.icon-chart-area:before {\n  content: '\\e8c7';\n}\n\n/* '' */\n.icon-tape:before {\n  content: '\\e8c8';\n}\n\n/* '' */\n.icon-graduation-cap:before {\n  content: '\\e8c9';\n}\n\n/* '' */\n.icon-language:before {\n  content: '\\e8ca';\n}\n\n/* '' */\n.icon-ticket:before {\n  content: '\\e8cb';\n}\n\n/* '' */\n.icon-water:before {\n  content: '\\e8cc';\n}\n\n/* '' */\n.icon-droplet:before {\n  content: '\\e8cd';\n}\n\n/* '' */\n.icon-air:before {\n  content: '\\e8ce';\n}\n\n/* '' */\n.icon-credit-card:before {\n  content: '\\e8cf';\n}\n\n/* '' */\n.icon-floppy:before {\n  content: '\\e8d0';\n}\n\n/* '' */\n.icon-clipboard:before {\n  content: '\\e8d1';\n}\n\n/* '' */\n.icon-megaphone:before {\n  content: '\\e8d2';\n}\n\n/* '' */\n.icon-database:before {\n  content: '\\e8d3';\n}\n\n/* '' */\n.icon-drive:before {\n  content: '\\e8d4';\n}\n\n/* '' */\n.icon-bucket:before {\n  content: '\\e8d5';\n}\n\n/* '' */\n.icon-thermometer:before {\n  content: '\\e8d6';\n}\n\n/* '' */\n.icon-key:before {\n  content: '\\e8d7';\n}\n\n/* '' */\n.icon-flow-cascade:before {\n  content: '\\e8d8';\n}\n\n/* '' */\n.icon-flow-branch:before {\n  content: '\\e8d9';\n}\n\n/* '' */\n.icon-flow-tree:before {\n  content: '\\e8da';\n}\n\n/* '' */\n.icon-flow-line:before {\n  content: '\\e8db';\n}\n\n/* '' */\n.icon-flow-parallel:before {\n  content: '\\e8dc';\n}\n\n/* '' */\n.icon-rocket:before {\n  content: '\\e8dd';\n}\n\n/* '' */\n.icon-gauge:before {\n  content: '\\e8de';\n}\n\n/* '' */\n.icon-traffic-cone:before {\n  content: '\\e8df';\n}\n\n/* '' */\n.icon-cc:before {\n  content: '\\e8e0';\n}\n\n/* '' */\n.icon-cc-by:before {\n  content: '\\e8e1';\n}\n\n/* '' */\n.icon-cc-nc:before {\n  content: '\\e8e2';\n}\n\n/* '' */\n.icon-cc-nc-eu:before {\n  content: '\\e8e3';\n}\n\n/* '' */\n.icon-cc-nc-jp:before {\n  content: '\\e8e4';\n}\n\n/* '' */\n.icon-cc-sa:before {\n  content: '\\e8e5';\n}\n\n/* '' */\n.icon-cc-nd:before {\n  content: '\\e8e6';\n}\n\n/* '' */\n.icon-cc-pd:before {\n  content: '\\e8e7';\n}\n\n/* '' */\n.icon-cc-zero:before {\n  content: '\\e8e8';\n}\n\n/* '' */\n.icon-cc-share:before {\n  content: '\\e8e9';\n}\n\n/* '' */\n.icon-cc-remix:before {\n  content: '\\e8ea';\n}\n\n/* '' */\n.icon-github:before {\n  content: '\\e8eb';\n}\n\n/* '' */\n.icon-github-circled:before {\n  content: '\\e8ec';\n}\n\n/* '' */\n.icon-flickr:before {\n  content: '\\e8ed';\n}\n\n/* '' */\n.icon-flickr-circled:before {\n  content: '\\e8ee';\n}\n\n/* '' */\n.icon-vimeo:before {\n  content: '\\e8ef';\n}\n\n/* '' */\n.icon-vimeo-circled:before {\n  content: '\\e8f0';\n}\n\n/* '' */\n.icon-twitter:before {\n  content: '\\e8f1';\n}\n\n/* '' */\n.icon-twitter-circled:before {\n  content: '\\e8f2';\n}\n\n/* '' */\n.icon-facebook:before {\n  content: '\\e8f3';\n}\n\n/* '' */\n.icon-facebook-circled:before {\n  content: '\\e8f4';\n}\n\n/* '' */\n.icon-facebook-squared:before {\n  content: '\\e8f5';\n}\n\n/* '' */\n.icon-gplus:before {\n  content: '\\e8f6';\n}\n\n/* '' */\n.icon-gplus-circled:before {\n  content: '\\e8f7';\n}\n\n/* '' */\n.icon-pinterest:before {\n  content: '\\e8f8';\n}\n\n/* '' */\n.icon-pinterest-circled:before {\n  content: '\\e8f9';\n}\n\n/* '' */\n.icon-tumblr:before {\n  content: '\\e8fa';\n}\n\n/* '' */\n.icon-tumblr-circled:before {\n  content: '\\e8fb';\n}\n\n/* '' */\n.icon-linkedin:before {\n  content: '\\e8fc';\n}\n\n/* '' */\n.icon-linkedin-circled:before {\n  content: '\\e8fd';\n}\n\n/* '' */\n.icon-dribbble:before {\n  content: '\\e8fe';\n}\n\n/* '' */\n.icon-dribbble-circled:before {\n  content: '\\e8ff';\n}\n\n/* '' */\n.icon-stumbleupon:before {\n  content: '\\e900';\n}\n\n/* '' */\n.icon-stumbleupon-circled:before {\n  content: '\\e901';\n}\n\n/* '' */\n.icon-lastfm:before {\n  content: '\\e902';\n}\n\n/* '' */\n.icon-lastfm-circled:before {\n  content: '\\e903';\n}\n\n/* '' */\n.icon-rdio:before {\n  content: '\\e904';\n}\n\n/* '' */\n.icon-rdio-circled:before {\n  content: '\\e905';\n}\n\n/* '' */\n.icon-spotify:before {\n  content: '\\e906';\n}\n\n/* '' */\n.icon-spotify-circled:before {\n  content: '\\e907';\n}\n\n/* '' */\n.icon-qq:before {\n  content: '\\e908';\n}\n\n/* '' */\n.icon-instagram:before {\n  content: '\\e909';\n}\n\n/* '' */\n.icon-dropbox:before {\n  content: '\\e90a';\n}\n\n/* '' */\n.icon-evernote:before {\n  content: '\\e90b';\n}\n\n/* '' */\n.icon-flattr:before {\n  content: '\\e90c';\n}\n\n/* '' */\n.icon-skype:before {\n  content: '\\e90d';\n}\n\n/* '' */\n.icon-skype-circled:before {\n  content: '\\e90e';\n}\n\n/* '' */\n.icon-renren:before {\n  content: '\\e90f';\n}\n\n/* '' */\n.icon-sina-weibo:before {\n  content: '\\e910';\n}\n\n/* '' */\n.icon-paypal:before {\n  content: '\\e911';\n}\n\n/* '' */\n.icon-picasa:before {\n  content: '\\e912';\n}\n\n/* '' */\n.icon-soundcloud:before {\n  content: '\\e913';\n}\n\n/* '' */\n.icon-mixi:before {\n  content: '\\e914';\n}\n\n/* '' */\n.icon-behance:before {\n  content: '\\e915';\n}\n\n/* '' */\n.icon-google-circles:before {\n  content: '\\e916';\n}\n\n/* '' */\n.icon-vkontakte:before {\n  content: '\\e917';\n}\n\n/* '' */\n.icon-smashing:before {\n  content: '\\e918';\n}\n\n/* '' */\n.icon-sweden:before {\n  content: '\\e919';\n}\n\n/* '' */\n.icon-db-shape:before {\n  content: '\\e91a';\n}\n\n/* '' */\n.icon-logo-db:before {\n  content: '\\e91b';\n}\n\n/* '' */\ntable {\n  width: 100%;\n  border: 0;\n  border-collapse: separate;\n  font-size: 12px;\n  text-align: left;\n}\n\nthead {\n  background-color: #f5f5f4;\n}\n\ntbody {\n  background-color: #fff;\n}\n\n.table-striped tr:nth-child(even) {\n  background-color: #f5f5f4;\n}\n\ntr:active,\n.table-striped tr:active:nth-child(even) {\n  color: #fff;\n  background-color: #116cd6;\n}\n\nthead tr:active {\n  color: #333;\n  background-color: #f5f5f4;\n}\n\nth {\n  font-weight: normal;\n  border-right: 1px solid #ddd;\n  border-bottom: 1px solid #ddd;\n}\n\nth,\ntd {\n  padding: 2px 15px;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\nth:last-child,\ntd:last-child {\n  border-right: 0;\n}\n\n.tab-group {\n  margin-top: -1px;\n  display: flex;\n  border-top: 1px solid #989698;\n  border-bottom: 1px solid #989698;\n}\n\n.tab-item {\n  position: relative;\n  flex: 1;\n  padding: 3px;\n  font-size: 12px;\n  text-align: center;\n  border-left: 1px solid #989698;\n  background-color: #b8b6b8;\n  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #b8b6b8), color-stop(100%, #b0aeb0));\n  background-image: -webkit-linear-gradient(top, #b8b6b8 0%, #b0aeb0 100%);\n  background-image: linear-gradient(to bottom, #b8b6b8 0%, #b0aeb0 100%);\n}\n.tab-item:first-child {\n  border-left: 0;\n}\n.tab-item.active {\n  background-color: #d4d2d4;\n  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #d4d2d4), color-stop(100%, #cccacc));\n  background-image: -webkit-linear-gradient(top, #d4d2d4 0%, #cccacc 100%);\n  background-image: linear-gradient(to bottom, #d4d2d4 0%, #cccacc 100%);\n}\n.tab-item .icon-close-tab {\n  position: absolute;\n  top: 50%;\n  left: 5px;\n  width: 15px;\n  height: 15px;\n  font-size: 15px;\n  line-height: 15px;\n  text-align: center;\n  color: #666;\n  opacity: 0;\n  transition: opacity .1s linear, background-color .1s linear;\n  border-radius: 3px;\n  transform: translateY(-50%);\n  z-index: 10;\n}\n.tab-item:after {\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  content: \"\";\n  background-color: rgba(0, 0, 0, 0.08);\n  opacity: 0;\n  transition: opacity .1s linear;\n  z-index: 1;\n}\n.tab-item:hover:not(.active):after {\n  opacity: 1;\n}\n.tab-item:hover .icon-close-tab {\n  opacity: 1;\n}\n.tab-item .icon-close-tab:hover {\n  background-color: rgba(0, 0, 0, 0.08);\n}\n"
  },
  {
    "path": "src/renderer/redux/actions/connection.js",
    "content": "'use strict';\n\nimport {createAction} from 'Utils';\nimport {Client} from 'ssh2';\nimport net from 'net';\nimport Redis from 'ioredis';\n\nfunction getIndex(getState) {\n  const {activeInstanceKey, instances} = getState()\n  return instances.findIndex(instance => instance.get('key') === activeInstanceKey)\n}\n\nexport const updateConnectStatus = createAction('UPDATE_CONNECT_STATUS', status => ({getState, next}) => {\n  next({status, index: getIndex(getState)})\n})\n\nexport const disconnect = createAction('DISCONNECT', () => ({getState, next}) => {\n  next({index: getIndex(getState)})\n})\n\nexport const connectToRedis = createAction('CONNECT', config => ({getState, dispatch, next}) => {\n  let sshErrorThrown = false\n  let redisErrorMessage\n\n  if (config.ssh) {\n    dispatch(updateConnectStatus('SSH connecting...'))\n\n    const conn = new Client();\n    conn.on('ready', () => {\n      const server = net.createServer(function (sock) {\n        conn.forwardOut(sock.remoteAddress, sock.remotePort, config.host, config.port, (err, stream) => {\n          if (err) {\n            sock.end()\n          } else {\n            sock.pipe(stream).pipe(sock)\n          }\n        })\n      }).listen(0, function () {\n        handleRedis(config, { host: '127.0.0.1', port: server.address().port })\n      })\n    }).on('error', err => {\n      sshErrorThrown = true;\n      dispatch(disconnect());\n      alert(`SSH Error: ${err.message}`);\n    })\n\n    try {\n      const connectionConfig = {\n        host: config.sshHost,\n        port: config.sshPort || 22,\n        username: config.sshUser\n      }\n      if (config.sshKey) {\n        conn.connect(Object.assign(connectionConfig, {\n          privateKey: config.sshKey,\n          passphrase: config.sshKeyPassphrase\n        }))\n      } else {\n        conn.connect(Object.assign(connectionConfig, {\n          password: config.sshPassword\n        }))\n      }\n    } catch (err) {\n      dispatch(disconnect());\n      alert(`SSH Error: ${err.message}`);\n    }\n  } else {\n    handleRedis(config);\n  }\n\n  function handleRedis(config, override) {\n    dispatch(updateConnectStatus('Redis connecting...'))\n    if (config.ssl) {\n      config.tls = {\n        rejectUnauthorized: false\n      };\n      if (config.tlsca) config.tls.ca = config.tlsca;\n      if (config.tlskey) config.tls.key = config.tlskey;\n      if (config.tlscert) config.tls.cert = config.tlscert;\n    }\n    const redis = new Redis(Object.assign({}, config, override, {\n      retryStrategy() {\n        return false;\n      }\n    }));\n    redis.defineCommand('setKeepTTL', {\n      numberOfKeys: 1,\n      lua: 'local ttl = redis.call(\"pttl\", KEYS[1]) if ttl > 0 then return redis.call(\"SET\", KEYS[1], ARGV[1], \"PX\", ttl) else return redis.call(\"SET\", KEYS[1], ARGV[1]) end'\n    });\n    redis.defineCommand('lremindex', {\n      numberOfKeys: 1,\n      lua: 'local FLAG = \"$$#__@DELETE@_REDIS_@PRO@__#$$\" redis.call(\"lset\", KEYS[1], ARGV[1], FLAG) redis.call(\"lrem\", KEYS[1], 1, FLAG)'\n    });\n    redis.defineCommand('duplicateKey', {\n      numberOfKeys: 2,\n      lua: 'local dump = redis.call(\"dump\", KEYS[1]) local pttl = 0 if ARGV[1] == \"TTL\" then pttl = redis.call(\"pttl\", KEYS[1]) end return redis.call(\"restore\", KEYS[2], pttl, dump)'\n    });\n    redis.once('connect', function () {\n      redis.ping((err, res) => {\n        if (err) {\n          if (err.message === 'Ready check failed: NOAUTH Authentication required.') {\n            err.message = 'Redis Error: Access denied. Please double-check your password.';\n          }\n          if (err.message !== 'Connection is closed.') {\n            alert(err.message);\n            redis.disconnect();\n          }\n          return;\n        }\n        const version = redis.serverInfo.redis_version;\n        if (version && version.length >= 5) {\n          const versionNumber = Number(version[0] + version[2]);\n          if (versionNumber < 28) {\n            alert('Medis only supports Redis >= 2.8 because servers older than 2.8 don\\'t support SCAN command, which means it not possible to access keys without blocking Redis.');\n            dispatch(disconnect());\n            return;\n          }\n        }\n        next({redis, config, index: getIndex(getState)});\n      })\n    });\n    redis.once('error', function (error) {\n      redisErrorMessage = error;\n    });\n    redis.once('end', function () {\n      dispatch(disconnect());\n      if (!sshErrorThrown) {\n        let msg = 'Redis Error: Connection failed. ';\n        if (redisErrorMessage) {\n          msg += `(${redisErrorMessage})`;\n        }\n        alert(msg);\n      }\n    });\n  }\n})\n"
  },
  {
    "path": "src/renderer/redux/actions/favorites.js",
    "content": "import {createAction} from 'Utils';\nimport {fromJS} from 'immutable'\nimport {Favorites} from '../../storage'\n\n\nexport const createFavorite = createAction('CREATE_FAVORITE', (data) => {\n  const key = `favorite-${Math.round(Math.random() * 100000)}`\n  return Object.assign({key}, data)\n})\n\nexport const reloadFavorites = createAction('RELOAD_FAVORITES', Favorites.get)\nexport const removeFavorite = createAction('REMOVE_FAVORITE')\nexport const reorderFavorites = createAction('REORDER_FAVORITES')\nexport const updateFavorite = createAction('UPDATE_FAVORITE', (key, data) => ({key, data}))\n"
  },
  {
    "path": "src/renderer/redux/actions/index.js",
    "content": "export * from './instances'\nexport * from './favorites'\nexport * from './patterns'\nexport * from './connection'\nexport * from './sizes'\n"
  },
  {
    "path": "src/renderer/redux/actions/instances.js",
    "content": "import {createAction} from 'Utils';\nimport {remote} from 'electron'\nimport {getId} from 'Utils'\n\nexport const createInstance = createAction('CREATE_INSTANCE', data => (\n  Object.assign({}, data, {key: getId('instance')})\n))\n\nexport const selectInstance = createAction('SELECT_INSTANCE')\n\nexport const moveInstance = createAction('MOVE_INSTANCE', (from, to) => ({getState, next}) => {\n  const {instances} = getState()\n\n  const [fromIndex, instance] = instances.findEntry(v => v.get('key') === from);\n  const toIndex = instances.findIndex(v => v.get('key') === to);\n\n  next({fromIndex, toIndex, activeInstanceKey: instance.get('key')})\n})\n\nexport const delInstance = createAction('DEL_INSTANCE', key => ({getState, next}) => {\n  const {activeInstanceKey, instances} = getState()\n  if (!key) {\n    key = activeInstanceKey\n  }\n\n  const targetIndex = instances.findIndex(instance => instance.get('key') === key);\n\n  const ret = {activeInstanceKey, targetIndex}\n\n  if (key === activeInstanceKey) {\n    const item = instances.get(targetIndex + 1) || (targetIndex > 0 && instances.get(targetIndex - 1))\n\n    console.log('still', item, targetIndex, instances.size)\n    if (item) {\n      ret.activeInstanceKey = item.get('key')\n    } else {\n      remote.getCurrentWindow().close();\n      return;\n    }\n  }\n\n  next(ret)\n})\n"
  },
  {
    "path": "src/renderer/redux/actions/patterns.js",
    "content": "import {createAction} from 'Utils'\nimport {Patterns} from '../../storage'\n\n\nexport const createPattern = createAction('CREATE_PATTERN', (conn) => {\n  const key = `pattern-${Math.round(Math.random() * 100000)}`\n  return Object.assign({key, conn})\n})\n\nexport const reloadPatterns = createAction('RELOAD_PATTERNS', Patterns.get)\nexport const removePattern = createAction('REMOVE_PATTERN', (conn, index) => ({conn, index}))\nexport const updatePattern = createAction('UPDATE_PATTERN', (conn, index, data) => ({conn, index, data}))\n"
  },
  {
    "path": "src/renderer/redux/actions/sizes.js",
    "content": "import {createAction} from 'Utils';\n\nexport const setSize = createAction('SET_SIZE', (type, value) => ({type, value: Number(value)}))\n"
  },
  {
    "path": "src/renderer/redux/middlewares/createThunkReplyMiddleware.js",
    "content": "function isThunkReply(action) {\n  return typeof action.payload === 'function' && action.args\n}\n\nexport default function createThunkReplyMiddleware(extraArgument) {\n  return function ({dispatch, getState}) {\n    return _next => action => {\n      if (!isThunkReply(action)) {\n        return _next(action)\n      }\n\n      function next(payload) {\n        dispatch({payload, type: action.type})\n      }\n\n      return action.payload(Object.assign({dispatch, getState, next}, extraArgument))\n    }\n  }\n}\n"
  },
  {
    "path": "src/renderer/redux/middlewares/index.js",
    "content": "import createThunkReplyMiddleware from './createThunkReplyMiddleware'\n\nexport {createThunkReplyMiddleware}\n"
  },
  {
    "path": "src/renderer/redux/persistEnhancer.js",
    "content": "import * as Storage from '../storage'\n\nconst whiteList = [\n  {key: 'patterns', storage: 'Patterns'},\n  {key: 'favorites', storage: 'Favorites'},\n  {key: 'sizes', storage: 'Sizes'}\n]\n\nexport default function (store) {\n  let lastState\n  store.subscribe(() => {\n    if (store.skipPersist) {\n      return\n    }\n    const state = store.getState()\n    whiteList.forEach(({key, storage}) => {\n      const value = state[key]\n      if (!lastState || value !== lastState[key]) {\n        Storage[storage].set(value)\n      }\n    })\n\n    lastState = state\n  })\n}\n"
  },
  {
    "path": "src/renderer/redux/reducers/activeInstanceKey.js",
    "content": "import {handleActions} from 'Utils'\nimport {\n  createInstance,\n  selectInstance,\n  moveInstance,\n  delInstance\n} from 'Redux/actions'\n\nexport const defaultInstanceKey = 'FIRST_INSTANCE'\n\nexport const activeInstanceKey = handleActions(defaultInstanceKey, {\n  [createInstance](state, data) {\n    return data.key\n  },\n  [selectInstance](state, data) {\n    return data\n  },\n  [moveInstance](state, {activeInstanceKey}) {\n    return activeInstanceKey\n  },\n  [delInstance](state, {activeInstanceKey}) {\n    console.log('==delInstance', activeInstanceKey)\n    return activeInstanceKey\n  }\n})\n"
  },
  {
    "path": "src/renderer/redux/reducers/favorites.js",
    "content": "import {handleActions} from 'Utils'\nimport {\n  createFavorite,\n  removeFavorite,\n  updateFavorite,\n  reorderFavorites,\n  reloadFavorites\n} from 'Redux/actions'\nimport {Favorites} from '../../storage'\nimport {Map, fromJS} from 'immutable'\n\nfunction FavoriteFactory(data) {\n  return Map(Object.assign({name: 'New Favorite'}, data))\n}\n\nexport const favorites = handleActions(fromJS(Favorites.get()), {\n  [createFavorite](state, data) {\n    return state.push(FavoriteFactory(data))\n  },\n  [removeFavorite](state, key) {\n    return state.filterNot(item => item.get('key') === key)\n  },\n  [updateFavorite](state, {key, data}) {\n    return state.map(item => item.get('key') === key ? item.merge(data) : item)\n  },\n  [reorderFavorites](state, {from, to}) {\n    const target = state.get(from);\n    return state.splice(from, 1).splice(to, 0, target);\n  },\n  [reloadFavorites](state, data) {\n    return fromJS(data)\n  }\n})\n"
  },
  {
    "path": "src/renderer/redux/reducers/index.js",
    "content": "'use strict';\n\nimport {combineReducers} from 'redux';\nimport {activeInstanceKey} from './activeInstanceKey'\nimport {instances} from './instances'\nimport {favorites} from './favorites'\nimport {patterns} from './patterns'\nimport {sizes} from './sizes'\n\nexport default combineReducers({\n  patterns,\n  favorites,\n  instances,\n  activeInstanceKey,\n  sizes\n});\n"
  },
  {
    "path": "src/renderer/redux/reducers/instances.js",
    "content": "import {handleActions} from 'Utils'\nimport {\n  createInstance,\n  moveInstance,\n  delInstance,\n  updateConnectStatus,\n  connectToRedis,\n  disconnect\n} from 'Redux/actions'\nimport {Map, List} from 'immutable'\nimport {defaultInstanceKey} from './activeInstanceKey'\n\nfunction InstanceFactory({key, data}) {\n  return Map(Object.assign({key, title: 'Medis'}, data))\n}\n\nexport const instances = handleActions(List([InstanceFactory({key: defaultInstanceKey})]), {\n  [createInstance](state, data) {\n    return state.push(InstanceFactory({data}))\n  },\n  [moveInstance](state, {fromIndex, toIndex}) {\n    const instance = state.get(fromIndex)\n    return state.splice(fromIndex, 1).splice(toIndex, 0, instance)\n  },\n  [delInstance](state, {targetIndex}) {\n    return state.remove(targetIndex)\n  },\n  [updateConnectStatus](state, {index, status}) {\n    return state.setIn([index, 'connectStatus'], status)\n  },\n  [disconnect](state, {index}) {\n    const properties = ['connectStatus', 'redis', 'config', 'version']\n    return state.update(index, instance => (\n      instance.withMutations(map => {\n        properties.forEach(key => map.remove(key))\n        map.set('title', 'Medis')\n      })\n    ))\n  },\n  [connectToRedis](state, {index, config, redis}) {\n    const {name, sshHost, host, port} = config\n    const remote = name ? `${name}/` : (sshHost ? `${sshHost}/` : '')\n    const address = `${host}:${port}`\n    const title = `${remote}${address}`\n    const connectionKey = `${sshHost || ''}|${host}|${port}`\n    const version = redis.serverInfo && redis.serverInfo.redis_version\n\n    return state.update(index, instance => (\n      instance.merge({config, title, redis, version, connectionKey}).remove('connectStatus')\n    ))\n  }\n})\n"
  },
  {
    "path": "src/renderer/redux/reducers/patterns.js",
    "content": "import {handleActions} from 'Utils'\nimport {\n  createPattern,\n  removePattern,\n  updatePattern,\n  reloadPatterns\n} from 'Redux/actions'\nimport {Patterns} from '../../storage'\nimport {Map, List, fromJS} from 'immutable'\n\nfunction PatternFactory(data) {\n  return Map(Object.assign({value: '*', name: '*'}, data))\n}\n\nexport const patterns = handleActions(fromJS(Patterns.get()), {\n  [createPattern](state, {conn, key}) {\n    return state.update(conn, List(), patterns => patterns.push(PatternFactory({key})))\n  },\n  [removePattern](state, {conn, index}) {\n    return state.update(conn, List(), patterns => patterns.remove(index))\n  },\n  [updatePattern](state, {conn, index, data}) {\n    return state.update(conn, List(), patterns => patterns.update(index, item => item.merge(data)))\n  },\n  // [reorderPatterns](state, {conn, from, to}) {\n  //   return state.update(conn, List(), patterns => {\n  //     const target = patterns.get(from);\n  //     return patterns.splice(from, 1).splice(to, 0, target);\n  //   })\n  // },\n  [reloadPatterns](state, data) {\n    return fromJS(data)\n  }\n})\n"
  },
  {
    "path": "src/renderer/redux/reducers/sizes.js",
    "content": "import {handleActions} from 'Utils'\nimport {\n  setSize\n} from 'Redux/actions'\nimport {Sizes} from '../../storage'\nimport {Map, List, fromJS} from 'immutable'\n\nexport const sizes = handleActions(fromJS(Sizes.get()), {\n  [setSize](state, {type, value}) {\n    return state.set(`${type}BarWidth`, value)\n  }\n})\n"
  },
  {
    "path": "src/renderer/redux/store.js",
    "content": "import {compose, createStore, applyMiddleware} from 'redux'\nimport persistEnhancer from './persistEnhancer'\nimport {createThunkReplyMiddleware} from 'Redux/middlewares'\nimport reducers from './reducers'\n\nconst store = window.store = createStore(\n  reducers,\n  applyMiddleware(createThunkReplyMiddleware())\n)\n\npersistEnhancer(store)\n\nexport default store\n"
  },
  {
    "path": "src/renderer/storage/Favorites.js",
    "content": "'use strict'\n\nimport {ipcRenderer} from 'electron'\n\nexport function get() {\n  const data = localStorage.getItem('favorites')\n  return data ? JSON.parse(data) : []\n}\n\nexport function set(favorites) {\n  localStorage.setItem('favorites', JSON.stringify(favorites))\n\n  ipcRenderer.send('dispatch', 'reloadFavorites')\n  return favorites\n}\n"
  },
  {
    "path": "src/renderer/storage/Patterns.js",
    "content": "'use strict'\n\nimport {ipcRenderer} from 'electron'\n\nexport function get() {\n  const data = localStorage.getItem('patternStore')\n  return data ? JSON.parse(data) : {}\n}\n\nexport function set(patterns) {\n  localStorage.setItem('patternStore', JSON.stringify(patterns))\n  ipcRenderer.send('dispatch', 'reloadPatterns')\n  return patterns\n}\n"
  },
  {
    "path": "src/renderer/storage/Sizes.js",
    "content": "'use strict'\n\nexport function get() {\n  const data = localStorage.getItem('sizes')\n  return data ? JSON.parse(data) : {}\n}\n\nexport function set(sizes) {\n  localStorage.setItem('sizes', JSON.stringify(sizes))\n  return sizes\n}\n"
  },
  {
    "path": "src/renderer/storage/index.js",
    "content": "import * as Favorites from './Favorites'\nimport * as Patterns from './Patterns'\nimport * as Sizes from './Sizes'\n\nexport {Favorites, Patterns, Sizes}\n"
  },
  {
    "path": "src/renderer/styles/global.scss",
    "content": "@import \"photon\";\n@import \"native\";\n\nhtml {\n  background: #ececec;\n}\n\nul {\n  margin: 0;\n  padding: 0;\n}\n\n.sidebar {\n  background: #f5f5f4;\n  display: flex;\n  flex-direction: column;\n\n  .nav-group {\n    overflow: auto;\n    flex: 1;\n  }\n}\n\n.main {\n  position: relative;\n  flex: 1;\n}\n\n.nav-group {\n  .sortable-placeholder {\n    width: 100%;\n    height: 26px;\n  }\n}\n\ntextarea {\n  &:focus {\n    outline: none;\n  }\n}\n\n.Pane.vertical {\n  height: 100%;\n  display: flex;\n  min-width: 0;\n}\n\n.fixedDataTableCellLayout_columnResizerContainer {\n  border-right: 1px solid #ccc;\n}\n"
  },
  {
    "path": "src/renderer/styles/native.scss",
    "content": ".nt-box {\n  box-sizing: border-box;\n  position: relative;\n  cursor: default;\n  background-color: rgba(0, 0, 0, .04);\n  border-width: 1px;\n  border-style: solid;\n  border-top-color: rgba(0, 0, 0, .07);\n  border-left-color: rgba(0, 0, 0, .037);\n  border-right-color: rgba(0, 0, 0, .037);\n  border-bottom-color: rgba(0, 0, 0, .026);\n  border-radius: 4px;\n  padding: 23px 18px 22px 18px;\n}\n\n.nt-form-row, .form-control {\n  padding: 5px 0;\n  $label-width: 140px;\n  label {\n    float: left;\n    width: $label-width;\n    text-align: right;\n    -webkit-user-select: text;\n  }\n  input, textarea, select {\n    margin-left: 10px;\n  }\n  input[type=\"text\"], input[type=\"number\"], input[type=\"password\"], select {\n    width: 250px;\n    -webkit-user-select: text;\n    border-width: 1px;\n    border-style: solid;\n    border-color: #b0b0b0;\n    border-left-color: #b1b1b1;\n    border-right-color: #b1b1b1;\n    box-shadow: inset 0 0 0 1px #f0f0f0;\n    padding-top: 4px;\n    padding-bottom: 4px;\n    padding-left: 3.5px;\n    padding-right: 3.5px;\n    line-height: 14px;\n    font-family: \"San Francisco\", \"Helvetica Neue\", \"Lucida Grande\", Arial, sans-serif;\n    font-size: 13px;\n    background: #fff;\n\n    &:focus {\n      outline: none;\n      border-color: #6691d6;\n      box-shadow: 0 0 0 2.5px #7ba7ec;\n      border-radius: 1px;\n    }\n\n    &:placeholder {\n      color: #c0c0c0;\n    }\n\n    &[disabled] {\n      background: #f8f8f8;\n    }\n  }\n\n  input[type=\"radio\"],\n  input[type=\"checkbox\"] {\n    line-height: normal;\n  }\n\n  &.nt-form-row--vertical {\n    overflow: visible;\n    label {\n      float: none;\n      display: block;\n      text-align: left;\n    }\n    input[type=\"text\"], input[type=\"password\"], textarea {\n      margin-left: 0;\n      width: 100%;\n    }\n  }\n}\n\n.nt-button {\n  cursor: default;\n  background-color: #ffffff;\n  outline: none;\n  border-width: 1px;\n  border-style: solid;\n  border-radius: 3px;\n  border-top-color: #c8c8c8;\n  border-bottom-color: #acacac;\n  border-left-color: #c2c2c2;\n  border-right-color: #c2c2c2;\n  box-shadow: 0 1px rgba(0, 0, 0, .039);\n  padding-top: 0;\n  padding-bottom: 0;\n  padding-left: 13px;\n  padding-right: 13px;\n  line-height: 19px;\n  font-size: 13px;\n\n  &:active {\n    background-image: -webkit-linear-gradient(top, #4c98fe 0%, #0564e3 100%);\n    border-top-color: #247fff;\n    border-bottom-color: #003ddb;\n    border-left-color: #125eed;\n    border-right-color: #125eed;\n    color: rgba(255, 255, 255, .9);\n  }\n\n  margin-right: 10px;\n  &:last-child {\n    margin-right: 0;\n  }\n}\n\n.nt-button--primary {\n  background-image: -webkit-linear-gradient(top, #6ca5fc 0%, #076aff 100%);\n  border-top-color: #4c93fa;\n  border-bottom-color: #0261ff;\n  border-left-color: #2d7efc;\n  border-right-color: #2d7efc;\n  color: rgba(255, 255, 255, .9);\n\n  &:active {\n    background-image: -webkit-linear-gradient(top, #4c98fe 0%, #0564e3 100%);\n    border-top-color: #247fff;\n    border-bottom-color: #003ddb;\n    border-left-color: #125eed;\n    border-right-color: #125eed;\n    color: rgba(255, 255, 255, .9);\n  }\n}\n\n.nt-button:focus {\n  outline: none;\n  border-color: #6691d6;\n  box-shadow: 0 0 0 2.5px #7ba7ec;\n}\n\n.nt-button[disabled], .nt-button--disabled {\n  background: #f1f1f1;\n  border-color: #d0d0d0;\n  color: #b1b1b1;\n}\n\n.nt-button-group {\n  text-align: center;\n\n  .nt-button {\n    display: inline-block;\n  }\n  &.nt-button-group--pull-right {\n    text-align: right;\n  }\n}\n"
  },
  {
    "path": "src/renderer/styles/photon.scss",
    "content": ".tab-item-btn {\n  width: 30px;\n  flex: none;\n}\n\n.tab-group {\n  background: #b3b1b3;\n}\n\n.nav-group-item:active {\n  background-color: transparent !important;\n}\n\n.nav-group-item.sortable-ghost {\n  opacity: 0;\n}\n\n.nav-group-item.active:active {\n  background-color: #dcdfe1 !important;\n}\n\n.window {\n  background: #ececec !important;\n}\n\n.toolbar-footer {\n  button {\n    width: 30px;\n    border: none;\n    border-right: 1px solid #c2c0c2;\n    box-shadow: 1px 0px 0px 0px rgba(255, 255, 255, 0.4);\n    background: transparent;\n    font-size: 18px;\n    line-height: 19px;\n    opacity: 0.8;\n    &:active {\n      background-color: #dcdfe1;\n    }\n  }\n}\n\nbutton:focus {\n  outline:0;\n}\n"
  },
  {
    "path": "src/renderer/utils.ts",
    "content": "import {createAction as _createAction} from 'redux-actions'\n\nexport function handleActions(defaultState, handlers) {\n  return function (state = defaultState, {type, payload}) {\n    const handler = handlers[type]\n    return handler ? handler(state, payload) : state\n  }\n}\n\nexport const getId = (function () {\n  const ids = {}\n\n  return function (item: string) {\n    if (!ids[item]) {\n      ids[item] = 0\n    }\n\n    return `${item}-${++ids[item] + (Math.random() * 100000 | 0)}`\n  }\n}())\n\nexport function createAction(type: string, payloadCreator, metaCreator) {\n  type = `$SOS_${type}`\n  const actionCreator = _createAction(type, payloadCreator, metaCreator)\n  const creator = (...args) => {\n    const action = actionCreator(...args)\n    if (typeof action.payload === 'function') {\n      return Object.assign(action, {args})\n    }\n    return action\n  }\n\n  return Object.assign(creator, {\n    toString: actionCreator.toString,\n    payload(payload) {\n      return {type, payload}\n    },\n    reply(args, result) {\n      return {type, payload: {args, result}}\n    }\n  })\n}\n"
  },
  {
    "path": "src/renderer/vendors/jquery.terminal/index.css",
    "content": "/*\n * This css file is part of jquery terminal\n *\n * Licensed under GNU LGPL Version 3 license\n * Copyright (c) 2011-2013 Jakub Jankiewicz <http://jcubic.pl>\n *\n */\n.terminal .terminal-output .format, .cmd .format,\n.cmd .prompt, .cmd .prompt div, .terminal .terminal-output div div{\n    display: inline-block;\n}\n.cmd .clipboard {\n    position: absolute;\n    bottom: 0;\n    left: 0;\n    opacity: 0.01;\n    filter: alpha(opacity = 0.01);\n    filter: progid:DXImageTransform.Microsoft.Alpha(opacity=0.01);\n    width: 2px;\n}\n.cmd > .clipboard {\n    position: fixed;\n}\n.terminal {\n    padding: 10px;\n    position: relative;\n    overflow: hidden;\n}\n.cmd {\n    padding: 0;\n    margin: 0;\n    height: 1.3em;\n    /*margin-top: 3px; */\n}\n.cmd .cursor.blink {\n    -webkit-animation: blink 1s infinite steps(1, start);\n       -moz-animation: blink 1s infinite steps(1, start);\n        -ms-animation: blink 1s infinite steps(1, start);\n            animation: blink 1s infinite steps(1, start);\n}\n@keyframes blink {\n  0%, 100% {\n        background-color: #000;\n        color: #aaa;\n  }\n  50% {\n        background-color: #bbb; /* not #aaa because it's seem there is Google Chrome bug */\n        color: #000;\n  }\n}\n@-webkit-keyframes blink {\n  0%, 100% {\n        background-color: #000;\n        color: #aaa;\n  }\n  50% {\n        background-color: #bbb;\n        color: #000;\n  }\n}\n@-ms-keyframes blink {\n  0%, 100% {\n        background-color: #000;\n        color: #aaa;\n  }\n  50% {\n        background-color: #bbb;\n        color: #000;\n  }\n}\n@-moz-keyframes blink {\n  0%, 100% {\n        background-color: #000;\n        color: #aaa;\n  }\n  50% {\n        background-color: #bbb;\n        color: #000;\n  }\n}\n.terminal .terminal-output div div, .cmd .prompt {\n    display: block;\n    line-height: 18px;\n    height: auto;\n}\n.cmd .prompt {\n    float: left;\n}\n.terminal, .cmd {\n    font-family: monospace;\n    color: #eed1b3;\n    background-color: #202020;\n    font-size: 14px;\n    line-height: 18px;\n}\n.terminal-output > div {\n    /*padding-top: 3px;*/\n    min-height: 14px;\n}\n.terminal .terminal-output div span {\n    display: inline-block;\n}\n.cmd span {\n    float: left;\n    /*display: inline-block; */\n}\n.terminal .inverted, .cmd .inverted, .cmd .cursor.blink {\n    background-color: #aaa;\n    color: #000;\n}\n.terminal .terminal-output div div::-moz-selection,\n.terminal .terminal-output div span::-moz-selection,\n.terminal .terminal-output div div a::-moz-selection {\n    background-color: #aaa;\n    color: #000;\n}\n.terminal .terminal-output div div::selection,\n.terminal .terminal-output div div a::selection,\n.terminal .terminal-output div span::selection,\n.cmd > span::selection,\n.cmd .prompt span::selection {\n    background-color: #aaa;\n    color: #000;\n}\n.terminal .terminal-output div.error, .terminal .terminal-output div.error div {\n    color: red;\n}\n.tilda {\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    z-index: 1100;\n}\n.clear {\n    clear: both;\n}\n.terminal a {\n    color: #0F60FF;\n}\n.terminal a:hover {\n    color: red;\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/ConnectionSelectorContainer/Config/index.jsx",
    "content": "'use strict'\n\nimport React from 'react'\nimport Immutable from 'immutable'\nimport {remote} from 'electron'\nimport fs from 'fs'\n\nrequire('./index.scss')\n\nclass Config extends React.PureComponent {\n  state = {\n    data: new Immutable.Map()\n  }\n\n  getProp(property) {\n    if (this.state.data.has(property)) {\n      return this.state.data.get(property)\n    }\n    return (this.props.favorite ? this.props.favorite.get(property) : '') || ''\n  }\n\n  setProp(property, value) {\n    this.setState({\n      data: typeof property === 'string' ? this.state.data.set(property, value) : this.state.data.merge(property),\n      changed: Boolean(this.props.favorite)\n    })\n  }\n\n  componentWillReceiveProps(nextProps) {\n    if (!this.props.connect && nextProps.connect) {\n      this.connect()\n    }\n    if (this.props.favorite || nextProps.favorite) {\n      const leaving = !this.props.favorite || !nextProps.favorite ||\n        (this.props.favorite.get('key') !== nextProps.favorite.get('key'))\n      if (leaving) {\n        this.setState({changed: false, data: new Immutable.Map()})\n      }\n    }\n  }\n\n  connect() {\n    const {favorite, connectToRedis} = this.props\n    const data = this.state.data\n    const config = favorite ? favorite.merge(data).toJS() : data.toJS()\n    config.host = config.host || 'localhost'\n    config.port = config.port || '6379'\n    config.sshPort = config.sshPort || '22'\n    connectToRedis(config)\n    this.save()\n  }\n\n  handleChange(property, e) {\n    let value = e.target.value\n    if (property === 'ssh' || property === 'ssl') {\n      value = e.target.checked\n    }\n    this.setProp(property, value)\n  }\n\n  duplicate() {\n    if (this.props.favorite) {\n      const data = Object.assign(this.props.favorite.toJS(), this.state.data.toJS())\n      delete data.key\n      this.props.onDuplicate(data)\n    } else {\n      const data = this.state.data.toJS()\n      data.name = 'Quick Connect'\n      this.props.onDuplicate(data)\n    }\n  }\n\n  save() {\n    if (this.props.favorite && this.state.changed) {\n      this.props.onSave(this.state.data.toJS())\n      this.setState({changed: false, data: new Immutable.Map()})\n    }\n  }\n\n  renderCertInput(label, id) {\n    return (<div className=\"nt-form-row\">\n      <label htmlFor=\"cert\">{label}:</label>\n      <input\n        type=\"text\"\n        id={id}\n        readOnly\n        value={this.getProp(`${id}File`)}\n        placeholder={`Select ${label} File (PEM)`}\n      />\n      <button\n        className={'icon icon-dot-3 ssh-key'}\n        onClick={() => {\n          const win = remote.getCurrentWindow()\n          const files = remote.dialog.showOpenDialog(win, {\n            properties: ['openFile']\n          })\n          if (files && files.length) {\n            const file = files[0]\n            const content = fs.readFileSync(file, 'utf8')\n            this.setProp({[id]: content, [`${id}File`]: file})\n          }\n        }}\n      />\n    </div>)\n  }\n\n  render() {\n    return (<div>\n      <div className=\"nt-box\" style={{width: 500, margin: '60px auto 0'}}>\n        <div className=\"nt-form-row\" style={{display: this.props.favorite ? 'block' : 'none'}}>\n          <label htmlFor=\"name\">Name:</label>\n          <input type=\"text\" id=\"name\" value={this.getProp('name')} onChange={this.handleChange.bind(this, 'name')} placeholder=\"Bookmark name\" />\n        </div>\n        <div className=\"nt-form-row\">\n          <label htmlFor=\"host\">Redis Host:</label>\n          <input type=\"text\" id=\"host\" value={this.getProp('host')} onChange={this.handleChange.bind(this, 'host')} placeholder=\"localhost\" />\n        </div>\n        <div className=\"nt-form-row\">\n          <label htmlFor=\"port\">Port:</label>\n          <input type=\"number\" id=\"port\" value={this.getProp('port')} onChange={this.handleChange.bind(this, 'port')} placeholder=\"6379\" min=\"0\" max=\"65535\"/>\n        </div>\n        <div className=\"nt-form-row\">\n          <label htmlFor=\"password\">Password:</label>\n          <input type=\"password\" id=\"password\" onChange={this.handleChange.bind(this, 'password')} value={this.getProp('password')} />\n        </div>\n        <div className=\"nt-form-row\">\n          <label htmlFor=\"ssh\">SSL:</label>\n          <input type=\"checkbox\" id=\"ssl\" onChange={this.handleChange.bind(this, 'ssl')} checked={this.getProp('ssl')} />\n        </div>\n        <div style={{display: this.getProp('ssl') ? 'block' : 'none'}}>\n          {this.renderCertInput('Private Key', 'tlskey')}\n          {this.renderCertInput('Certificate', 'tlscert')}\n          {this.renderCertInput('CA', 'tlsca')}\n        </div>\n        <div className=\"nt-form-row\">\n          <label htmlFor=\"ssh\">SSH Tunnel:</label>\n          <input type=\"checkbox\" id=\"ssh\" onChange={this.handleChange.bind(this, 'ssh')} checked={this.getProp('ssh')} />\n        </div>\n        <div style={{display: this.getProp('ssh') ? 'block' : 'none'}}>\n          <div className=\"nt-form-row\">\n            <label htmlFor=\"sshHost\">SSH Host:</label>\n            <input type=\"text\" id=\"sshHost\" onChange={this.handleChange.bind(this, 'sshHost')} value={this.getProp('sshHost')} placeholder=\"\" />\n          </div>\n          <div className=\"nt-form-row\">\n            <label htmlFor=\"sshUser\">SSH User:</label>\n            <input type=\"text\" id=\"sshUser\" onChange={this.handleChange.bind(this, 'sshUser')} value={this.getProp('sshUser')} placeholder=\"\" />\n          </div>\n          <div className=\"nt-form-row\">\n            <label htmlFor=\"sshPassword\">SSH {this.getProp('sshKey') ? 'Key' : 'Password'}:</label>\n            <input\n              type={this.getProp('sshKeyFile') ? 'text' : 'password'}\n              id=\"sshPassword\"\n              readOnly={Boolean(this.getProp('sshKey'))}\n              onChange={this.handleChange.bind(this, 'sshPassword')}\n              value={this.getProp('sshKeyFile') || this.getProp('sshPassword')}\n              placeholder=\"\"\n            />\n            <button\n              className={'icon icon-key ssh-key' + (this.getProp('sshKey') ? ' is-active' : '')}\n              onClick={() => {\n                if (this.getProp('sshKey')) {\n                  this.setProp({\n                    sshKey: false,\n                    sshKeyFile: false\n                  })\n                  return\n                }\n                const win = remote.getCurrentWindow()\n                const files = remote.dialog.showOpenDialog(win, {\n                  message: 'Select a private key (Most often in the ~/.ssh)',\n                  properties: ['openFile', 'showHiddenFiles']\n                })\n                if (files && files.length) {\n                  const file = files[0]\n                  const content = fs.readFileSync(file, 'utf8')\n                  this.setProp({sshKey: content, sshKeyFile: file})\n                }\n              }}\n            />\n          </div>\n          <div className=\"nt-form-row\" style={{display: this.getProp('sshKey') && this.getProp('sshKey').indexOf('ENCRYPTED') > -1 ? 'block' : 'none'}}>\n            <label htmlFor=\"sshKeyPassphrase\">SSH Key Passphrase:</label>\n            <input type=\"password\" id=\"sshKeyPassphrase\" onChange={this.handleChange.bind(this, 'sshKeyPassphrase')} value={this.getProp('sshKeyPassphrase')} />\n          </div>\n          <div className=\"nt-form-row\">\n            <label htmlFor=\"sshPort\">SSH Port:</label>\n            <input type=\"number\" min=\"0\" max=\"65535\" id=\"sshPort\" placeholder=\"22\" onChange={this.handleChange.bind(this, 'sshPort')} value={this.getProp('sshPort')} />\n          </div>\n        </div>\n      </div>\n      <div className=\"nt-button-group nt-button-group--pull-right\" style={{width: 500, margin: '10px auto 0', paddingBottom: 10}}>\n        <button\n          className=\"nt-button\" style={{float: 'left'}} onClick={() => {\n            this.duplicate()\n          }}\n        >{this.props.favorite ? 'Duplicate' : 'Add to Favorite'}</button>\n        <button\n          className=\"nt-button\"\n          style={{display: this.state.changed ? 'inline-block' : 'none'}}\n          onClick={() => {\n            this.save()\n          }}\n        >Save Changes</button>\n        <button\n          disabled={Boolean(this.props.connectStatus)} ref=\"connectButton\" className=\"nt-button nt-button--primary\" onClick={() => {\n            this.connect()\n          }}\n        >{this.props.connectStatus || (this.state.changed ? 'Save and Connect' : 'Connect')}</button>\n      </div>\n    </div>)\n  }\n}\n\nexport default Config\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/ConnectionSelectorContainer/Config/index.scss",
    "content": "#sshPassword {\n  padding-right: 32px;\n}\n\n.ssh-key {\n  height: 22px;\n  line-height: 22px;\n  padding: 0;\n  text-align: center;\n  width: 30px;\n  position: relative;\n  top: 0;\n  left: -30px;\n  border-top: 0;\n  border-bottom: 0;\n  border-right: 1px solid #b1b1b1;\n  border-left: 1px solid #b1b1b1;\n  background: linear-gradient(to bottom, #fafafa 0%, #f5f5f5 100%);\n\n  &.is-active {\n    color: #388af8;\n  }\n\n  &:active {\n    background: linear-gradient(to bottom, #c2c2c2 0%, #b6b5b6 100%);\n    color: #388af8;\n  }\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/ConnectionSelectorContainer/Favorite.jsx",
    "content": "'use strict'\n\nimport React from 'react'\nimport Sortable from 'sortablejs'\n\nclass Favorite extends React.PureComponent {\n  constructor() {\n    super()\n    this.state = {\n      activeKey: null\n    }\n    this._updateSortableKey()\n  }\n\n  _updateSortableKey() {\n    this.sortableKey = `sortable-${Math.round(Math.random() * 10000)}`\n  }\n\n  _bindSortable() {\n    const {reorderFavorites} = this.props\n\n    this.sortable = Sortable.create(this.refs.sortable, {\n      animation: 100,\n      onStart: evt => {\n        this.nextSibling = evt.item.nextElementSibling\n      },\n      onAdd: () => {\n        this._updateSortableKey()\n      },\n      onUpdate: evt => {\n        this._updateSortableKey()\n        reorderFavorites({from: evt.oldIndex, to: evt.newIndex})\n      }\n    })\n  }\n\n  componentDidMount() {\n    this._bindSortable()\n  }\n\n  componentDidUpdate() {\n    this._bindSortable()\n  }\n\n  onClick(index, evt) {\n    evt.preventDefault()\n    this.selectIndex(index)\n  }\n\n  onDoubleClick(index, evt) {\n    evt.preventDefault()\n    this.selectIndex(index, true)\n  }\n\n  selectIndex(index, connect) {\n    this.select(index === -1 ? null : this.props.favorites.get(index), connect)\n  }\n\n  select(favorite, connect) {\n    const activeKey = favorite ? favorite.get('key') : null\n    this.setState({activeKey})\n    if (connect) {\n      this.props.onRequireConnecting(activeKey)\n    } else {\n      this.props.onSelect(activeKey)\n    }\n  }\n\n  render() {\n    return (<div style={{flex: 1, display: 'flex', flexDirection: 'column', overflowY: 'hidden'}}>\n      <nav className=\"nav-group\">\n        <h5 className=\"nav-group-title\"/>\n        <a\n          className={'nav-group-item' + (this.state.activeKey ? '' : ' active')}\n          onClick={this.onClick.bind(this, -1)}\n          onDoubleClick={this.onDoubleClick.bind(this, -1)}\n          >\n          <span className=\"icon icon-flash\"/>\n          QUICK CONNECT\n        </a>\n        <h5 className=\"nav-group-title\">FAVORITES</h5>\n        <div ref=\"sortable\" key={this.sortableKey}>{\n          this.props.favorites.map((favorite, index) => {\n            return (<a\n              key={favorite.get('key')}\n              className={'nav-group-item' + (favorite.get('key') === this.state.activeKey ? ' active' : '')}\n              onClick={this.onClick.bind(this, index)}\n              onDoubleClick={this.onDoubleClick.bind(this, index)}\n              >\n              <span className=\"icon icon-home\"/>\n              <span>{favorite.get('name')}</span>\n            </a>)\n          })\n        }</div>\n      </nav>\n      <footer className=\"toolbar toolbar-footer\">\n        <button\n          onClick={() => {\n            this.props.createFavorite()\n          // TODO: auto select\n          // this.select(favorite);\n          }}\n          >+</button>\n        <button\n          onClick={\n          () => {\n            const key = this.state.activeKey\n            if (!key) {\n              return\n            }\n            showModal({\n              title: 'Delete the bookmark?',\n              button: 'Delete',\n              content: 'Are you sure you want to delete the selected bookmark? This action cannot be undone.'\n            }).then(() => {\n              const index = this.props.favorites.findIndex(favorite => key === favorite.get('key'))\n              this.props.removeFavorite(key)\n              this.selectIndex(index - 1)\n            })\n          }\n        }\n          >-</button>\n      </footer>\n    </div>)\n  }\n\n  componentWillUnmount() {\n    this.sortable.destroy()\n  }\n}\n\nexport default Favorite\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/ConnectionSelectorContainer/index.jsx",
    "content": "'use strict'\n\nimport React, {PureComponent} from 'react'\nimport {connect} from 'react-redux'\nimport Favorite from './Favorite'\nimport Config from './Config'\nimport {connectToRedis} from 'Redux/actions'\nimport {removeFavorite, updateFavorite, createFavorite, reorderFavorites} from 'Redux/actions'\n\nclass ConnectionSelector extends PureComponent {\n  state = {connect: false, key: null}\n\n  handleSelectFavorite(connect, key) {\n    this.setState({connect, key})\n  }\n\n  render() {\n    const selectedFavorite = this.state.key && this.props.favorites.find(item => item.get('key') === this.state.key)\n    return (<div className=\"pane-group\">\n      <aside className=\"pane pane-sm sidebar\">\n        <Favorite\n          favorites={this.props.favorites}\n          onSelect={this.handleSelectFavorite.bind(this, false)}\n          onRequireConnecting={this.handleSelectFavorite.bind(this, true)}\n          updateFavorite={this.props.updateFavorite}\n          createFavorite={this.props.createFavorite}\n          removeFavorite={this.props.removeFavorite}\n          reorderFavorites={this.props.reorderFavorites}\n          />\n      </aside>\n      <div className=\"pane\">\n        <Config\n          favorite={selectedFavorite}\n          connectStatus={this.props.connectStatus}\n          connect={this.state.connect}\n          connectToRedis={this.props.connectToRedis}\n          onSave={data => {\n            this.props.updateFavorite(selectedFavorite.get('key'), data)\n          }}\n          onDuplicate={this.props.createFavorite}\n          />\n      </div>\n    </div>)\n  }\n}\n\nfunction mapStateToProps(state, {instance}) {\n  return {\n    favorites: state.favorites,\n    connectStatus: instance.get('connectStatus')\n  }\n}\nconst mapDispatchToProps = {\n  updateFavorite,\n  createFavorite,\n  connectToRedis,\n  reorderFavorites,\n  removeFavorite\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(ConnectionSelector)\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/AddButton/index.jsx",
    "content": "import React, {memo} from 'react'\n\nrequire('./index.scss')\n\nfunction AddButton({title, reload, onReload, onClick}) {\n  return (<div className=\"AddButton\">\n    {title}\n    {reload && <span className=\"reload icon icon-cw\" onClick={onReload} />}\n    <span className=\"plus\" onClick={onClick}>+</span>\n  </div>)\n}\n\nexport default memo(AddButton)\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/AddButton/index.scss",
    "content": ".AddButton {\n  position: relative;\n\n  span.plus, span.reload {\n    position: absolute;\n    right: 4px;\n    top: 4px;\n    width: 16px;\n    height: 16px;\n    line-height: 13px;\n    border: 1px solid #ccc;\n    border-radius: 2px;\n    background-image: linear-gradient(#fff, #efefef);\n    text-align: center;\n    font-weight: normal;\n    color: #888;\n\n    &:hover {\n      background: #fff;\n    }\n\n    &:active {\n      background: #efefef;\n    }\n  }\n\n  span.reload {\n    right: 24px;\n  }\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/Config/index.jsx",
    "content": "'use strict'\n\nimport React from 'react'\nimport clone from 'lodash.clone'\n\nrequire('./index.scss')\n\nclass Config extends React.Component {\n  constructor(props) {\n    super(props)\n    this.writeableFields = [\n      'dbfilename',\n      'requirepass',\n      'masterauth',\n      'maxclients',\n      'appendonly',\n      'save',\n      'dir',\n      'client-output-buffer-limit',\n      'notify-keyspace-events',\n      'rdbcompression',\n      'repl-disable-tcp-nodelay',\n      'repl-diskless-sync',\n      'cluster-require-full-coverage',\n      'aof-rewrite-incremental-fsync',\n      'aof-load-truncated',\n      'slave-serve-stale-data',\n      'slave-read-only',\n      'activerehashing',\n      'stop-writes-on-bgsave-error',\n      'lazyfree-lazy-eviction',\n      'lazyfree-lazy-expire',\n      'lazyfree-lazy-server-del',\n      'slave-lazy-flush',\n      'tcp-keepalive',\n      'maxmemory-samples',\n      'timeout',\n      'auto-aof-rewrite-percentage',\n      'auto-aof-rewrite-min-size',\n      'hash-max-ziplist-entries',\n      'hash-max-ziplist-value',\n      'list-max-ziplist-entries',\n      'list-max-ziplist-value',\n      'list-max-ziplist-size',\n      'list-compress-depth',\n      'set-max-intset-entries',\n      'zset-max-ziplist-entries',\n      'zset-max-ziplist-value',\n      'hll-sparse-max-bytes',\n      'lua-time-limit',\n      'slowlog-log-slower-than',\n      'slowlog-max-len',\n      'latency-monitor-threshold',\n      'repl-ping-slave-period',\n      'repl-timeout',\n      'repl-backlog-ttl',\n      'repl-diskless-sync-delay',\n      'slave-priority',\n      'min-slaves-to-write',\n      'min-slaves-max-lag',\n      'cluster-node-timeout',\n      'cluster-migration-barrier',\n      'cluster-slave-validity-factor',\n      'hz',\n      'watchdog-period',\n      'maxmemory',\n      'repl-backlog-size',\n      'loglevel',\n      'maxmemory-policy',\n      'appendfsync'\n    ]\n    this.groups = [\n      {\n        name: 'General',\n        configs: [\n          {name: 'port', type: 'number'},\n          {name: 'bind'},\n          {name: 'unixsocket'},\n          {name: 'unixsocketperm', type: 'number'},\n          {name: 'daemonize', type: 'boolean'},\n          {name: 'pidfile'},\n          {name: 'tcp-backlog', type: 'number'},\n          {name: 'tcp-keepalive', type: 'number'},\n          {name: 'timeout', type: 'number'},\n          {name: 'databases', type: 'number'}\n        ]\n      },\n      {\n        name: 'Logging',\n        configs: [\n          {name: 'loglevel', type: ['debug', 'verbose', 'notice', 'warning']},\n          {name: 'logfile'},\n          {name: 'syslog-enabled', type: 'boolean'},\n          {name: 'syslog-ident'},\n          {name: 'syslog-facility'}\n        ]\n      },\n      {\n        name: 'Snap Shotting',\n        configs: [\n          {name: 'dbfilename'},\n          {name: 'dir'},\n          {name: 'save'},\n          {name: 'stop-writes-on-bgsave-error', type: 'boolean'},\n          {name: 'rdbcompression', type: 'boolean'},\n          {name: 'rdbchecksum', type: 'boolean'}\n        ]\n      },\n      {\n        name: 'Replication',\n        configs: [\n          {name: 'slaveof'},\n          {name: 'masterauth'},\n          {name: 'slave-serve-stale-data', type: 'boolean'},\n          {name: 'slave-read-only', type: 'boolean'},\n          {name: 'repl-diskless-sync', type: 'boolean'},\n          {name: 'repl-diskless-sync-delay', type: 'number'},\n          {name: 'repl-ping-slave-period', type: 'number'},\n          {name: 'repl-timeout', type: 'number'},\n          {name: 'repl-disable-tcp-nodelay', type: 'boolean'},\n          {name: 'repl-backlog-size'},\n          {name: 'repl-backlog-ttl', type: 'number'},\n          {name: 'slave-priority', type: 'number'},\n          {name: 'min-slaves-to-write', type: 'number'},\n          {name: 'min-slaves-max-lag', type: 'number'}\n        ]\n      },\n      {\n        name: 'Security',\n        configs: [\n          {name: 'requirepass'},\n          {name: 'rename-command'}\n        ]\n      },\n      {\n        name: 'Limits',\n        configs: [\n          {name: 'maxclients'},\n          {name: 'maxmemory'},\n          {name: 'maxmemory-policy', type: ['noeviction', 'volatile-lru', 'allkeys-lru', 'volatile-random', 'allkeys-random', 'volatile-ttl']},\n          {name: 'maxmemory-samples', type: 'number'}\n        ]\n      },\n      {\n        name: 'Append Only Mode',\n        configs: [\n          {name: 'appendonly', type: 'boolean'},\n          {name: 'appendfilename'},\n          {name: 'appendfsync', type: ['everysec', 'always', 'no']},\n          {name: 'no-appendfsync-on-rewrite', type: 'boolean'},\n          {name: 'auto-aof-rewrite-percentage', type: 'number'},\n          {name: 'auto-aof-rewrite-min-size'},\n          {name: 'aof-load-truncated', type: 'number'}\n        ]\n      },\n      {\n        name: 'LUA Scripting',\n        configs: [\n          {name: 'lua-time-limit', type: 'number'}\n        ]\n      },\n      {\n        name: 'Cluster',\n        configs: [\n          {name: 'cluster-enabled', type: 'boolean'},\n          {name: 'cluster-config-file'},\n          {name: 'cluster-node-timeout', type: 'number'},\n          {name: 'cluster-slave-validity-factor', type: 'nubmer'},\n          {name: 'cluster-migration-barrier', type: 'number'},\n          {name: 'cluster-require-full-coverage', type: 'boolean'}\n        ]\n      },\n      {\n        name: 'Slow Log',\n        configs: [\n          {name: 'slowlog-log-slower-than', type: 'number'},\n          {name: 'slowlog-max-len', type: 'number'}\n        ]\n      },\n      {\n        name: 'Latency Monitor',\n        configs: [\n          {name: 'latency-monitor-threshold', type: 'number'}\n        ]\n      },\n      {\n        name: 'Event Notification',\n        configs: [\n          {name: 'notify-keyspace-events'}\n        ]\n      },\n      {\n        name: 'Advanced Config',\n        configs: [\n          {name: 'hash-max-ziplist-entries', type: 'number'},\n          {name: 'hash-max-ziplist-value', type: 'number'},\n          {name: 'list-max-ziplist-entries', type: 'number'},\n          {name: 'list-max-ziplist-value', type: 'number'},\n          {name: 'set-max-intset-entries', type: 'number'},\n          {name: 'zset-max-ziplist-entries', type: 'number'},\n          {name: 'zset-max-ziplist-value', type: 'number'},\n          {name: 'hll-sparse-max-bytes', type: 'number'},\n          {name: 'activerehashing', type: 'boolean'},\n          {name: 'client-output-buffer-limit'},\n          {name: 'hz', type: 'number'},\n          {name: 'aof-rewrite-incremental-fsync', type: 'boolean'}\n        ]\n      }\n    ]\n    this.state = {\n      groups: [],\n      unsavedRewrites: {},\n      unsavedConfigs: {}\n    }\n    this.load()\n  }\n\n  load() {\n    this.props.redis.config('get', '*').then(config => {\n      const configs = {}\n\n      for (let i = 0; i < config.length - 1; i += 2) {\n        configs[config[i]] = config[i + 1]\n      }\n\n      const groups = clone(this.groups, true).map(g => {\n        g.configs = g.configs.map(c => {\n          if (typeof configs[c.name] !== 'undefined') {\n            c.value = configs[c.name]\n            delete configs[c.name]\n          }\n          return c\n        }).filter(c => typeof c.value !== 'undefined')\n        return g\n      }).filter(g => g.configs.length)\n\n      if (Object.keys(configs).length) {\n        groups.push({name: 'Other', configs: Object.keys(configs).map(key => {\n          return {\n            name: key,\n            value: configs[key]\n          }\n        })})\n      }\n\n      this.setState({\n        groups,\n        unsavedConfigs: {},\n        unsavedRewrites: {}\n      })\n    })\n  }\n\n  componentWillUnmount() {\n    this.props.redis.removeAllListeners('select', this.onSelectBinded)\n  }\n\n  renderGroup(group) {\n    return (<div\n      key={group.name}\n      className=\"config-group\"\n      >\n      <h3>{group.name}</h3>\n      { group.configs.map(this.renderConfig, this) }\n    </div>)\n  }\n\n  change({name, value}) {\n    this.state.unsavedConfigs[name] = value\n    this.state.unsavedRewrites[name] = value\n    this.setState({\n      groups: this.state.groups,\n      unsavedConfigs: this.state.unsavedConfigs,\n      unsavedRewrites: this.state.unsavedRewrites\n    })\n  }\n\n  renderConfig(config) {\n    let input\n    const props = {readOnly: this.writeableFields.indexOf(config.name) === -1}\n    props.disabled = props.readOnly\n    if (config.type === 'boolean' &&\n        (config.value === 'yes' || config.value === 'no')) {\n      input = (<input\n        type=\"checkbox\" checked={config.value === 'yes'} onChange={e => {\n          config.value = e.target.checked ? 'yes' : 'no'\n          this.change(config)\n        }} {...props}\n           />)\n    } else if (config.type === 'number' && String(parseInt(config.value, 10)) === config.value) {\n      input = (<input\n        type=\"number\" value={config.value} onChange={e => {\n          config.value = e.target.value\n          this.change(config)\n        }} {...props}\n           />)\n    } else if (Array.isArray(config.type) && config.type.indexOf(config.value) !== -1) {\n      input = (<select\n        value={config.value} onChange={e => {\n          config.value = e.target.value\n          this.change(config)\n        }} {...props}\n           >\n        {config.type.map(option => <option key={option}>{option}</option>)}\n      </select>)\n    } else {\n      input = (<input\n        type=\"text\" value={config.value} onChange={e => {\n          config.value = e.target.value\n          this.change(config)\n        }} {...props}\n           />)\n    }\n    return (<div\n      className=\"nt-form-row\"\n      key={config.name}\n      >\n      <label htmlFor={config.name}>{config.name}</label>\n      { input }\n      <div className=\"description\">{config.description}</div>\n    </div>)\n  }\n\n  isChanged(rewrite) {\n    return Object.keys(this.state[rewrite ? 'unsavedRewrites' : 'unsavedConfigs']).length > 0\n  }\n\n  handleReload() {\n    if (this.isChanged()) {\n      showModal({\n        title: 'Reload config?',\n        content: 'Are you sure you want to reload the config? Your changes will be lost if you reload the config.',\n        button: 'Reload'\n      }).then(() => {\n        this.load()\n      })\n    } else {\n      this.load()\n    }\n  }\n\n  handleSave() {\n    showModal({\n      title: 'Save the changes',\n      content: 'Are you sure you want to apply the changes and save the changes to the config file?',\n      button: 'Save'\n    }).then(() => {\n      return this.handleApply(true)\n    }).then(res => {\n      return this.props.redis.config('rewrite')\n    }).then(res => {\n      this.setState({unsavedRewrites: {}})\n    }).catch(err => {\n      alert(err.message)\n    })\n  }\n\n  handleApply(embed) {\n    const confirm = embed ? Promise.resolve(1) : showModal({\n      title: 'Apply the changes',\n      content: 'Are you sure you want to apply the changes? The changes are only valid for the current session and will be lost when Redis restarts.',\n      button: 'Apply'\n    })\n    return confirm.then(() => {\n      const pipeline = this.props.redis.pipeline()\n      const unsavedConfigs = this.state.unsavedConfigs\n      Object.keys(unsavedConfigs).forEach(config => {\n        pipeline.config('set', config, unsavedConfigs[config])\n      })\n      return pipeline.exec()\n    }).then(res => {\n      this.setState({unsavedConfigs: {}})\n    })\n  }\n\n  render() {\n    return (<div style={this.props.style} className=\"Config\">\n      <div className=\"wrapper\">\n        <form>\n          {\n            this.state.groups.map(this.renderGroup, this)\n          }\n        </form>\n        <div className=\"nt-button-group nt-button-group--pull-right\">\n          <button\n            ref=\"submit\"\n            className=\"nt-button\"\n            onClick={this.handleReload.bind(this)}\n            >Reload</button>\n          <button\n            ref=\"submit\"\n            className=\"nt-button\"\n            disabled={!this.isChanged(true)}\n            onClick={this.handleSave.bind(this)}\n            >Save To Config File</button>\n          <button\n            ref=\"cancel\"\n            className=\"nt-button nt-button--primary\"\n            disabled={!this.isChanged()}\n            onClick={() => {\n              this.handleApply()\n            }}\n            >Apply</button>\n        </div>\n      </div>\n    </div>)\n  }\n}\n\nexport default Config\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/Config/index.scss",
    "content": ".Config {\n  overflow: auto;\n  .wrapper {\n    width: 430px;\n    margin: 0 auto;\n    .nt-form-row label {\n      width: 170px;\n    }\n  }\n\n  h3 {\n    font-size: 20px;\n  }\n\n  .nt-button-group {\n    margin: 20px 0;\n  }\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/Footer.jsx",
    "content": "'use strict'\n\nimport React from 'react'\nimport humanFormat from 'human-format'\n\nconst timeScale = new humanFormat.Scale({\n  ms: 1,\n  s: 1000,\n  min: 60000,\n  h: 3600000,\n  d: 86400000\n})\n\nconst initState = {\n  ttl: null,\n  encoding: null,\n  size: null\n}\n\nclass Footer extends React.PureComponent {\n  state = initState\n\n  init(keyName, keyType) {\n    if (!keyType && keyType !== 'none') {\n      this.setState(initState)\n      return\n    }\n    const pipeline = this.props.redis.pipeline()\n    pipeline.pttl(keyName)\n    pipeline.object('ENCODING', keyName)\n\n    let sizeUnit = 'Members'\n    switch (keyType) {\n      case 'string': pipeline.strlen(keyName); sizeUnit = 'Bytes'; break\n      case 'hash': pipeline.hlen(keyName); break\n      case 'list': pipeline.llen(keyName); break\n      case 'set': pipeline.scard(keyName); break\n      case 'zset': pipeline.zcard(keyName); break\n    }\n\n    pipeline.exec((err, [[err1, pttl], [err2, encoding], res3]) => {\n      this.setState({\n        encoding: encoding ? `Encoding: ${encoding}` : '',\n        ttl: pttl >= 0 ? `TTL: ${humanFormat(pttl, {scale: timeScale}).replace(' ', '')}` : null,\n        size: (res3 && res3[1]) ? `${sizeUnit}: ${res3[1]}` : null\n      })\n    })\n  }\n\n  componentDidMount() {\n    this.init(this.props.keyName, this.props.keyType)\n  }\n\n  componentWillReceiveProps(nextProps) {\n    if (nextProps.keyName !== this.props.keyName ||\n      nextProps.keyType !== this.props.keyType ||\n      nextProps.version !== this.props.version) {\n      this.init(nextProps.keyName, nextProps.keyType)\n    }\n  }\n\n  render() {\n    const desc = ['size', 'encoding', 'ttl']\n      .map(key => ({key, value: this.state[key]}))\n      .filter(item => typeof item.value === 'string')\n    return (<footer className=\"toolbar toolbar-footer\">\n      {\n        desc.map(({key, value}) => <span\n          key={key}\n          style={{margin: '0 5px'}}\n        >{value}</span>)\n      }\n    </footer>)\n  }\n}\n\nexport default Footer\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/KeyContent/BaseContent/Editor/index.jsx",
    "content": "'use strict'\n\nimport React from 'react'\nimport ReactDOM from 'react-dom'\nimport Codemirror from 'medis-react-codemirror'\nrequire('codemirror/mode/javascript/javascript')\nrequire('codemirror/addon/lint/json-lint')\nrequire('codemirror/addon/lint/lint')\nrequire('codemirror/addon/selection/active-line')\nrequire('codemirror/addon/edit/closebrackets')\nrequire('codemirror/addon/edit/matchbrackets')\nrequire('codemirror/addon/search/search')\nrequire('codemirror/addon/search/searchcursor')\nrequire('codemirror/addon/search/jump-to-line')\nrequire('codemirror/addon/dialog/dialog')\nrequire('codemirror/addon/dialog/dialog.css')\nimport jsonlint from 'jsonlint'\nwindow.jsonlint = jsonlint.parser\nrequire('codemirror/lib/codemirror.css')\nrequire('codemirror/addon/lint/lint.css')\nconst msgpack = require('msgpack5')()\n\nrequire('./index.scss')\n\nclass Editor extends React.PureComponent {\n  constructor() {\n    super()\n\n    this.resizeObserver = new ResizeObserver(() => {\n      this.updateLayout()\n    })\n\n    this.state = {\n      currentMode: '',\n      wrapping: true,\n      changed: false,\n      modes: {\n        raw: false,\n        json: false,\n        messagepack: false\n      }\n    }\n  }\n\n  updateLayout() {\n    const {wrapSelector, codemirror} = this.refs\n\n    const $this = $(ReactDOM.findDOMNode(this))\n    if ($this.width() < 372) {\n      $(ReactDOM.findDOMNode(wrapSelector)).hide()\n    } else {\n      $(ReactDOM.findDOMNode(wrapSelector)).show()\n    }\n    if (codemirror) {\n      codemirror.getCodeMirror().refresh()\n    }\n  }\n\n  componentDidMount() {\n    this.init(this.props.buffer)\n    this.resizeObserver.observe(ReactDOM.findDOMNode(this))\n  }\n\n  componentWillUnmount() {\n    this.resizeObserver.disconnect()\n  }\n\n  componentWillReceiveProps(nextProps) {\n    if (nextProps.buffer !== this.props.buffer) {\n      this.init(nextProps.buffer)\n    }\n  }\n\n  init(buffer) {\n    if (!buffer) {\n      this.setState({currentMode: '', changed: false})\n      return\n    }\n    const content = buffer.toString()\n    const modes = {}\n    modes.raw = content\n    modes.json = tryFormatJSON(content, true)\n    modes.messagepack = modes.json ? false : tryFormatMessagepack(buffer, true)\n    let currentMode = 'raw'\n    if (modes.messagepack) {\n      currentMode = 'messagepack'\n    } else if (modes.json) {\n      currentMode = 'json'\n    }\n    this.setState({modes, currentMode, changed: false}, () => {\n      this.updateLayout()\n    })\n  }\n\n  save() {\n    let content = this.state.modes.raw\n    if (this.state.currentMode === 'json') {\n      content = tryFormatJSON(this.state.modes.json)\n      if (!content) {\n        alert('The json is invalid. Please check again.')\n        return\n      }\n    } else if (this.state.currentMode === 'messagepack') {\n      content = tryFormatMessagepack(this.state.modes.messagepack)\n      if (!content) {\n        alert('The json is invalid. Please check again.')\n        return\n      }\n      content = msgpack.encode(JSON.parse(content))\n    }\n    this.props.onSave(content, err => {\n      if (err) {\n        alert(`Redis save failed: ${err.message}`)\n      } else {\n        this.init(typeof content === 'string' ? Buffer.from(content) : content)\n      }\n    })\n  }\n\n  updateContent(mode, content) {\n    if (this.state.modes[mode] !== content) {\n      this.state.modes[mode] = content\n      this.setState({modes: this.state.modes, changed: true})\n    }\n  }\n\n  updateMode(evt) {\n    const newMode = evt.target.value\n    this.setState({currentMode: newMode})\n  }\n\n  focus() {\n    const codemirror = this.refs.codemirror\n    if (codemirror) {\n      const node = ReactDOM.findDOMNode(codemirror)\n      if (node) {\n        node.focus()\n      }\n    }\n  }\n\n  handleKeyDown(evt) {\n    if (!evt.ctrlKey && evt.metaKey && evt.keyCode === 83) {\n      this.save()\n      evt.preventDefault()\n      evt.stopPropagation()\n    }\n  }\n\n  render() {\n    let viewer\n    if (this.state.currentMode === 'raw') {\n      viewer = (<Codemirror\n        ref=\"codemirror\"\n        key=\"raw\"\n        value={this.state.modes.raw}\n        onChange={this.updateContent.bind(this, 'raw')}\n        options={{\n          mode: 'none',\n          styleActiveLine: true,\n          lineWrapping: this.state.wrapping,\n          gutters: ['CodeMirror-lint-markers'],\n          lineNumbers: true\n        }}\n        />)\n    } else if (this.state.currentMode === 'json') {\n      viewer = (<Codemirror\n        ref=\"codemirror\"\n        key=\"json\"\n        value={this.state.modes.json}\n        onChange={this.updateContent.bind(this, 'json')}\n        options={{\n          mode: {\n            name: 'javascript',\n            json: true\n          },\n          tabSize: 2,\n          indentWithTabs: true,\n          styleActiveLine: true,\n          lineNumbers: true,\n          lineWrapping: this.state.wrapping,\n          gutters: ['CodeMirror-lint-markers'],\n          autoCloseBrackets: true,\n          matchTags: true,\n          lint: Boolean(this.state.modes.raw)\n        }}\n        />)\n    } else if (this.state.currentMode === 'messagepack') {\n      viewer = (<Codemirror\n        ref=\"codemirror\"\n        key=\"messagepack\"\n        value={this.state.modes.messagepack}\n        onChange={this.updateContent.bind(this, 'messagepack')}\n        options={{\n          mode: {\n            name: 'javascript',\n            json: true\n          },\n          tabSize: 2,\n          indentWithTabs: true,\n          styleActiveLine: true,\n          lineNumbers: true,\n          lineWrapping: this.state.wrapping,\n          gutters: ['CodeMirror-lint-markers'],\n          autoCloseBrackets: true,\n          matchTags: true,\n          lint: Boolean(this.state.modes.raw)\n        }}\n        />)\n    } else {\n      viewer = <div/>\n    }\n    return (<div\n      style={{flex: 1, display: 'flex', flexDirection: 'column'}}\n      className=\"Editor\"\n      onKeyDown={this.handleKeyDown.bind(this)}\n      >\n      { viewer }\n      <div\n        className=\"operation-pannel\"\n        >\n        <label className=\"wrap-selector\" ref=\"wrapSelector\">\n          <input\n            type=\"checkbox\"\n            checked={this.state.wrapping}\n            onChange={evt => this.setState({wrapping: evt.target.checked})}\n            />\n          <span>Wrapping</span>\n        </label>\n        <select\n          className=\"mode-selector\"\n          value={this.state.currentMode}\n          onChange={this.updateMode.bind(this)}\n          >\n          <option value=\"raw\" disabled={typeof this.state.modes.raw !== 'string'}>Raw</option>\n          <option value=\"json\" disabled={typeof this.state.modes.json !== 'string'}>JSON</option>\n          <option value=\"messagepack\" disabled={typeof this.state.modes.messagepack !== 'string'}>MessagePack</option>\n        </select>\n        <button\n          className=\"nt-button\"\n          disabled={!this.state.changed}\n          onClick={this.save.bind(this)}\n          >Save Changes</button>\n      </div>\n    </div>)\n  }\n}\n\nexport default Editor\n\nfunction tryFormatJSON(jsonString, beautify) {\n  try {\n    const o = JSON.parse(jsonString)\n    if (o && typeof o === 'object' && o !== null) {\n      if (beautify) {\n        return JSON.stringify(o, null, '\\t')\n      }\n      return JSON.stringify(o)\n    }\n  } catch (e) { /**/ }\n\n  return false\n}\n\nfunction tryFormatMessagepack(buffer, beautify) {\n  try {\n    let o\n    if (typeof buffer === 'string') {\n      o = JSON.parse(buffer)\n    } else {\n      o = msgpack.decode(buffer)\n    }\n    if (o && typeof o === 'object' && o !== null) {\n      if (beautify) {\n        return JSON.stringify(o, null, '\\t')\n      }\n      return JSON.stringify(o)\n    }\n  } catch (e) { /**/ }\n\n  return false\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/KeyContent/BaseContent/Editor/index.scss",
    "content": ".Editor {\n  position: relative;\n  min-width: 0;\n  textarea {\n    border: none;\n    width: 100%;\n    height: 100%;\n  }\n\n  .CodeMirror {\n    height: auto;\n    flex: 1;\n    display: flex;\n    flex-direction: column;\n    font-family: Consolas, monospace;\n  }\n\n  .ReactCodeMirror {\n    position: relative;\n    flex: 1;\n    display: flex;\n    width: 100%;\n    overflow-y: auto;\n\n    &:before {\n      content: '';\n      position: absolute;\n      left: 0;\n      top: 0;\n      width: 45px;\n      z-index: 1;\n      background: #f7f7f7;\n      border-right: 1px solid #ddd;\n      height: 100%;\n    }\n  }\n\n  .operation-pannel {\n    z-index: 99;\n  }\n\n  .mode-selector {\n    position: absolute;\n    bottom: 10px;\n    right: 10px;\n  }\n\n  .wrap-selector {\n    position: absolute;\n    bottom: 5px;\n    right: 120px;\n    padding: 0 10px;\n    span {\n      margin-left: 4px;\n    }\n  }\n\n  button {\n    position: absolute;\n    bottom: 10px;\n    left: 54px;\n    z-index: 99;\n  }\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/KeyContent/BaseContent/HashContent.jsx",
    "content": "'use strict'\n\nimport React from 'react'\nimport BaseContent from '.'\nimport SplitPane from 'react-split-pane'\nimport {Table, Column} from 'fixed-data-table-contextmenu'\nimport Editor from './Editor'\nimport AddButton from '../../../AddButton'\nimport ContentEditable from '../../../ContentEditable'\nimport ReactDOM from 'react-dom'\nimport {clipboard, remote} from 'electron'\n\nclass HashContent extends BaseContent {\n  save(value, callback) {\n    if (typeof this.state.selectedIndex === 'number') {\n      const [key] = this.state.members[this.state.selectedIndex]\n      this.state.members[this.state.selectedIndex][1] = Buffer.from(value)\n      this.setState({members: this.state.members})\n      this.props.redis.hset(this.state.keyName, key, value, (err, res) => {\n        this.props.onKeyContentChange()\n        callback(err, res)\n      })\n    } else {\n      alert('Please wait for data been loaded before saving.')\n    }\n  }\n\n  load(index) {\n    if (!super.load(index)) {\n      return\n    }\n    const count = Number(this.cursor) ? 10000 : 500\n    this.props.redis.hscanBuffer(this.state.keyName, this.cursor, 'MATCH', '*', 'COUNT', count, (_, [cursor, result]) => {\n      for (let i = 0; i < result.length - 1; i += 2) {\n        this.state.members.push([result[i].toString(), result[i + 1]])\n      }\n      this.cursor = cursor\n      this.setState({members: this.state.members}, () => {\n        if (typeof this.state.selectedIndex !== 'number' && this.state.members.length) {\n          this.handleSelect(null, 0)\n        }\n        this.loading = false\n        if (this.state.members.length - 1 < this.maxRow && Number(cursor)) {\n          this.load()\n        }\n      })\n    })\n  }\n\n  handleSelect(evt, selectedIndex) {\n    const item = this.state.members[selectedIndex]\n    if (item) {\n      this.setState({selectedIndex, content: item[1]})\n    }\n  }\n\n  handleKeyDown(e) {\n    if (typeof this.state.selectedIndex === 'number' && typeof this.state.editableIndex !== 'number') {\n      if (e.keyCode === 8) {\n        this.deleteSelectedMember()\n        return false\n      }\n      if (e.keyCode === 38) {\n        if (this.state.selectedIndex > 0) {\n          this.handleSelect(null, this.state.selectedIndex - 1)\n        }\n        return false\n      }\n      if (e.keyCode === 40) {\n        if (this.state.selectedIndex < this.state.members.length - 1) {\n          this.handleSelect(null, this.state.selectedIndex + 1)\n        }\n        return false\n      }\n    }\n  }\n\n  deleteSelectedMember() {\n    if (typeof this.state.selectedIndex !== 'number') {\n      return\n    }\n    showModal({\n      title: 'Delete selected item?',\n      button: 'Delete',\n      content: 'Are you sure you want to delete the selected item? This action cannot be undone.'\n    }).then(() => {\n      const members = this.state.members\n      const deleted = members.splice(this.state.selectedIndex, 1)\n      if (deleted.length) {\n        this.props.redis.hdel(this.state.keyName, deleted[0])\n        if (this.state.selectedIndex >= members.length - 1) {\n          this.state.selectedIndex -= 1\n        }\n        this.setState({members, length: this.state.length - 1}, () => {\n          this.props.onKeyContentChange()\n          this.handleSelect(null, this.state.selectedIndex)\n        })\n      }\n    })\n  }\n\n  showContextMenu(e, row) {\n    this.handleSelect(null, row)\n\n    const menu = remote.Menu.buildFromTemplate([\n      {\n        label: 'Copy to Clipboard',\n        click: () => {\n          clipboard.writeText(this.state.members[row][0])\n        }\n      },\n      {\n        type: 'separator'\n      },\n      {\n        label: 'Rename Key',\n        click: () => {\n          this.setState({editableIndex: row})\n        }\n      },\n      {\n        label: 'Delete',\n        click: () => {\n          this.deleteSelectedMember()\n        }\n      }\n    ])\n    menu.popup(remote.getCurrentWindow())\n  }\n\n  render() {\n    return (<SplitPane\n        minSize={80}\n        split=\"vertical\"\n        ref=\"node\"\n        defaultSize={this.props.contentBarWidth}\n        onChange={this.props.setSize.bind(null, 'content')}\n      >\n      <div\n        style={{marginTop: -1}}\n        onKeyDown={this.handleKeyDown.bind(this)}\n        tabIndex=\"0\"\n        ref=\"table\"\n        className={'base-content ' + this.randomClass}\n        >\n        <Table\n          rowHeight={24}\n          rowsCount={this.state.length}\n          rowClassNameGetter={this.rowClassGetter.bind(this)}\n          onRowContextMenu={this.showContextMenu.bind(this)}\n          onRowClick={this.handleSelect.bind(this)}\n          onRowDoubleClick={(evt, index) => {\n            this.handleSelect(evt, index)\n            this.setState({editableIndex: index})\n          }}\n          width={this.props.contentBarWidth}\n          height={this.props.height}\n          headerHeight={24}\n          >\n          <Column\n            header={\n              <AddButton\n                title=\"key\" onClick={() => {\n                  showModal({\n                    button: 'Insert Member',\n                    form: {\n                      type: 'object',\n                      properties: {\n                        'Key:': {\n                          type: 'string'\n                        }\n                      }\n                    }\n                  }).then(res => {\n                    const data = res['Key:']\n                    const value = 'New Member'\n                    this.props.redis.hsetnx(this.state.keyName, data, value).then(inserted => {\n                      if (!inserted) {\n                        alert('The field already exists')\n                        return\n                      }\n                      this.state.members.push([data, Buffer.from(value)])\n                      this.setState({\n                        members: this.state.members,\n                        length: this.state.length + 1\n                      }, () => {\n                        this.props.onKeyContentChange()\n                        this.handleSelect(null, this.state.members.length - 1)\n                      })\n                    })\n                  })\n                }}\n                            />\n            }\n            width={this.props.contentBarWidth}\n            cell={({rowIndex}) => {\n              const member = this.state.members[rowIndex]\n              if (!member) {\n                this.load(rowIndex)\n                return 'Loading...'\n              }\n              return (<ContentEditable\n                className=\"ContentEditable overflow-wrapper\"\n                enabled={rowIndex === this.state.editableIndex}\n                onChange={target => {\n                  const members = this.state.members\n                  const member = members[rowIndex]\n                  const keyName = this.state.keyName\n                  const source = member[0]\n                  if (source !== target && target) {\n                    this.props.redis.hexists(keyName, target).then(exists => {\n                      if (exists) {\n                        return showModal({\n                          title: 'Overwrite the field?',\n                          button: 'Overwrite',\n                          content: `Field \"${target}\" already exists. Are you sure you want to overwrite this field?`\n                        }).then(() => {\n                          let found\n                          for (let i = 0; i < members.length; i++) {\n                            if (members[i][0] === target) {\n                              found = i\n                              break\n                            }\n                          }\n                          if (typeof found === 'number') {\n                            members.splice(found, 1)\n                            this.setState({length: this.state.length - 1})\n                          }\n                        })\n                      }\n                    }).then(() => {\n                      member[0] = target\n                      this.props.redis.multi()\n                      .hdel(keyName, source)\n                      .hset(keyName, target, member[1]).exec()\n                      this.setState({members})\n                    }).catch(() => {})\n                  }\n                  this.setState({editableIndex: null})\n                  ReactDOM.findDOMNode(this).focus()\n                }}\n                html={member[0]}\n                />)\n            }}\n            />\n        </Table>\n      </div>\n      <Editor\n        buffer={this.state.content}\n        onSave={this.save.bind(this)}\n        />\n    </SplitPane>)\n  }\n}\n\nexport default HashContent\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/KeyContent/BaseContent/ListContent.jsx",
    "content": "'use strict'\n\nimport React from 'react'\nimport BaseContent from '.'\nimport SplitPane from 'react-split-pane'\nimport {Table, Column} from 'fixed-data-table-contextmenu'\nimport Editor from './Editor'\nimport SortHeaderCell from './SortHeaderCell'\nimport AddButton from '../../../AddButton'\nimport {remote} from 'electron'\n\nclass ListContent extends BaseContent {\n  save(value, callback) {\n    const {selectedIndex, keyName, desc} = this.state\n    if (typeof selectedIndex === 'number') {\n      const members = this.state.members.slice()\n      members[selectedIndex] = value.toString()\n      this.setState({members})\n      this.props.redis.lset(keyName, desc ? -1 - selectedIndex : selectedIndex, value, (err, res) => {\n        this.props.onKeyContentChange()\n        callback(err, res)\n      })\n    } else {\n      alert('Please wait for data been loaded before saving.')\n    }\n  }\n\n  load(index) {\n    if (!super.load(index)) {\n      return\n    }\n\n    const {members, length, keyName, desc} = this.state\n    let from = members.length\n    let to = Math.min(from === 0 ? 200 : from + 1000, length - 1)\n    if (to < from) {\n      this.loading = false\n      return\n    }\n    if (desc) {\n      [from, to] = [-1 - to, -1 - from]\n    }\n\n    this.props.redis.lrange(keyName, from, to, (_, results) => {\n      if (this.state.desc !== desc) {\n        // TODO: use a counter instead to avoid\n        // cancel multiple loading attempts.\n        // LIST & ZSET\n        this.loading = false\n        return\n      }\n      if (desc) {\n        results.reverse()\n      }\n      const diff = to - from + 1 - results.length\n      this.setState({\n        members: members.concat(results),\n        length: length - diff\n      }, () => {\n        if (typeof this.state.selectedIndex !== 'number' && this.state.members.length) {\n          this.handleSelect(null, 0)\n        }\n        this.loading = false\n        if (this.state.members.length - 1 < this.maxRow && !diff) {\n          this.load()\n        }\n      })\n    })\n  }\n\n  handleSelect(_, selectedIndex) {\n    if (typeof this.state.members[selectedIndex] === 'undefined') {\n      this.setState({selectedIndex: null})\n    } else {\n      this.setState({selectedIndex})\n    }\n  }\n\n  async deleteSelectedMember() {\n    if (typeof this.state.selectedIndex !== 'number') {\n      return\n    }\n    await showModal({\n      title: 'Delete selected item?',\n      button: 'Delete',\n      content: 'Are you sure you want to delete the selected item? This action cannot be undone.'\n    })\n    const {selectedIndex, desc, length, keyName} = this.state\n    const members = this.state.members.slice()\n    const deleted = members.splice(selectedIndex, 1)\n    if (deleted.length) {\n      this.props.redis.lremindex(keyName, desc ? -1 - selectedIndex : selectedIndex)\n\n      const nextSelectedIndex = selectedIndex >= members.length - 1\n        ? selectedIndex - 1\n        : selectedIndex\n      this.setState({members, length: length - 1}, () => {\n        this.props.onKeyContentChange()\n        this.handleSelect(null, nextSelectedIndex)\n      })\n    }\n  }\n\n  handleKeyDown(e) {\n    if (typeof this.state.selectedIndex === 'number') {\n      switch (e.keyCode) {\n      case 8:\n        this.deleteSelectedMember()\n        return false\n      case 38:\n        if (this.state.selectedIndex > 0) {\n          this.handleSelect(null, this.state.selectedIndex - 1)\n        }\n        return false\n      case 40:\n        if (this.state.selectedIndex < this.state.members.length - 1) {\n          this.handleSelect(null, this.state.selectedIndex + 1)\n        }\n        return false\n      }\n    }\n  }\n\n  showContextMenu(e, row) {\n    this.handleSelect(null, row)\n\n    const menu = remote.Menu.buildFromTemplate([\n      {\n        label: 'Delete',\n        click: () => {\n          this.deleteSelectedMember()\n        }\n      }\n    ])\n    menu.popup(remote.getCurrentWindow())\n  }\n\n  renderEditor() {\n    const content = this.state.members[this.state.selectedIndex]\n    const buffer = typeof content === 'string'\n      ? Buffer.from(content)\n      : undefined\n    return <Editor\n      buffer={buffer}\n      onSave={this.save.bind(this)}\n      />\n  }\n\n  renderIndexColumn() {\n    return <Column\n      header={\n        <SortHeaderCell\n          title=\"index\"\n          onOrderChange={desc => this.setState({\n            desc,\n            members: [],\n            selectedIndex: null\n          })}\n          desc={this.state.desc}\n          />\n      }\n      width={this.props.indexBarWidth}\n      isResizable\n      cell={({rowIndex}) => {\n        return <div className=\"index-label\">{ this.state.desc ? this.state.length - 1 - rowIndex : rowIndex }</div>\n      }}\n      />\n  }\n\n  renderValueColumn() {\n    return <Column\n      header={\n        <AddButton\n          title=\"item\" onClick={async () => {\n            const res = await showModal({\n              button: 'Insert Item',\n              form: {\n                type: 'object',\n                properties: {\n                  'Insert To:': {\n                    type: 'string',\n                    enum: ['head', 'tail']\n                  }\n                }\n              }\n            })\n            const insertToHead = res['Insert To:'] === 'head'\n            const method = insertToHead ? 'lpush' : 'rpush'\n            const data = 'New Item'\n            await this.props.redis[method](this.state.keyName, data)\n\n            const members = this.state.members.slice()\n            members[insertToHead ? 'unshift' : 'push'](data)\n            this.setState({\n              members,\n              length: this.state.length + 1\n            }, () => {\n              this.props.onKeyContentChange()\n              if (insertToHead) {\n                this.handleSelect(null, 0)\n              }\n            })\n          }}\n        />\n      }\n      width={this.props.contentBarWidth - this.props.indexBarWidth}\n      cell={({rowIndex}) => {\n        const data = this.state.members[rowIndex]\n        if (typeof data === 'undefined') {\n          this.load(rowIndex)\n          return 'Loading...'\n        }\n        return <div className=\"overflow-wrapper\"><span>{data}</span></div>\n      }}\n      />\n  }\n\n  render() {\n    return (<SplitPane\n        minSize={80}\n        split=\"vertical\"\n        ref=\"node\"\n        defaultSize={this.props.contentBarWidth}\n        onChange={this.props.setSize.bind(null, 'content')}\n      >\n      <div\n        tabIndex=\"0\"\n        ref=\"table\"\n        onKeyDown={this.handleKeyDown.bind(this)}\n        className={'base-content ' + this.randomClass}\n        >\n        <Table\n          rowHeight={24}\n          rowsCount={this.state.length}\n          rowClassNameGetter={this.rowClassGetter.bind(this)}\n          onRowClick={this.handleSelect.bind(this)}\n          onRowContextMenu={this.showContextMenu.bind(this)}\n          isColumnResizing={false}\n          onColumnResizeEndCallback={this.props.setSize.bind(null, 'index')}\n          width={this.props.contentBarWidth}\n          height={this.props.height}\n          headerHeight={24}\n          >\n          {this.renderIndexColumn()}\n          {this.renderValueColumn()}\n        </Table>\n      </div>\n      {this.renderEditor()}\n    </SplitPane>)\n  }\n}\n\nexport default ListContent\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/KeyContent/BaseContent/SetContent.jsx",
    "content": "'use strict'\n\nimport React from 'react'\nimport BaseContent from '.'\nimport SplitPane from 'react-split-pane'\nimport {Table, Column} from 'fixed-data-table-contextmenu'\nimport Editor from './Editor'\nimport AddButton from '../../../AddButton'\nimport {remote} from 'electron'\n\nrequire('./index.scss')\n\nclass SetContent extends BaseContent {\n  save(value, callback) {\n    if (typeof this.state.selectedIndex === 'number') {\n      const oldValue = this.state.members[this.state.selectedIndex]\n\n      const key = this.state.keyName\n      this.props.redis.sismember(key, value).then(exists => {\n        if (exists) {\n          callback(new Error('The value already exists in the set'))\n          return\n        }\n        this.props.redis.multi().srem(key, oldValue).sadd(key, value).exec((err, res) => {\n          if (!err) {\n            this.state.members[this.state.selectedIndex] = value.toString()\n            this.setState({members: this.state.members})\n          }\n          this.props.onKeyContentChange()\n          callback(err, res)\n        })\n      })\n    } else {\n      alert('Please wait for data been loaded before saving.')\n    }\n  }\n\n  load(index) {\n    if (!super.load(index)) {\n      return\n    }\n    const count = Number(this.cursor) ? 10000 : 500\n    this.props.redis.sscan(this.state.keyName, this.cursor, 'COUNT', count, (_, [cursor, results]) => {\n      this.cursor = cursor\n      const length = Number(cursor) ? this.state.length : this.state.members.length + results.length\n\n      this.setState({\n        members: this.state.members.concat(results),\n        length\n      }, () => {\n        if (typeof this.state.selectedIndex !== 'number' && this.state.members.length) {\n          this.handleSelect(null, 0)\n        }\n        this.loading = false\n        if (this.state.members.length - 1 < this.maxRow && Number(cursor)) {\n          this.load()\n        }\n      })\n    })\n  }\n\n  handleSelect(evt, selectedIndex) {\n    const content = this.state.members[selectedIndex]\n    if (typeof content !== 'undefined') {\n      this.setState({selectedIndex, content})\n    }\n  }\n\n  handleKeyDown(e) {\n    if (typeof this.state.selectedIndex === 'number') {\n      if (e.keyCode === 8) {\n        this.deleteSelectedMember()\n        return false\n      }\n      if (e.keyCode === 38) {\n        if (this.state.selectedIndex > 0) {\n          this.handleSelect(null, this.state.selectedIndex - 1)\n        }\n        return false\n      }\n      if (e.keyCode === 40) {\n        if (this.state.selectedIndex < this.state.members.length - 1) {\n          this.handleSelect(null, this.state.selectedIndex + 1)\n        }\n        return false\n      }\n    }\n  }\n\n  deleteSelectedMember() {\n    if (typeof this.state.selectedIndex !== 'number') {\n      return\n    }\n    showModal({\n      title: 'Delete selected item?',\n      button: 'Delete',\n      content: 'Are you sure you want to delete the selected item? This action cannot be undone.'\n    }).then(() => {\n      const members = this.state.members\n      const deleted = members.splice(this.state.selectedIndex, 1)\n      if (deleted.length) {\n        this.props.redis.srem(this.state.keyName, deleted)\n        if (this.state.selectedIndex >= members.length - 1) {\n          this.state.selectedIndex -= 1\n        }\n        this.setState({members, length: this.state.length - 1}, () => {\n          this.props.onKeyContentChange()\n          this.handleSelect(null, this.state.selectedIndex)\n        })\n      }\n    })\n  }\n\n  showContextMenu(e, row) {\n    this.handleSelect(null, row)\n\n    const menu = remote.Menu.buildFromTemplate([\n      {\n        label: 'Delete',\n        click: () => {\n          this.deleteSelectedMember()\n        }\n      }\n    ])\n    menu.popup(remote.getCurrentWindow())\n  }\n\n  render() {\n    return (<SplitPane\n      className=\"pane-group\"\n      minSize={80}\n      split=\"vertical\"\n      ref=\"node\"\n      defaultSize={this.props.contentBarWidth}\n      onChange={this.props.setSize.bind(null, 'content')}\n      >\n      <div\n        onKeyDown={this.handleKeyDown.bind(this)}\n        tabIndex=\"0\"\n        ref=\"table\"\n        className={'base-content ' + this.randomClass}\n        >\n        <Table\n          rowHeight={24}\n          rowsCount={this.state.length}\n          rowClassNameGetter={this.rowClassGetter.bind(this)}\n          onRowContextMenu={this.showContextMenu.bind(this)}\n          onRowClick={this.handleSelect.bind(this)}\n          width={this.props.contentBarWidth}\n          height={this.props.height}\n          headerHeight={24}\n          >\n          <Column\n            width={this.props.contentBarWidth}\n            cell={({rowIndex}) => {\n              const member = this.state.members[rowIndex]\n              if (typeof member === 'undefined') {\n                this.load(rowIndex)\n                return 'Loading...'\n              }\n              return <div className=\"overflow-wrapper\"><span>{member}</span></div>\n            }}\n            header={\n              <AddButton\n                title=\"member\" onClick={() => {\n                  showModal({\n                    button: 'Insert Member',\n                    form: {\n                      type: 'object',\n                      properties: {\n                        'Value:': {\n                          type: 'string'\n                        }\n                      }\n                    }\n                  }).then(res => {\n                    const data = res['Value:']\n                    return this.props.redis.sismember(this.state.keyName, data).then(exists => {\n                      if (exists) {\n                        const error = 'Member already exists'\n                        alert(error)\n                        throw new Error(error)\n                      }\n                      return data\n                    })\n                  }).then(data => {\n                    this.props.redis.sadd(this.state.keyName, data).then(() => {\n                      this.state.members.push(data)\n                      this.setState({\n                        members: this.state.members,\n                        length: this.state.length + 1\n                      }, () => {\n                        this.props.onKeyContentChange()\n                        this.handleSelect(null, this.state.members.length - 1)\n                      })\n                    })\n                  })\n                }}\n                               />\n            }\n          />\n        </Table>\n      </div>\n      <Editor\n        buffer={typeof this.state.content === 'string' && Buffer.from(this.state.content)}\n        onSave={this.save.bind(this)}\n        />\n    </SplitPane>)\n  }\n}\n\nexport default SetContent\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/KeyContent/BaseContent/SortHeaderCell.jsx",
    "content": "'use strict'\n\nimport React, {memo} from 'react'\nimport {Cell} from 'fixed-data-table-contextmenu'\n\nfunction SortHeaderCell({onOrderChange, desc, title}) {\n  function handleOnClick(evt) {\n    onOrderChange(!desc)\n    evt.preventDefault()\n    evt.stopPropagation()\n  }\n\n  return (<Cell\n    onClick={handleOnClick}\n    >\n    <a className={'SortHeaderCell' + (desc ? '' : ' is-asc')}>\n      {title}\n      {\n        <img\n          width=\"7\"\n          height=\"4\"\n          src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAJBAMAAADwYwBaAAAAKlBMVEUAAACfn5/t7e2lpaXAwMD9/f3x8fHGxsapqan4+Pj19fWrq6uhoaG+vr4IBCNyAAAAAXRSTlMAQObYZgAAAERJREFUCNdjmMUAAisZDl4FUiEyDBtFAhhYHaUZMgQdGFgE2xjYGsUZCiUSGBiUBcsFjYBqmAwFhRVAepRBXJCAMZALALm5CbsZPOUxAAAAAElFTkSuQmCC\"\n        />\n      }\n    </a>\n  </Cell>)\n}\n\nexport default memo(SortHeaderCell)\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/KeyContent/BaseContent/StringContent.jsx",
    "content": "'use strict'\n\nimport React from 'react'\nimport BaseContent from '.'\nimport Editor from './Editor'\n\nclass StringContent extends BaseContent {\n  init(keyName, keyType) {\n    super.init(keyName, keyType)\n    this.props.redis.getBuffer(keyName, (_, buffer) => {\n      this.setState({buffer: buffer instanceof Buffer ? buffer : Buffer.alloc(0)})\n    })\n  }\n\n  save(value, callback) {\n    if (this.state.keyName) {\n      this.props.redis.setKeepTTL(this.state.keyName, value, (err, res) => {\n        this.props.onKeyContentChange()\n        callback(err, res)\n      })\n    } else {\n      alert('Please wait for data been loaded before saving.')\n    }\n  }\n\n  create() {\n    return this.props.redis.set(this.state.keyName, '')\n  }\n\n  render() {\n    return (<Editor\n      buffer={this.state.buffer}\n      onSave={this.save.bind(this)}\n      />)\n  }\n}\n\nexport default StringContent\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/KeyContent/BaseContent/ZSetContent.jsx",
    "content": "'use strict'\n\nimport React from 'react'\nimport BaseContent from '.'\nimport SplitPane from 'react-split-pane'\nimport {Table, Column} from 'fixed-data-table-contextmenu'\nimport Editor from './Editor'\nimport SortHeaderCell from './SortHeaderCell'\nimport AddButton from '../../../AddButton'\nimport ContentEditable from '../../../ContentEditable'\nimport ReactDOM from 'react-dom'\nimport {clipboard, remote} from 'electron'\nimport sortedIndexBy from 'lodash.sortedindexby'\n\nrequire('./index.scss')\n\nclass ZSetContent extends BaseContent {\n  save(value, callback) {\n    const {selectedIndex, members, keyName} = this.state\n    if (typeof selectedIndex === 'number') {\n      const item = members[selectedIndex]\n      const oldValue = item[0]\n      item[0] = value.toString()\n      this.setState({members})\n      this.props.redis.multi()\n        .zrem(keyName, oldValue)\n        .zadd(keyName, item[1], value)\n        .exec((err, res) => {\n          this.props.onKeyContentChange()\n          callback(err, res)\n        })\n    } else {\n      alert('Please wait for data been loaded before saving.')\n    }\n  }\n\n  load(index) {\n    if (!super.load(index)) {\n      return\n    }\n    const {members, desc, length, keyName} = this.state\n    const from = members.length\n    const to = Math.min(from === 0 ? 200 : from + 1000, length - 1)\n\n    const commandName = desc ? 'zrevrange' : 'zrange'\n    this.props.redis[commandName](keyName, from, to, 'WITHSCORES', (_, results) => {\n      if (this.state.desc !== desc || this.state.members.length !== from) {\n        this.loading = false\n        return\n      }\n      const items = []\n      for (let i = 0; i < results.length - 1; i += 2) {\n        items.push([results[i], results[i + 1]])\n      }\n      const diff = to - from + 1 - items.length\n      this.setState({\n        members: members.concat(items),\n        length: length - diff\n      }, () => {\n        const currentMembers = this.state.members\n        if (typeof this.state.selectedIndex !== 'number' && currentMembers.length) {\n          this.handleSelect(null, 0)\n        }\n        this.loading = false\n        if (currentMembers.length - 1 < this.maxRow && !diff) {\n          this.load()\n        }\n      })\n    })\n  }\n\n  handleSelect(_, selectedIndex) {\n    const item = this.state.members[selectedIndex]\n    if (item) {\n      this.setState({selectedIndex})\n    }\n  }\n\n  handleKeyDown(e) {\n    const {selectedIndex, editableIndex, members} = this.state\n    if (typeof selectedIndex === 'number' && typeof editableIndex !== 'number') {\n      switch (e.keyCode) {\n      case 8:\n        this.deleteSelectedMember()\n        return false\n      case 38:\n        if (selectedIndex > 0) {\n          this.handleSelect(null, selectedIndex - 1)\n        }\n        return false\n      case 40:\n        if (selectedIndex < members.length - 1) {\n          this.handleSelect(null, selectedIndex + 1)\n        }\n        return false\n      }\n    }\n  }\n\n  deleteSelectedMember() {\n    if (typeof this.state.selectedIndex !== 'number') {\n      return\n    }\n    showModal({\n      title: 'Delete selected item?',\n      button: 'Delete',\n      content: 'Are you sure you want to delete the selected item? This action cannot be undone.'\n    }).then(() => {\n      const members = this.state.members\n      const deleted = members.splice(this.state.selectedIndex, 1)\n      if (deleted.length) {\n        this.props.redis.zrem(this.state.keyName, deleted[0])\n        const nextSelectedIndex = this.state.selectedIndex >= members.length - 1\n          ? this.state.selectedIndex - 1\n          : this.state.selectedIndex\n        this.setState({members, length: this.state.length - 1}, () => {\n          this.props.onKeyContentChange()\n          this.handleSelect(null, nextSelectedIndex)\n        })\n      }\n    })\n  }\n\n  showContextMenu(_, row) {\n    this.handleSelect(null, row)\n\n    const menu = remote.Menu.buildFromTemplate([\n      {\n        label: 'Copy Score to Clipboard',\n        click: () => {\n          clipboard.writeText(this.state.members[row][1])\n        }\n      },\n      {\n        type: 'separator'\n      },\n      {\n        label: 'Edit Score',\n        click: () => {\n          this.setState({editableIndex: row})\n        }\n      },\n      {\n        label: 'Delete',\n        click: () => {\n          this.deleteSelectedMember()\n        }\n      }\n    ])\n    menu.popup(remote.getCurrentWindow())\n  }\n\n  renderTable() {\n    return <Table\n      rowHeight={24}\n      rowsCount={this.state.length}\n      rowClassNameGetter={this.rowClassGetter.bind(this)}\n      onRowClick={this.handleSelect.bind(this)}\n      onRowContextMenu={this.showContextMenu.bind(this)}\n      onRowDoubleClick={(evt, index) => {\n        this.handleSelect(evt, index)\n        this.setState({editableIndex: index})\n      }}\n      isColumnResizing={false}\n      onColumnResizeEndCallback={this.props.setSize.bind(null, 'score')}\n      width={this.props.contentBarWidth}\n      height={this.props.height}\n      headerHeight={24}\n      >\n      {this.renderScoreColumn()}\n      {this.renderMemberColumn()}\n    </Table>\n  }\n\n  renderScoreColumn() {\n    return <Column\n    header={\n      <SortHeaderCell\n        title=\"score\"\n        onOrderChange={desc => this.setState({\n          desc,\n          members: [],\n          selectedIndex: null\n        })}\n        desc={this.state.desc}\n        />\n    }\n    width={this.props.scoreBarWidth}\n    isResizable\n    cell={({rowIndex}) => {\n      const member = this.state.members[rowIndex]\n      if (!member) {\n        return ''\n      }\n      return (<ContentEditable\n        className=\"ContentEditable overflow-wrapper\"\n        enabled={rowIndex === this.state.editableIndex}\n        onChange={async (newScore) => {\n          const members = this.state.members.slice()\n\n          try {\n            await this.props.redis.zadd(this.state.keyName, newScore, members[rowIndex][0])\n            // Don't sort when changing scores\n            members[rowIndex][1] = newScore\n            this.setState({\n              members,\n              editableIndex: null\n            })\n          } catch (err) {\n            alert(err.message)\n            this.setState({\n              editableIndex: null\n            })\n          }\n          ReactDOM.findDOMNode(this.refs.table).focus()\n        }}\n        html={member[1]}\n        />)\n    }}\n    />\n  }\n\n  renderMemberColumn() {\n    return <Column\n    header={\n      <AddButton\n        title=\"member\" onClick={async () => {\n          const res = await showModal({\n            button: 'Insert Member',\n            form: {\n              type: 'object',\n              properties: {\n                'Value:': {\n                  type: 'string'\n                },\n                'Score:': {\n                  type: 'number'\n                }\n              }\n            }\n          })\n          const data = res['Value:']\n          const score = res['Score:']\n          const rank = await this.props.redis.zscore(this.state.keyName, data)\n          if (rank !== null) {\n            const error = 'Member already exists'\n            alert(error)\n            return\n          }\n          await this.props.redis.zadd(this.state.keyName, score, data)\n          const members = this.state.members.slice()\n          const newMember = [data, score]\n          const index = sortedIndexBy(\n            members,\n            newMember,\n            (member) => Number(member[1]) * (this.state.desc ? -1 : 1)\n          )\n          if (index < members.length - 1) {\n            members.splice(index, 0, newMember)\n            this.setState({\n              members,\n              length: this.state.length + 1\n            }, () => {\n              this.props.onKeyContentChange()\n              this.handleSelect(null, index)\n            })\n          }\n          alert('Added successfully')\n        }}/>\n    }\n    width={this.props.contentBarWidth - this.props.scoreBarWidth}\n    cell={({rowIndex}) => {\n      const member = this.state.members[rowIndex]\n      if (!member) {\n        this.load(rowIndex)\n        return 'Loading...'\n      }\n      return <div className=\"overflow-wrapper\"><span>{member[0]}</span></div>\n    }}\n    />\n  }\n\n  renderEditor() {\n    const item = this.state.members[this.state.selectedIndex]\n    const buffer = item\n      ? Buffer.from(item[0])\n      : undefined\n    return <Editor\n      buffer={buffer}\n      onSave={this.save.bind(this)}\n      />\n  }\n\n  render() {\n    return (<SplitPane\n        minSize={80}\n        split=\"vertical\"\n        ref=\"node\"\n        defaultSize={this.props.contentBarWidth}\n        onChange={this.props.setSize.bind(null, 'content')}\n      >\n      <div\n        onKeyDown={this.handleKeyDown.bind(this)}\n        tabIndex=\"0\"\n        ref=\"table\"\n        className={'base-content ' + this.randomClass}\n        >\n        {this.renderTable()}\n      </div>\n      {this.renderEditor()}\n    </SplitPane>)\n  }\n}\n\nexport default ZSetContent\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/KeyContent/BaseContent/index.jsx",
    "content": "'use strict'\n\nimport React from 'react'\n\nrequire('./index.scss')\n\nconst getDefaultState = function () {\n  return {\n    keyName: null,\n    content: null,\n    desc: false,\n    length: 0,\n    members: []\n  }\n}\n\nclass BaseContent extends React.Component {\n  constructor() {\n    super()\n    this.state = getDefaultState()\n    this.maxRow = 0\n    this.cursor = 0\n    this.randomClass = 'base-content-' + (Math.random() * 100000 | 0)\n  }\n\n  init(keyName, keyType) {\n    if (!keyName || !keyType) {\n      return\n    }\n    this.loading = false\n    this.setState(getDefaultState())\n\n    const {redis} = this.props\n\n    const method = {\n      string: 'strlen',\n      list: 'llen',\n      set: 'scard',\n      zset: 'zcard',\n      hash: 'hlen'\n    }[keyType]\n\n    redis[method](keyName).then(length => {\n      this.setState({keyName, length: length || 0})\n    })\n  }\n\n  load(index) {\n    if (index > this.maxRow) {\n      this.maxRow = index\n    }\n    if (this.loading) {\n      return\n    }\n    this.loading = true\n    return true\n  }\n\n  rowClassGetter(index) {\n    const item = this.state.members[index]\n    if (typeof item === 'undefined') {\n      return 'type-list is-loading'\n    }\n    if (index === this.state.selectedIndex) {\n      return 'type-list is-selected'\n    }\n    return 'type-list'\n  }\n\n  componentDidMount() {\n    this.init(this.props.keyName, this.props.keyType)\n  }\n\n  componentDidUpdate() {\n    if (typeof this.state.scrollToRow === 'number') {\n      this.setState({scrollToRow: null})\n    }\n  }\n\n  componentWillReceiveProps(nextProps) {\n    if (nextProps.keyName !== this.props.keyName ||\n        nextProps.keyType !== this.props.keyType) {\n      this.init(nextProps.keyName, nextProps.keyType)\n    }\n  }\n\n  componentWillUnmount() {\n    this.setState = function () {}\n  }\n}\n\nexport default BaseContent\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/KeyContent/BaseContent/index.scss",
    "content": ".BaseContent {\n  flex: 1;\n  position: relative;\n\n  .type-list {\n    .index-label {\n      background: #ccc;\n      margin: 4px 4px 0 0;\n      font-family: Consolas, monospace !important;\n      padding: 0 4px !important;\n      height: 16px;\n      font-size: 11px !important;\n      line-height: 16px !important;\n      display: block;\n      text-align: center;\n      color: #fff;\n      white-space: nowrap;\n      overflow: hidden;\n      text-overflow: ellipsis;\n    }\n  }\n\n  .SortHeaderCell {\n    position: relative;\n\n    a {\n      display: block;\n    }\n\n    img {\n      position: absolute;\n      right: -15px;\n      top: 5px;\n    }\n\n    &.is-asc img {\n      transform: rotate(180deg);\n    }\n  }\n  .base-content {\n    margin-top: -1px;\n    position: relative;\n    overflow: hidden;\n    &:focus {\n      outline: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/KeyContent/index.jsx",
    "content": "'use strict'\n\nimport React, {PureComponent} from 'react'\nimport {connect} from 'react-redux'\nimport {setSize} from 'Redux/actions'\nimport StringContent from './BaseContent/StringContent'\nimport ListContent from './BaseContent/ListContent'\nimport SetContent from './BaseContent/SetContent'\nimport HashContent from './BaseContent/HashContent'\nimport ZSetContent from './BaseContent/ZSetContent'\n\nrequire('./index.scss')\n\nclass KeyContent extends PureComponent {\n  constructor() {\n    super()\n    this.state = {}\n  }\n\n  render() {\n    const props = {key: this.props.keyName, ...this.props}\n    let view\n    switch (this.props.keyType) {\n    case 'string': view = <StringContent {...props}/>; break\n    case 'list': view = <ListContent {...props}/>; break\n    case 'set': view = <SetContent {...props}/>; break\n    case 'hash': view = <HashContent {...props}/>; break\n    case 'zset': view = <ZSetContent {...props}/>; break\n    case 'none':\n      view = (<div className=\"notfound\">\n        <span className=\"icon icon-trash\"/>\n        <p>The key has been deleted</p>\n      </div>)\n      break\n    }\n    return <div style={this.props.style} className=\"BaseContent\">{ view }</div>\n  }\n}\n\nfunction mapStateToProps(state) {\n  return {\n    contentBarWidth: state.sizes.get('contentBarWidth') || 200,\n    scoreBarWidth: state.sizes.get('scoreBarWidth') || 60,\n    indexBarWidth: state.sizes.get('indexBarWidth') || 60\n  }\n}\n\nconst mapDispatchToProps = {\n  setSize\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(KeyContent)\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/KeyContent/index.scss",
    "content": ".notfound {\n  position: absolute;\n  top: 50%;\n  font-size: 22px;\n  color: #ccc;\n  text-align: center;\n  transform: translateY(-50%);\n  width: 100%;\n\n  p {\n    margin: 0;\n  }\n\n  .icon {\n    font-size: 62px;\n  }\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/TabBar/index.jsx",
    "content": "'use strict'\n\nimport React, {memo} from 'react'\nrequire('./index.scss')\n\nconst TABS = ['Content', 'Terminal', 'Config']\n\nfunction renderTabIcon(tab) {\n  switch (tab) {\n    case 'Content':\n      return <span className=\"icon icon-book\" />\n    case 'Terminal':\n      return <span className=\"icon icon-window\" />\n    case 'Config':\n      return <span className=\"icon icon-cog\" />\n  }\n}\n\nfunction renderTab(tab, {activeTab, onSelectTab}) {\n  return <div\n    className={'item' + (tab === activeTab ? ' is-active' : '')}\n    key={tab}\n    onClick={() => onSelectTab(tab)}\n  >\n    {renderTabIcon(tab)}\n    {tab}\n  </div>\n}\n\nfunction Content(props) {\n  return <div className=\"TabBar\">{TABS.map(tab => renderTab(tab, props))}</div>\n}\n\nexport default memo(Content)\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/TabBar/index.scss",
    "content": ".TabBar {\n  text-align: right;\n  border-bottom: 1px solid #d3d3d3;\n  .item {\n    display: inline-block;\n    padding: 12px;\n\n    .icon {\n      margin-right: 5px;\n    }\n\n    &.is-active {\n      background: #dbdfe1;\n    }\n  }\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/Terminal/index.jsx",
    "content": "'use strict'\n\nimport React from 'react'\nimport commands from 'redis-commands'\nimport splitargs from 'redis-splitargs'\nimport 'jquery.terminal'\n\nrequire('../../../../../../../../node_modules/jquery.terminal/css/jquery.terminal.css')\nrequire('./index.scss')\n\nclass Terminal extends React.PureComponent {\n  constructor() {\n    super()\n    this.onSelectBinded = this.onSelect.bind(this)\n  }\n\n  componentDidMount() {\n    const {redis} = this.props\n    redis.on('select', this.onSelectBinded)\n    const terminal = this.terminal = $(this.refs.terminal).terminal((command, term) => {\n      if (!command) {\n        return\n      }\n      command = splitargs(command)\n      const commandName = command[0] && command[0].toUpperCase()\n      if (commandName === 'FLUSHALL' || commandName === 'FLUSHDB') {\n        term.push(input => {\n          if (input.match(/y|yes/i)) {\n            this.execute(term, command)\n            term.pop()\n          } else if (input.match(/n|no/i)) {\n            term.pop()\n          }\n        }, {\n          prompt: '[[;#aac6e3;]Are you sure (y/n)? ]'\n        })\n      } else {\n        this.execute(term, command)\n      }\n    }, {\n      greetings: '',\n      exit: false,\n      completion(command, callback) {\n        const commandName = command.split(' ')[0]\n        const lower = commandName.toLowerCase()\n        const isUppercase = commandName.toUpperCase() === commandName\n        callback(\n          commands.list\n          .filter(item => item.indexOf(lower) === 0)\n          .map(item => {\n            const last = item.slice(commandName.length)\n            return commandName + (isUppercase ? last.toUpperCase() : last)\n          })\n        )\n      },\n      name: this.props.connectionKey,\n      height: '100%',\n      width: '100%',\n      outputLimit: 200,\n      prompt: `[[;#fff;]redis> ]`,\n      keydown(e) {\n        if (!terminal.enabled()) {\n          return true\n        }\n        if (e.ctrlKey || e.metaKey) {\n          if (e.keyCode >= 48 && e.keyCode <= 57) {\n            return true\n          }\n          if ([84, 87, 78, 82, 81].indexOf(e.keyCode) !== -1) {\n            return true\n          }\n        }\n        if (e.ctrlKey) {\n          if (e.keyCode === 67) {\n            if (terminal.level() > 1) {\n              terminal.pop()\n              if (terminal.paused()) {\n                terminal.resume()\n              }\n            }\n            return false\n          }\n        }\n      }\n    })\n  }\n\n  onSelect(db) {\n    this.props.onDatabaseChange(db)\n  }\n\n  execute(term, args) {\n    term.pause()\n    const redis = this.props.redis\n    if (args.length === 1 && args[0].toUpperCase() === 'MONITOR') {\n      redis.monitor((_, monitor) => {\n        term.echo('[[;#aac6e3;]Enter monitor mode. Press Ctrl+C to exit. ]')\n        term.resume()\n        term.push(input => {\n        }, {\n          onExit() {\n            monitor.disconnect()\n          }\n        })\n        monitor.on('monitor', (time, args) => {\n          if (term.level() > 1) {\n            term.echo(formatMonitor(time, args), {raw: true})\n          }\n        })\n      })\n    } else if (args.length > 1 && ['SUBSCRIBE', 'PSUBSCRIBE'].indexOf(args[0].toUpperCase()) !== -1) {\n      const newRedis = redis.duplicate()\n      newRedis.call.apply(newRedis, args).then(res => {\n        term.echo('[[;#aac6e3;]Enter subscription mode. Press Ctrl+C to exit. ]')\n        term.resume()\n        term.push(input => {\n        }, {\n          prompt: '',\n          onExit() {\n            newRedis.disconnect()\n          }\n        })\n      })\n      newRedis.on('message', (channel, message) => {\n        term.echo(formatMessage(channel, message), {raw: true})\n      })\n      newRedis.on('pmessage', (pattern, channel, message) => {\n        term.echo(formatMessage(channel, message), {raw: true})\n      })\n    } else {\n      redis.call.apply(redis, args).then(res => {\n        term.echo(getHTML(res), {raw: true})\n        term.resume()\n      }).catch(err => {\n        term.echo(getHTML(err), {raw: true})\n        term.resume()\n      })\n    }\n  }\n\n  componentWillReceiveProps(nextProps) {\n    if (this.props.style.display === 'none' && nextProps.style.display === 'block') {\n      this.terminal.focus()\n    }\n  }\n\n  componentWillUnmount() {\n    this.props.redis.removeAllListeners('select', this.onSelectBinded)\n  }\n\n  render() {\n    return (<div ref=\"terminal\" style={this.props.style} className=\"Terminal\"/>)\n  }\n}\n\nexport default Terminal\n\nfunction getHTML(response) {\n  if (Array.isArray(response)) {\n    return `<ul class=\"array-resp\">\n    ${response.map((item, index) => '<li><span>' + index + '</span>' + getHTML(item) + '</li>').join('')}\n    </ul>`\n  }\n  const type = typeof response\n  if (type === 'number') {\n    return `<div class=\"number\">${response}</div>`\n  }\n  if (type === 'string') {\n    return `<div class=\"string\">${response.replace(/\\r?\\n/g, '<br>')}</div>`\n  }\n  if (response === null) {\n    return `<div class=\"null\">null</div>`\n  }\n  if (response instanceof Error) {\n    return `<div class=\"error\">${response.message}</div>`\n  }\n  if (type === 'object') {\n    return `<ul class=\"object-resp\">\n    ${Object.keys(response).map(item => '<li><span class=\"key\">' + item + '</span>' + getHTML(response[item]) + '</li>').join('')}\n    <ul>`\n  }\n\n  return `<div class=\"json\">${JSON.stringify(response)}</div>`\n}\n\nfunction formatMonitor(time, args) {\n  args = args || []\n  const command = args[0] ? args.shift().toUpperCase() : ''\n  if (command) {\n    commands.getKeyIndexes(command.toLowerCase(), args).forEach(index => {\n      args[index] = `<span class=\"command-key\">${args[index]}</span>`\n    })\n  }\n  return `<div class=\"monitor\">\n    <span class=\"time\">${time}</span>\n    <span class=\"command\">\n      <span class=\"command-name\">${command}</span>\n      <span class=\"command args\">${args.join(' ')}</span>\n    </span>\n  </div>`\n}\n\nfunction formatMessage(channel, message) {\n  return `<div class=\"monitor\">\n    <span class=\"time\">${channel}</span>\n    <span class=\"message\">${message}</span>\n  </div>`\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/Terminal/index.scss",
    "content": ".Terminal {\n  overflow: auto;\n  flex: 1;\n  * {\n    -webkit-user-select: text;\n  }\n\n  &.terminal, .cmd {\n    --background: #272b34;\n    --background: #000;\n    --size: 1.4;\n    font-family: Consolas, monospace;\n  }\n\n  .number {\n    color: #78CF8A;\n  }\n\n  .string {\n    color: #d6ec9c;\n  }\n\n  .array-resp, .object-resp {\n    margin: 0;\n    padding: 0;\n\n    li {\n      display: flex;\n      span {\n        color: #848080;\n        min-width: 28px;\n        text-align: right;\n        margin-right: 10px;\n      }\n      div {\n        flex: 1;\n      }\n    }\n  }\n\n  .object-resp li span {\n    font-weight: bold;\n    color: #cda869;\n  }\n\n  .null {\n    color: #cf7ea9;\n  }\n\n  .error {\n    color: #ee6868 !important;\n  }\n\n  .list {\n    color: #8f9d6a;\n  }\n\n  .monitor {\n    color: #8f9d6a;\n    .time {\n      color: #3d3d3d;\n      margin-right: 4px;\n    }\n\n    .command-name {\n      color: #cf7ea9;\n    }\n\n    .command-key {\n      color: #cda869;\n    }\n  }\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/Content/index.jsx",
    "content": "'use strict'\n\nimport React from 'react'\nimport TabBar from './TabBar'\nimport KeyContent from './KeyContent'\nimport Terminal from './Terminal'\nimport Config from './Config'\nimport Footer from './Footer'\n\nclass Content extends React.PureComponent {\n  constructor() {\n    super()\n    this.state = {\n      pattern: '',\n      db: 0,\n      version: 0,\n      tab: 'Content'\n    }\n  }\n\n  init(keyName) {\n    this.setState({keyType: null})\n    if (keyName !== null) {\n      this.setState({keyType: null})\n      this.props.redis.type(keyName).then(keyType => {\n        if (keyName === this.props.keyName) {\n          this.setState({keyType})\n        }\n      })\n    }\n  }\n\n  componentDidMount() {\n    this.init(this.props.keyName)\n  }\n\n  componentWillReceiveProps(nextProps) {\n    if (nextProps.keyName !== this.props.keyName || nextProps.version !== this.props.version) {\n      this.init(nextProps.keyName)\n    }\n    if (nextProps.metaVersion !== this.props.metaVersion) {\n      this.setState({version: this.state.version + 1})\n    }\n  }\n\n  handleTabChange(tab) {\n    this.setState({tab})\n  }\n\n  render() {\n    return (<div className=\"pane sidebar\" style={{height: '100%'}}>\n      <TabBar\n        activeTab={this.state.tab}\n        onSelectTab={this.handleTabChange.bind(this)}\n        />\n      <KeyContent\n        style={{display: this.state.tab === 'Content' ? 'flex' : 'none'}}\n        keyName={this.props.keyName}\n        keyType={this.state.keyType}\n        height={this.props.height - 66}\n        redis={this.props.redis}\n        onKeyContentChange={() => {\n          this.setState({version: this.state.version + 1})\n        }}\n        />\n      <Terminal\n        style={{display: this.state.tab === 'Terminal' ? 'block' : 'none'}}\n        height={this.props.height - 67}\n        redis={this.props.redis}\n        connectionKey={this.props.connectionKey}\n        onDatabaseChange={this.props.onDatabaseChange}\n        />\n      <Config\n        style={{display: this.state.tab === 'Config' ? 'block' : 'none'}}\n        height={this.props.height - 67}\n        redis={this.props.redis}\n        connectionKey={this.props.connectionKey}\n        />\n      <Footer\n        keyName={this.props.keyName}\n        keyType={this.state.keyType}\n        version={this.state.version}\n        redis={this.props.redis}\n        />\n    </div>)\n  }\n}\n\nexport default Content\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/ContentEditable/index.jsx",
    "content": "import React from 'react'\nimport ReactDOM from 'react-dom'\nimport escape from 'lodash.escape'\n\nrequire('./index.scss')\n\nexport default class ContentEditable extends React.Component {\n  constructor() {\n    super()\n  }\n\n  render() {\n    const {html, enabled, ...props} = this.props\n    return (<div\n      {...props}\n      >\n      <span\n        onInput={this.handleChange.bind(this)}\n        onKeyDown={this.handleKeyDown.bind(this)}\n        onBlur={this.handleSubmit.bind(this)}\n        contentEditable={enabled}\n        ref=\"text\"\n        dangerouslySetInnerHTML={{__html: escape(html)}}\n        />\n    </div>)\n  }\n\n  shouldComponentUpdate(nextProps) {\n    return nextProps.html !== this.props.html || // ReactDOM.findDOMNode(this.refs.text).innerHTML ||\n      nextProps.enabled !== this.props.enabled\n  }\n\n  componentDidMount() {\n    if (this.props.enabled) {\n      ReactDOM.findDOMNode(this.refs.text).focus()\n    }\n  }\n\n  componentDidUpdate() {\n    const node = ReactDOM.findDOMNode(this.refs.text)\n    if (escape(this.props.html) !== node.innerHTML) {\n      node.innerHTML = this.props.html\n    }\n    if (this.props.enabled) {\n      const range = document.createRange()\n      range.selectNodeContents(node)\n      const sel = window.getSelection()\n      sel.removeAllRanges()\n      sel.addRange(range)\n    }\n  }\n\n  handleKeyDown(evt) {\n    if (evt.keyCode === 13) {\n      ReactDOM.findDOMNode(this.refs.text).blur()\n      evt.preventDefault()\n      evt.stopPropagation()\n      return\n    }\n    if (evt.keyCode === 27) {\n      this.props.onChange(this.props.html)\n      evt.preventDefault()\n      evt.stopPropagation()\n    }\n  }\n\n  handleChange(evt) {\n    const html = ReactDOM.findDOMNode(this.refs.text).innerHTML\n    if (html !== this.lastHtml) {\n      evt.target = {value: html}\n    }\n    this.lastHtml = html\n  }\n\n  handleSubmit() {\n    this.props.onChange(ReactDOM.findDOMNode(this.refs.text).textContent)\n  }\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/ContentEditable/index.scss",
    "content": ".ContentEditable {\n  [contenteditable=\"true\"] {\n    background: #fff !important;\n    color: #333;\n  }\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/KeyBrowser/Footer.jsx",
    "content": "'use strict'\n\nimport React from 'react'\n\nclass Footer extends React.Component {\n  constructor() {\n    super()\n    this.state = {}\n  }\n\n  componentDidMount() {\n    this.updateInfo()\n    this.updateDBCount()\n    this.interval = setInterval(this.updateInfo.bind(this), 10000)\n  }\n\n  componentWillReceiveProps(nextProps) {\n    if (nextProps.db !== this.props.db) {\n      this.updateInfo()\n    }\n  }\n\n  updateDBCount() {\n    this.props.redis.config('get', 'databases', (err, res) => {\n      if (!err && res[1]) {\n        this.setState({databases: Number(res[1])})\n      } else {\n        const redis = this.props.redis.duplicate()\n        const select = redis.select.bind(redis)\n        this.guessDatabaseNumber(select, 15).then(count => {\n          return typeof count === 'number' ? count : this.guessDatabaseNumber(select, 1, 0)\n        }).then(count => {\n          this.setState({databases: count + 1})\n        })\n      }\n    })\n  }\n\n  updateInfo() {\n    this.props.redis.info((err, res) => {\n      if (err) {\n        return\n      }\n      const info = {}\n\n      const lines = res.split('\\r\\n')\n      for (let i = 0; i < lines.length; i++) {\n        const parts = lines[i].split(':')\n        if (parts[1]) {\n          info[parts[0]] = parts[1]\n        }\n      }\n\n      this.setState(info)\n    })\n  }\n\n  guessDatabaseNumber(select, startIndex, lastSuccessIndex) {\n    if (startIndex > 30) {\n      return Promise.resolve(30)\n    }\n    return select(startIndex)\n    .then(() => {\n      return this.guessDatabaseNumber(select, startIndex + 1, startIndex)\n    }).catch(err => {\n      if (typeof lastSuccessIndex === 'number') {\n        return lastSuccessIndex\n      }\n      return null\n    })\n  }\n\n  componentWillUnmount() {\n    clearInterval(this.interval)\n    this.interval = null\n  }\n\n  handleChange(evt) {\n    const db = Number(evt.target.value)\n    this.props.onDatabaseChange(db)\n  }\n\n  keyCountByDb(dbNumber){\n    const db = `db${dbNumber}`\n    let keys = 0\n    if (this.state[db]) {\n      const match = this.state[db].match(/keys=(\\d+)/)\n      if (match) {\n        keys = match[1]\n      }\n    }\n    return keys\n  }\n\n  render() {\n    const keys = this.keyCountByDb(this.props.db)\n    return (<footer className=\"toolbar toolbar-footer\">\n      <span style={{marginLeft: 6}}>Keys: {keys}</span>\n      <div style={{float: 'right'}}>\n        <span>DB:</span>\n        <select\n          onChange={this.handleChange.bind(this)}\n          value={this.props.db} className=\"form-control\" style={{\n            width: 50,\n            marginTop: 2,\n            marginRight: 2,\n            marginLeft: 3,\n            fontSize: 10,\n            float: 'right'\n          }}\n        >\n          {(max => {\n            return new Array(max).fill(0).map((value, db) => {\n              return (\n                <option key={db} value={db}>\n                  {db} {this.keyCountByDb(db) > 0 ? `(${this.keyCountByDb(db)})` : ''}\n                </option>\n              );\n            });\n          })(this.state.databases || 1)}\n        </select>\n      </div>\n    </footer>)\n  }\n}\n\nexport default Footer\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/KeyBrowser/KeyList/index.jsx",
    "content": "'use strict'\n\nimport React from 'react'\nimport ReactDOM from 'react-dom'\nimport {Table, Column} from 'fixed-data-table-contextmenu'\nimport ContentEditable from '../../ContentEditable'\nimport AddButton from '../../AddButton'\nimport zip from 'lodash.zip'\nimport {clipboard, remote} from 'electron'\nrequire('./index.scss')\n\nclass KeyList extends React.Component {\n  state = {\n    keys: [],\n    selectedKey: null,\n    sidebarWidth: 300,\n    cursor: '0'\n  }\n\n  randomClass = 'pattern-table-' + (Math.random() * 100000 | 0)\n\n  refresh(firstTime) {\n    this.setState({\n      cursor: '0',\n      keys: []\n    }, () => {\n      this.handleSelect()\n      this.scan(firstTime)\n    })\n  }\n\n  componentWillReceiveProps(nextProps) {\n    if (nextProps.db !== this.props.db) {\n      this.props.redis.select(nextProps.db)\n    }\n\n    const needRefresh = nextProps.db !== this.props.db ||\n      nextProps.pattern !== this.props.pattern ||\n      nextProps.redis !== this.props.redis\n\n    if (needRefresh) {\n      if (this.timer) {\n        clearTimeout(this.timer)\n        this.timer = null\n      }\n      this.timer = setTimeout(() => {\n        this.refresh(true)\n      }, 200)\n    }\n  }\n\n  scan(firstTime) {\n    const scanKey = this.scanKey = Math.random() * 10000 | 0\n    if (this.scanning) {\n      this.lastFirstTime = firstTime\n      return\n    }\n    this.scanning = true\n    this.setState({scanning: true})\n\n    const redis = this.props.redis\n\n    const targetPattern = this.props.pattern\n    let pattern = targetPattern\n    if (pattern.indexOf('*') === -1 && pattern.indexOf('?') === -1) {\n      pattern += '*'\n    }\n\n    let count = 0\n    let cursor = this.state.cursor\n\n    let filterKey\n    let filterKeyExists\n\n    // Plain key\n    if (targetPattern !== pattern) {\n      filterKey = targetPattern\n      if (this.state.keys.length) {\n        iter.call(this, 100, 1)\n      } else {\n        redis.type(targetPattern, (err, type) => {\n          if (type !== 'none') {\n            this.setState({\n              keys: this.state.keys.concat([[targetPattern, type]])\n            })\n            filterKeyExists = true\n            if (firstTime) {\n              iter.call(this, 1, 1)\n              return\n            }\n          }\n          iter.call(this, 100, 1)\n        })\n      }\n    } else {\n      iter.call(this, 100, 1)\n    }\n\n    function iter(fetchCount, times) {\n      redis.scan(cursor, 'MATCH', pattern, 'COUNT', fetchCount, (err, res) => {\n        if (this.scanKey !== scanKey) {\n          this.scanning = false\n          setTimeout(this.scan.bind(this, this.lastFirstTime), 0)\n          return\n        }\n        const newCursor = res[0]\n        let fetchedKeys = res[1]\n        let promise\n        if (fetchedKeys.length) {\n          if (filterKey) {\n            fetchedKeys = fetchedKeys.filter(key => key !== filterKey)\n          }\n          count += fetchedKeys.length\n          const pipeline = redis.pipeline()\n          fetchedKeys.forEach(key => pipeline.type(key))\n          promise = pipeline.exec()\n        } else {\n          promise = Promise.resolve([])\n        }\n        promise.then(types => {\n          if (this.props.pattern !== targetPattern) {\n            this.scanning = false\n            setTimeout(this.scan.bind(this), 0)\n            return\n          }\n          const keys = zip(fetchedKeys, types.map(res => res[1]))\n\n          let needContinue = true\n          if (filterKeyExists && firstTime) {\n            needContinue = false\n          } else if (Number(newCursor) === 0) {\n            needContinue = false\n          } else if (count >= 100) {\n            needContinue = false\n          } else if (count > 0 && times > 200) {\n            needContinue = false\n          }\n          cursor = newCursor\n\n          if (needContinue) {\n            this.setState({\n              cursor,\n              keys: this.state.keys.concat(keys)\n            }, () => {\n              iter.call(this, count < 10 ? 5000 : (count < 50 ? 2000 : 1000), times + 1)\n              if (typeof this.index !== 'number') {\n                this.handleSelect(0)\n              }\n            })\n          } else {\n            this.setState({\n              cursor,\n              scanning: false,\n              keys: this.state.keys.concat(keys)\n            }, () => {\n              this.scanning = false\n              if (typeof this.index !== 'number') {\n                this.handleSelect(0)\n              }\n            })\n          }\n        })\n      })\n    }\n  }\n\n  handleSelect(index, force) {\n    if (index === this.index && !force) {\n      return\n    }\n    const item = this.state.keys[index]\n    if (item && typeof item[0] !== 'undefined') {\n      const key = item[0]\n      this.index = index\n      const editableKey = this.state.editableKey === key ? this.state.editableKey : null\n      this.setState({selectedKey: item[0], editableKey})\n      this.props.onSelect(item[0])\n    } else {\n      this.index = null\n      this.setState({selectedKey: null, editableKey: null})\n      this.props.onSelect(null)\n    }\n  }\n\n  deleteSelectedKey() {\n    if (typeof this.index !== 'number') {\n      return\n    }\n    showModal({\n      title: 'Delete selected key?',\n      button: 'Delete',\n      content: 'Are you sure you want to delete the selected key? This action cannot be undone.'\n    }).then(() => {\n      const keys = this.state.keys\n      const deleted = keys.splice(this.index, 1)\n      if (deleted.length) {\n        this.props.redis.del(deleted[0][0])\n        if (this.index >= keys.length - 1) {\n          this.index -= 1\n        }\n        this.setState({keys}, () => {\n          this.handleSelect(this.index, true)\n        })\n      }\n    }).catch(() => {})\n  }\n\n  componentDidMount() {\n    $(ReactDOM.findDOMNode(this)).on('keydown', e => {\n      if (typeof this.index === 'number' && typeof this.state.editableKey !== 'string') {\n        if (e.keyCode === 8) {\n          this.deleteSelectedKey()\n          return false\n        }\n        if (e.keyCode === 38) {\n          this.handleSelect(this.index - 1)\n          return false\n        }\n        if (e.keyCode === 40) {\n          this.handleSelect(this.index + 1)\n          return false\n        }\n      }\n      if (!e.ctrlKey && e.metaKey) {\n        if (e.keyCode === 67) {\n          clipboard.writeText(this.state.keys[this.index][0])\n          return false\n        }\n        if (e.keyCode === 82) {\n          this.refresh()\n          return false\n        }\n      }\n      return true\n    })\n    this.scan()\n  }\n\n  setTTLforKey() {\n    const {redis, onKeyMetaChange} = this.props\n    redis.pttl(this.state.selectedKey).then(ttl => {\n      showModal({\n        button: 'Set Expiration',\n        form: {\n          type: 'object',\n          properties: {\n            'PTTL (ms):': {\n              type: 'number',\n              minLength: 1,\n              default: ttl\n            }\n          }\n        }\n      }).then(res => {\n        const ttl = Number(res['PTTL (ms):'])\n        if (ttl >= 0) {\n          redis.pexpire(this.state.selectedKey, ttl).then(res => {\n            if (res <= 0) {\n              alert('Update Failed')\n            }\n            onKeyMetaChange()\n          })\n        } else {\n          redis.persist(this.state.selectedKey, () => {\n            onKeyMetaChange()\n          })\n        }\n      })\n    })\n  }\n\n  duplicateKey() {\n    const sourceKey = this.state.keys[this.index][0]\n    let targetKey\n    showModal({\n      button: 'Duplicate Key',\n      form: {\n        type: 'object',\n        properties: {\n          'Target Key:': {\n            type: 'string',\n            minLength: 1\n          },\n          'Keep TTL:': {\n            type: 'boolean'\n          }\n        }\n      }\n    }).then(res => {\n      targetKey = res['Target Key:']\n      const duplicateTTL = res['Keep TTL:']\n      this.props.redis.duplicateKey(sourceKey, targetKey, duplicateTTL ? 'TTL' : 'NOTTL')\n    }).then(() => {\n      this.props.onCreateKey(targetKey)\n    }).catch(err => {\n      if (err && err.message) {\n        alert(err.message)\n      }\n    })\n  }\n\n  createKey(key, type) {\n    const redis = this.props.redis\n    switch (type) {\n    case 'string':\n      return redis.set(key, '')\n    case 'list':\n      return redis.lpush(key, 'New Item')\n    case 'hash':\n      return redis.hset(key, 'New Key', 'New Value')\n    case 'set':\n      return redis.sadd(key, 'New Member')\n    case 'zset':\n      return redis.zadd(key, 0, 'New Member')\n    }\n  }\n\n  showContextMenu(e, row) {\n    this.handleSelect(row)\n\n    const menu = remote.Menu.buildFromTemplate([\n      {\n        label: 'Copy to Clipboard',\n        click: () => {\n          clipboard.writeText(this.state.keys[row][0])\n        }\n      },\n      {\n        label: 'Reload',\n        click: () => {\n          this.handleSelect(row, true)\n        }\n      },\n      {\n        type: 'separator'\n      },\n      {\n        label: 'Set expiration',\n        click: () => {\n          this.setTTLforKey()\n        }\n      },\n      {\n        label: 'Rename Key...',\n        click: () => {\n          this.setState({editableKey: this.state.keys[row][0]})\n        }\n      },\n      {\n        label: 'Duplicate Key...',\n        click: () => {\n          this.duplicateKey()\n        }\n      },\n      {\n        label: 'Delete',\n        click: () => {\n          this.deleteSelectedKey()\n        }\n      }\n    ])\n    menu.popup(remote.getCurrentWindow())\n  }\n\n  render() {\n    return (<div\n      tabIndex=\"0\"\n      className={'pattern-table ' + this.randomClass}\n      >\n      <Table\n        rowHeight={24}\n        rowsCount={this.state.keys.length + (this.state.cursor === '0' ? 0 : 1)}\n        onScrollStart={() => {\n          if (this.state.editableKey) {\n            this.setState({editableKey: null})\n          }\n        }}\n        rowClassNameGetter={index => {\n          const item = this.state.keys[index]\n          if (!item) {\n            return 'is-loading'\n          }\n          if (item[0] === this.state.selectedKey) {\n            return 'is-selected'\n          }\n          return ''\n        }}\n        onRowContextMenu={this.showContextMenu.bind(this)}\n        onRowClick={(evt, index) => this.handleSelect(index)}\n        onRowDoubleClick={(evt, index) => {\n          this.handleSelect(index)\n          this.setState({editableKey: this.state.keys[index][0]})\n        }}\n        width={this.props.width}\n        height={this.props.height}\n        headerHeight={24}\n        >\n        <Column\n          header=\"type\"\n          width={40}\n          cell={({rowIndex}) => {\n            const item = this.state.keys[rowIndex]\n            if (!item) {\n              return ''\n            }\n            const cellData = item[1]\n            if (!cellData) {\n              return ''\n            }\n            const type = cellData === 'string' ? 'str' : cellData\n            return <span className={`key-type ${type}`}>{type}</span>\n          }}\n          />\n        <Column\n          header={\n            <AddButton\n              reload=\"true\" title=\"name\" onReload={() => {\n                this.refresh()\n              }} onClick={() => {\n                showModal({\n                  button: 'Create Key',\n                  form: {\n                    type: 'object',\n                    properties: {\n                      'Key Name:': {\n                        type: 'string',\n                        minLength: 1\n                      },\n                      'Type:': {\n                        type: 'string',\n                        enum: ['string', 'hash', 'list', 'set', 'zset']\n                      }\n                    }\n                  }\n                }).then(res => {\n                  const key = res['Key Name:']\n                  const type = res['Type:']\n                  return this.props.redis.exists(key).then(exists => {\n                    const error = 'The key already exists'\n                    if (exists) {\n                      alert(error)\n                      throw new Error(error)\n                    }\n                    return {key, type}\n                  })\n                }).then(({key, type}) => {\n                  this.createKey(key, type).then(() => {\n                    this.props.onCreateKey(key)\n                  })\n                })\n              }}\n                 />\n          }\n          width={this.props.width - 40}\n          cell={({rowIndex}) => {\n            const item = this.state.keys[rowIndex]\n            let cellData\n            if (item) {\n              cellData = item[0]\n            }\n            if (typeof cellData === 'undefined') {\n              if (this.state.scanning) {\n                return <span style={{color: '#ccc'}}>Scanning...(cursor {this.state.cursor})</span>\n              }\n              return (<a\n                href=\"#\" style={{color: '#666'}} onClick={evt => {\n                  evt.preventDefault()\n                  this.scan()\n                }}>Scan more</a>)\n            }\n            return (<ContentEditable\n              className=\"ContentEditable overflow-wrapper\"\n              enabled={cellData === this.state.editableKey}\n              onChange={newKeyName => {\n                const keys = this.state.keys\n                const oldKey = keys[rowIndex][0]\n                if (oldKey !== newKeyName && newKeyName) {\n                  this.props.redis.exists(newKeyName).then(exists => {\n                    if (exists) {\n                      return showModal({\n                        title: 'Overwrite the key?',\n                        button: 'Overwrite',\n                        content: `Key \"${newKeyName}\" already exists. Are you sure you want to overwrite this key?`\n                      })\n                    }\n                  }).then(() => {\n                    keys[rowIndex] = [newKeyName, keys[rowIndex][1]]\n                    this.props.redis.rename(oldKey, newKeyName)\n                    let found\n                    for (let i = 0; i < keys.length; i++) {\n                      if (i !== rowIndex && keys[i][0] === newKeyName) {\n                        keys.splice(i, 1)\n                        found = i\n                        break\n                      }\n                    }\n                    if (typeof found === 'number') {\n                      if (this.index >= found) {\n                        this.index -= 1\n                      }\n                      this.setState({keys}, () => {\n                        this.handleSelect(this.index, true)\n                      })\n                    } else {\n                      this.setState({keys})\n                    }\n                  }).catch(() => {})\n                }\n                this.setState({editableKey: null})\n                ReactDOM.findDOMNode(this).focus()\n              }}\n              html={cellData}\n              />)\n          }}\n          />\n      </Table>\n    </div>)\n  }\n}\n\nexport default KeyList\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/KeyBrowser/KeyList/index.scss",
    "content": ".pattern-table {\n  position: relative;\n  overflow: hidden;\n  &:focus {\n    outline: 0;\n  }\n\n  footer {\n    height: 24px;\n  }\n}\n\n.public_fixedDataTable_bottomShadow {\n  display: none;\n}\n\n.key-type {\n  margin: 4px 0 0;\n  padding: 0 !important;\n  text-transform: uppercase;\n  width: 32px;\n  height: 16px;\n  font-size: 11px !important;\n  line-height: 17px !important;\n  display: block;\n  text-align: center;\n  background: #60d4ca;\n  color: #fff;\n\n  &.str { background: #5dc936; }\n  &.list { background: #fca32a; }\n  &.hash { background: #b865d0; }\n  &.zset { background: #fa5049; }\n  &.set { background: #239ff2; }\n}\n\n.public_fixedDataTable_header, .public_fixedDataTableRow_main.is-loading {\n  .public_fixedDataTableCell_main {\n    font-family: system, -apple-system, \".SFNSDisplay-Regular\", \"Helvetica Neue\", Helvetica, \"Segoe UI\", sans-serif !important;\n  }\n}\n\n.public_fixedDataTableCell_cellContent {\n  padding: 0;\n}\n\n.public_fixedDataTableCell_main {\n  font-family: Consolas, monospace;\n  font-size: 12px;\n  line-height: 24px;\n  padding: 0 8px;\n}\n\n.public_fixedDataTableRow_main {\n  color: #606061;\n}\n\n:focus .public_fixedDataTableRow_main.is-selected {\n  background: #116cd6;\n  color: #fff;\n\n  .public_fixedDataTableCell_main {\n    background: transparent;\n  }\n}\n\n.public_fixedDataTableRow_main.is-selected {\n  background: #dcdcdc;\n\n  .public_fixedDataTableCell_main {\n    background: transparent;\n  }\n}\n\n.public_fixedDataTableCell_main {\n  border: none;\n}\n\n.public_fixedDataTable_main {\n  border-right: none;\n  border-left: none;\n  border-bottom: none;\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/KeyBrowser/PatternList/index.jsx",
    "content": "'use strict'\n\nimport React from 'react'\nimport {ipcRenderer} from 'electron'\n\nrequire('./index.scss')\n\nclass PatternList extends React.Component {\n  constructor(props) {\n    super()\n    this.state = {\n      patternDropdown: false,\n      pattern: props.pattern\n    }\n  }\n\n  componentWillReceiveProps(nextProps) {\n    if (nextProps.db !== this.props.db) {\n      this.updatePattern('')\n    }\n    if (nextProps.pattern !== this.props.pattern) {\n      this.setState({pattern: nextProps.pattern})\n    }\n  }\n\n  updatePattern(value) {\n    this.setState({pattern: value})\n    this.props.onChange(value)\n  }\n\n  render() {\n    return (<div className=\"pattern-input\">\n      <span className=\"icon icon-search\"/>\n      <input\n        type=\"search\"\n        className=\"form-control\"\n        placeholder=\"Key name or patterns (e.g. user:*)\"\n        value={this.state.pattern}\n        onChange={evt => {\n          this.updatePattern(evt.target.value)\n        }}\n        />\n      <span\n        className={'js-pattern-dropdown icon icon-down-open' + (this.state.patternDropdown ? ' is-active' : '')}\n        onClick={() => {\n          this.setState({patternDropdown: !this.state.patternDropdown})\n        }}\n        />\n      <div\n        className={'js-pattern-dropdown pattern-dropdown' + (this.state.patternDropdown ? ' is-active' : '')}\n        style={{maxHeight: this.props.height}}\n        >\n        <ul>\n          {\n            this.props.patterns.map(pattern => {\n              return (<li\n                key={pattern.get('key')} onClick={() => {\n                  const value = pattern.get('value')\n                  this.props.onChange(value)\n                  this.setState({patternDropdown: false, pattern: value})\n                }}\n                                         >{pattern.get('name')}</li>)\n            })\n          }\n          <li\n            className=\"manage-pattern-button\"\n            onClick={() => {\n              ipcRenderer.send('create patternManager', `${this.props.connectionKey}|${this.props.db}`)\n            }}\n            >\n            <span className=\"icon icon-cog\"/>\n            Manage Patterns...\n          </li>\n        </ul>\n      </div>\n    </div>)\n  }\n}\n\nexport default PatternList\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/KeyBrowser/PatternList/index.scss",
    "content": ".pattern-input {\n  position: relative;\n  padding: 6px;\n  .icon-search {\n    position: absolute;\n    left: 14px;\n    top: 12px;\n    opacity: 0.5;\n  }\n  .icon-down-open {\n    position: absolute;\n    right: 0;\n    top: 0;\n    opacity: 0.5;\n    transition: 0.1s;\n    display: inline-block;\n    width: 40px;\n    text-align: center;\n    height: 42px;\n    line-height: 42px;\n\n    &.is-active {\n      transform: rotate(180deg);\n    }\n  }\n\n  input {\n    padding-left: 22px;\n  }\n}\n\n.pattern-dropdown {\n  position: absolute;\n  z-index: 999;\n  background: #fff;\n  margin-top: 6px;\n  left: 0;\n  width: 100%;\n  box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.21);\n  transition: transform 125ms cubic-bezier(0.18, 0.89, 0.32, 1.12), opacity 100ms linear;\n  transform-origin: top;\n  transform: scale(1, 0.2);\n  pointer-events: none;\n  opacity: 0;\n\n  &.is-active {\n    pointer-events: initial;\n    opacity: 1;\n    transform: scale(1, 1);\n  }\n\n  ul {\n    height: 100%;\n    overflow: auto;\n  }\n\n  li {\n    display: block;\n    padding: 8px 12px;\n    font-family: Consolas, monospace;\n    border-top: 1px solid #f5f5f4;\n\n    &:hover {\n      background: #116cd6;\n      color: #fff;\n    }\n\n    &:last-child {\n      font-family: system, -apple-system, \".SFNSDisplay-Regular\", \"Helvetica Neue\", Helvetica, \"Segoe UI\", sans-serif;\n    }\n  }\n}\n.manage-pattern-button {\n  color: #116cd6;\n\n  span.icon {\n    margin-right: 5px;\n  }\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/KeyBrowser/index.jsx",
    "content": "'use strict'\n\nimport React, {memo} from 'react'\nimport {List} from 'immutable'\nimport PatternList from './PatternList'\nimport KeyList from './KeyList'\nimport Footer from './Footer'\n\nconst FOOTER_HEIGHT = 66\n\nfunction KeyBrowser({\n  pattern, patterns, connectionKey, db, height, width, redis,\n  onPatternChange, onCreateKey, onKeyMetaChange, onSelectKey, onDatabaseChange\n}) {\n  const clientHeight = height - FOOTER_HEIGHT\n  return (<div className=\"pane sidebar\">\n    <PatternList\n      patterns={patterns.get(`${connectionKey}|${db}`, List())}\n      height={clientHeight}\n      connectionKey={connectionKey}\n      db={db}\n      pattern={pattern}\n      onChange={onPatternChange}\n    />\n    <KeyList\n      height={clientHeight}\n      width={width}\n      db={db}\n      pattern={pattern || '*'}\n      redis={redis}\n      onCreateKey={onCreateKey}\n      onKeyMetaChange={onKeyMetaChange}\n      onSelect={onSelectKey}\n    />\n    <Footer onDatabaseChange={onDatabaseChange} db={db} redis={redis} />\n  </div>)\n}\n\nexport default memo(KeyBrowser)\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/index.jsx",
    "content": "'use strict'\n\nimport React from 'react'\nimport {connect} from 'react-redux'\nimport SplitPane from 'react-split-pane'\nimport KeyBrowser from './KeyBrowser'\nimport Content from './Content'\nrequire('./index.scss')\n\nclass Database extends React.PureComponent {\n  constructor() {\n    super()\n    this.$window = $(window)\n\n    this.state = {\n      sidebarWidth: 260,\n      key: null,\n      db: 0,\n      version: 0,\n      metaVersion: 0,\n      pattern: '',\n      clientHeight: this.$window.height() - $('#tabGroupWrapper').height()\n    }\n  }\n\n  componentDidMount() {\n    this.updateLayoutBinded = this.updateLayout.bind(this)\n    $(window).on('resize', this.updateLayoutBinded)\n    this.updateLayout()\n  }\n\n  componentWillUnmount() {\n    $(window).off('resize', this.updateLayoutBinded)\n  }\n\n  updateLayout() {\n    this.setState({\n      clientHeight: this.$window.height() - $('#tabGroupWrapper').height()\n    })\n  }\n\n  handleCreateKey(key) {\n    this.setState({key, pattern: key})\n  }\n\n  render() {\n    return (<SplitPane\n      className=\"pane-group\"\n      split=\"vertical\"\n      minSize={250}\n      defaultSize={260}\n      ref=\"node\"\n      onChange={size => {\n        this.setState({sidebarWidth: size})\n      }}\n      >\n      <KeyBrowser\n        patterns={this.props.patterns}\n        pattern={this.state.pattern}\n        onPatternChange={pattern => this.setState({pattern})}\n        height={this.state.clientHeight}\n        width={this.state.sidebarWidth}\n        redis={this.props.redis}\n        connectionKey={this.props.connectionKey}\n        onSelectKey={key => this.setState({key, version: this.state.version + 1})}\n        onCreateKey={this.handleCreateKey.bind(this)}\n        db={this.state.db}\n        onDatabaseChange={db => this.setState({db})}\n        onKeyMetaChange={() => this.setState({metaVersion: this.state.metaVersion + 1})}\n        />\n      <Content\n        height={this.state.clientHeight}\n        keyName={this.state.key}\n        version={this.state.version}\n        metaVersion={this.state.metaVersion}\n        connectionKey={this.props.connectionKey}\n        redis={this.props.redis}\n        db={this.state.db}\n        onDatabaseChange={db => this.setState({db})}\n        />\n    </SplitPane>)\n  }\n}\n\nfunction mapStateToProps(state, {instance}) {\n  return {\n    patterns: state.patterns,\n    redis: instance.get('redis'),\n    connectionKey: instance.get('connectionKey')\n  }\n}\n\nexport default connect(mapStateToProps)(Database)\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/DatabaseContainer/index.scss",
    "content": ".Resizer {\n  background: #000;\n  opacity: .2;\n  z-index: 1;\n  -moz-box-sizing: border-box;\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n  -moz-background-clip: padding;\n  -webkit-background-clip: padding;\n  background-clip: padding-box;\n}\n\n.Resizer.horizontal {\n  height: 11px;\n  margin: -5px 0;\n  border-top: 5px solid rgba(255, 255, 255, 0);\n  border-bottom: 5px solid rgba(255, 255, 255, 0);\n  cursor: row-resize;\n  width: 100%;\n}\n\n.Resizer.vertical {\n  width: 11px;\n  margin: 0 -5px;\n  border-left: 5px solid rgba(255, 255, 255, 0);\n  border-right: 5px solid rgba(255, 255, 255, 0);\n  cursor: col-resize;\n  height: 100%;\n}\n\n.overflow-wrapper {\n  display: flex;\n  width: calc(100% + 16px);\n  margin-left: -8px;\n\n  span {\n    padding: 0 8px;\n    display: block;\n    flex: 1;\n    width: 100%;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n\n  span[contenteditable=\"true\"] {\n    text-overflow: clip;\n  }\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/Modal/index.jsx",
    "content": "import React from 'react'\nimport ReactDOM from 'react-dom'\nrequire('json-editor')\n\nrequire('./index.scss')\n\nexport default class Modal extends React.Component {\n  handleSubmit() {\n    if (this.editor) {\n      const errors = this.editor.validate()\n      if (errors.length) {\n        $('.ui-state-error', ReactDOM.findDOMNode(this.refs.form)).css('opacity', 1)\n        return\n      }\n      this.props.onSubmit(this.editor.getValue())\n    } else {\n      this.props.onSubmit(1)\n    }\n  }\n\n  handleCancel() {\n    this.props.onCancel()\n  }\n\n  componentDidMount() {\n    if (this.props.form) {\n      this.editor = new JSONEditor(ReactDOM.findDOMNode(this.refs.form), {\n        disable_array_add: true,\n        disable_array_delete: true,\n        disable_array_reorder: true,\n        disable_collapse: true,\n        disable_edit_json: true,\n        disable_properties: true,\n        required_by_default: true,\n        schema: this.props.form,\n        show_errors: 'always',\n        theme: 'jqueryui'\n      })\n\n      $('.row input, .row select', ReactDOM.findDOMNode(this.refs.form)).first().focus()\n    } else {\n      $('.nt-button', ReactDOM.findDOMNode(this)).first().focus()\n    }\n  }\n\n  handleKeyDown(evt) {\n    if (evt.keyCode === 9) {\n      const $all = $('.row input, .row select, .nt-button', ReactDOM.findDOMNode(this))\n      const focused = $(':focus')[0]\n      let i\n      for (i = 0; i < $all.length - 1; ++i) {\n        if ($all[i] != focused) {\n          continue\n        }\n        $all[i + 1].focus()\n        $($all[i + 1]).select()\n        break\n      }\n      // Must have been focused on the last one or none of them.\n      if (i == $all.length - 1) {\n        $all[0].focus()\n        $($all[0]).select()\n      }\n      evt.stopPropagation()\n      evt.preventDefault()\n      return\n    }\n    if (evt.keyCode === 27) {\n      this.handleCancel()\n      evt.stopPropagation()\n      evt.preventDefault()\n      return\n    }\n    if (evt.keyCode === 13) {\n      const node = ReactDOM.findDOMNode(this.props.form ? this.refs.cancel : this.refs.submit)\n      node.focus()\n      setTimeout(() => {\n        node.click()\n      }, 10)\n      evt.stopPropagation()\n      evt.preventDefault()\n    }\n  }\n\n  render() {\n    return (<div\n      className=\"Modal\"\n      tabIndex=\"0\"\n      onKeyDown={this.handleKeyDown.bind(this)}\n      >\n      <div className=\"Modal__content\">\n        {\n          this.props.title && <div className=\"Modal__title\">\n            {this.props.title}\n          </div>\n        }\n        <div className=\"Modal__body\">\n          {!this.props.form && <div className=\"Modal__icon\"><span/></div>}\n          {this.props.content}\n          <div className=\"Modal__form\" ref=\"form\"/>\n        </div>\n        <div className=\"nt-button-group nt-button-group--pull-right\">\n          <button\n            ref=\"submit\"\n            className={'nt-button' + (this.props.form ? '' : ' nt-button--primary')}\n            onClick={this.handleCancel.bind(this)}\n            >Cancel</button>\n          <button\n            ref=\"cancel\"\n            className={'nt-button' + (!this.props.form ? '' : ' nt-button--primary')}\n            onClick={this.handleSubmit.bind(this)}\n            >{this.props.button || 'OK'}</button>\n        </div>\n      </div>\n    </div>)\n  }\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/Modal/index.scss",
    "content": ".Modal {\n  position: fixed;\n  left: 0;\n  width: 100%;\n  z-index: 999;\n  height: calc(100% + 100px);\n  margin-top: -100px;\n}\n\n.Modal__title {\n  font-size: 14px;\n  font-weight: bold;\n  margin-bottom: 10px;\n}\n\n.Modal__content {\n  position: relative;\n  width: 420px;\n  background: #efefef;\n  border: 1px solid #a3a3a3;\n  border-top: 0;\n\n  padding: 18px 20px 18px 100px;\n  box-shadow: inset 1px 4px 9px -6px, 0 5px 20px rgba(0, 0, 0, 0.3);\n\n  margin: 100px auto 0;\n\n  font-size: 12px;\n\n  .nt-button-group {\n    margin-top: 20px;\n  }\n\n  * {\n    -webkit-user-select: text;\n  }\n}\n\n.Modal__icon {\n  position: absolute;\n  left: 20px;\n  top: 22px;\n  width: 62px;\n  height: 57px;\n  background: transparent url(./warning.png) left top no-repeat;\n  background-size: 62px 57px;\n\n  span {\n    position: absolute;\n    bottom: -6px;\n    right: -6px;\n    width: 34px;\n    height: 34px;\n    background: transparent url(./icon.png) left top no-repeat;\n    background-size: 34px 34px;\n  }\n}\n\n.Modal__form {\n  h3 {\n    display: none;\n  }\n\n  .ui-corner-all {\n    padding: 0 !important;\n    margin: 0 !important;\n  }\n\n  .form-control {\n    position: relative;\n    background: none;\n    border: 0;\n    padding: 4px 0px 14px !important;\n\n    label {\n      position: absolute;\n      left: -90px;\n      text-align: right;\n      width: 80px;\n      font-weight: normal !important;\n    }\n\n    input, select {\n      width: 100% !important;\n      margin: 0 !important;\n    }\n\n    .ui-state-error {\n      position: absolute;\n      top: 25px;\n      opacity: 0;\n    }\n  }\n\n  .ui-state-error {\n    color: #ff2a1c;\n    font-size: 12px;\n  }\n\n  // input, select {\n  //   width: 100%;\n  //   min-height: 25px;\n  //   padding: 5px 10px;\n  //   line-height: 1.6;\n  //   background-color: #fff;\n  //   border: 1px solid #ddd;\n  //   outline: 0;\n\n  //   &:focus {\n  //     border-radius: 4px;\n  //     border-color: #6db3fd;\n  //     box-shadow: 3px 3px 0 #6db3fd, -3px -3px 0 #6db3fd, -3px 3px 0 #6db3fd, 3px -3px 0 #6db3fd;\n  //   }\n  // }\n}\n\n.modal-enter {\n  .Modal__content {\n    transform: translateY(-100%);\n  }\n}\n\n.modal-enter.modal-enter-active {\n  .Modal__content {\n    transform: translateY(0);\n    transition: transform 150ms linear;\n  }\n}\n\n.modal-leave {\n  .Modal__content {\n    transform: translateY(0);\n  }\n}\n\n.modal-leave.modal-leave-active {\n  .Modal__content {\n    transform: translateY(-100%);\n    transition: transform 150ms linear;\n  }\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceContent/index.jsx",
    "content": "'use strict'\n\nimport React, {PureComponent} from 'react'\nimport ConnectionSelectorContainer from './ConnectionSelectorContainer'\nimport DatabaseContainer from './DatabaseContainer'\nimport Modal from './Modal'\nimport ReactCSSTransitionGroup from 'react-addons-css-transition-group'\n\nclass InstanceContent extends PureComponent {\n  constructor() {\n    super()\n    this.state = {}\n  }\n\n  componentDidMount() {\n    window.showModal = modal => {\n      this.activeElement = document.activeElement\n      this.setState({modal})\n\n      return new Promise((resolve, reject) => {\n        this.promise = {resolve, reject}\n      })\n    }\n  }\n\n  modalSubmit(result) {\n    this.promise.resolve(result)\n    this.setState({modal: null})\n    if (this.activeElement) {\n      this.activeElement.focus()\n    }\n  }\n\n  modalCancel() {\n    this.promise.reject()\n    this.setState({modal: null})\n    if (this.activeElement) {\n      this.activeElement.focus()\n    }\n  }\n\n  componentWillUnmount() {\n    delete window.showModal\n  }\n\n  render() {\n    const {instances, activeInstanceKey} = this.props\n    const contents = instances.map(instance => (\n      <div\n        key={instance.get('key')}\n        style={{display: instance.get('key') === activeInstanceKey ? 'block' : 'none'}}\n        >\n        {\n        instance.get('redis')\n          ? <DatabaseContainer instance={instance}/>\n          : <ConnectionSelectorContainer instance={instance}/>\n      }\n      </div>\n    ))\n\n    return (\n      <div className=\"main\">\n        <ReactCSSTransitionGroup\n          transitionName=\"modal\"\n          transitionEnterTimeout={150}\n          transitionLeaveTimeout={150}\n          >\n          {\n          this.state.modal &&\n          <Modal\n            key=\"modal\"\n            {...this.state.modal}\n            onSubmit={this.modalSubmit.bind(this)}\n            onCancel={this.modalCancel.bind(this)}\n            />\n        }\n        </ReactCSSTransitionGroup>\n        {contents}\n      </div>\n    )\n  }\n}\n\nexport default InstanceContent\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceTabs/Tab.tsx",
    "content": "import React, {memo} from 'react'\nimport {SortableElement} from 'react-sortable-hoc'\n\ninterface ITabProps {\n  instanceKey: string,\n  title?: string,\n  active: boolean,\n  onTabClick: (key: string) => void,\n  onTabCloseButtonClick: (key: string) => void\n}\n\nfunction Tab({instanceKey, onTabClick, onTabCloseButtonClick, active, title = 'Quick Connect'}: ITabProps) {\n  return <div\n    onMouseDown={() => {\n      onTabClick(instanceKey)\n    }}\n    className={active ? 'tab-item active' : 'tab-item'}\n  >\n    {title}\n    <span\n      className=\"icon icon-cancel icon-close-tab\"\n      onClick={() => onTabCloseButtonClick(instanceKey)}></span>\n  </div>\n}\n\nexport default memo(SortableElement(Tab))\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceTabs/Tabs.tsx",
    "content": "import React, {memo} from 'react'\nimport {SortableContainer} from 'react-sortable-hoc'\nimport Tab from './Tab'\n\ninterface ITabsProps {\n  instances: any\n  activeInstanceKey: string\n  onTabSelect: (key: string) => void\n  onTabClose: (key: string) => void\n\n}\nfunction Tabs({instances, activeInstanceKey, onTabSelect, onTabClose}: ITabsProps) {\n  return (\n    <div style={{display: 'flex', flex: 1}}>\n      {instances.map((instance, index) => {\n        const key = instance.get('key')\n        return <Tab\n          key={key}\n          instanceKey={key}\n          index={index}\n          title={instance.get('title')}\n          active={activeInstanceKey === key}\n          onTabClick={onTabSelect}\n          onTabCloseButtonClick={onTabClose}\n        />\n      })}\n    </div>\n  )\n}\n\nexport default memo(SortableContainer(Tabs))\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceTabs/index.tsx",
    "content": "import React, {memo} from 'react'\nimport Tabs from './Tabs'\n\nrequire('./main.scss')\n\nfunction isModalShown() {\n  return $('.Modal').length > 0\n}\n\nlet display = 'flex'\n\ninterface IInstanceTabsProps {\n  instances: any\n  activeInstanceKey: string\n  onCreateInstance: any\n  onSelectInstance: any\n  onDelInstance: any\n  onMoveInstance: any\n}\n\nfunction InstanceTabs({\n  onCreateInstance, onSelectInstance, onDelInstance, instances, activeInstanceKey, onMoveInstance\n}: IInstanceTabsProps) {\n  const handleAddButtonClick = () => {\n    if (!isModalShown()) {\n      onCreateInstance()\n    }\n  }\n\n  const handleTabSelect = (key: string) => {\n    if (!isModalShown()) {\n      onSelectInstance(key)\n    }\n  }\n\n  const handleTabClose = (key: string) => {\n    if (!isModalShown()) {\n      onDelInstance(key)\n    }\n  }\n\n  const currentDisplay = instances.count() === 1 ? 'none' : 'flex'\n  if (display !== currentDisplay) {\n    display = currentDisplay\n    setTimeout(() => $(window).trigger('resize'), 0)\n  }\n\n  return <div id=\"tabGroupWrapper\">\n    <div className=\"tab-group\" style={{display: display, flex: 1}}>\n      <Tabs\n        instances={instances}\n        activeInstanceKey={activeInstanceKey}\n        axis={'x'}\n        lockAxis={'x'}\n        helperClass={\"active\"}\n        lockToContainerEdges={true}\n        lockOffset={[0, 0]}\n        onTabSelect={handleTabSelect}\n        onTabClose={handleTabClose}\n        onSortEnd={({oldIndex, newIndex}) => {\n          if (oldIndex !== newIndex) {\n            onMoveInstance(instances.getIn([oldIndex, 'key']), instances.getIn([newIndex, 'key']))\n          }\n        }}\n        shouldCancelStart={(e) => (e.target as any).nodeName.toUpperCase() === 'SPAN'}\n      />\n      <div className='tab-item tab-item-btn' onClick={handleAddButtonClick}>\n        <span>{'+'}</span>\n      </div>\n    </div>\n  </div>\n}\n\nexport default memo(InstanceTabs)\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/InstanceTabs/main.scss",
    "content": ".instance-tabs {\n  * {\n    -webkit-user-select: none;\n  }\n  display: flex;\n\n  li {\n    position: relative;\n    flex-grow: 1;\n    background: #bebebe;\n    color: #424242;\n    border: 1px solid #a0a0a0;\n    border-right: none;\n    text-align: center;\n    line-height: 22px;\n    cursor: default;\n\n    &:first-child {\n      border-left: none;\n    }\n\n    &.is-active,\n    &.is-active:hover {\n      background: #d3d3d3;\n      color: #000000;\n      border-top-color: #d3d3d3;\n\n      .rdTabCloseIcon:hover {\n        background: #c0c0c0;\n        color: #6c6c6c;\n      }\n    }\n\n    &:hover {\n      background: #b2b2b2;\n      color: #3e3e3e;\n      .rdTabCloseIcon {\n        display: block;\n      }\n    }\n  }\n}\n\n.instance-tabs__add {\n  flex-grow: 0 !important;\n  flex-basis: 24px;\n  &:hover {\n    background: #bebebe !important;\n    color: #424242 !important;\n  }\n}\n\n.rdTabCloseIcon {\n  position: absolute;\n  left: 4px;\n  top: 4px;\n  display: none;\n  border-radius: 2px;\n  line-height: 1em;\n  width: 14px;\n  height: 14px;\n\n  &:hover {\n    color: #5b5b5b;\n    background: #a2a2a2;\n  }\n}\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/entry.jsx",
    "content": "'use strict'\n\nrequire('../../photon/css/photon.min.css')\nrequire('../../../../node_modules/fixed-data-table-contextmenu/dist/fixed-data-table.css')\n\nimport ReactDOM from 'react-dom'\nimport MainWindow from './'\nimport {ipcRenderer} from 'electron'\nimport store from 'Redux/store'\nimport * as actions from 'Redux/actions'\n\nrequire('../../styles/global.scss')\n\nwindow.$ = window.jQuery = require('jquery');\nwindow.Buffer = global.Buffer;\n\nipcRenderer.on('action', (evt, action) => {\n  if ($('.Modal').length && action.indexOf('Instance') !== -1) {\n    return\n  }\n\n  store.skipPersist = true\n  store.dispatch(actions[action]())\n  store.skipPersist = false\n})\n\nReactDOM.render(MainWindow, document.body.appendChild(document.createElement('div')))\n"
  },
  {
    "path": "src/renderer/windows/MainWindow/index.jsx",
    "content": "'use strict'\n\nimport React, {PureComponent} from 'react'\nimport {createSelector} from 'reselect'\nimport {Provider, connect} from 'react-redux'\nimport InstanceTabs from './InstanceTabs'\nimport InstanceContent from './InstanceContent'\nimport DocumentTitle from 'react-document-title'\nimport {createInstance, selectInstance, delInstance, moveInstance} from 'Redux/actions'\nimport store from 'Redux/store'\n\nclass MainWindow extends PureComponent {\n  componentDidMount() {\n    $(window).on('keydown.redis', this.onHotKey.bind(this))\n  }\n\n  componentWillUnmount() {\n    $(window).off('keydown.redis')\n  }\n\n  onHotKey(e) {\n    const {instances, selectInstance} = this.props\n    if (!e.ctrlKey && e.metaKey) {\n      const code = e.keyCode\n      if (code >= 49 && code <= 57) {\n        const number = code - 49\n        if (number === 8) {\n          const instance = instances.get(instances.count() - 1)\n          if (instance) {\n            selectInstance(instance.get('key'))\n            return false\n          }\n        } else {\n          const instance = instances.get(number)\n          if (instance) {\n            selectInstance(instance.get('key'))\n            return false\n          }\n        }\n      }\n    }\n    return true\n  }\n\n  getTitle() {\n    const {activeInstance} = this.props\n    if (!activeInstance) {\n      return ''\n    }\n    const version = activeInstance.get('version')\n      ? `(Redis ${activeInstance.get('version')}) `\n      : ''\n\n    return version + activeInstance.get('title')\n  }\n\n  render() {\n    const {instances, activeInstance, createInstance,\n      selectInstance, delInstance, moveInstance} = this.props\n\n    return (<DocumentTitle title={this.getTitle()}>\n      <div className=\"window\">\n        <InstanceTabs\n          instances={instances}\n          onCreateInstance={createInstance}\n          onSelectInstance={selectInstance}\n          onDelInstance={delInstance}\n          onMoveInstance={moveInstance}\n          activeInstanceKey={activeInstance.get('key')}\n          />\n        <InstanceContent\n          instances={instances}\n          activeInstanceKey={activeInstance.get('key')}\n          />\n      </div>\n    </DocumentTitle>)\n  }\n}\n\nconst selector = createSelector(\n  state => state.instances,\n  state => state.activeInstanceKey,\n  (instances, activeInstanceKey) => {\n    return {\n      instances,\n      activeInstance: instances.find(instance => instance.get('key') === activeInstanceKey)\n    }\n  }\n)\n\nconst mapDispatchToProps = {\n  createInstance,\n  selectInstance,\n  delInstance,\n  moveInstance\n}\n\nconst MainWindowContainer = connect(selector, mapDispatchToProps)(MainWindow)\n\nexport default <Provider store={store}>\n  <MainWindowContainer/>\n</Provider>\n"
  },
  {
    "path": "src/renderer/windows/PatternManagerWindow/app.scss",
    "content": ".patternList {\n  background: #fff;\n  border: 1px solid #c5c5c5;\n  width: 210px;\n  position: absolute;\n  top: 20px;\n  left: 20px;\n  height: 236px;\n\n  footer {\n    position: absolute;\n    bottom: 0;\n    left: 0;\n    width: 208px;\n    height: 19px;\n    background: #fafafa;\n    border-top: 1px solid #b4b4b4;\n    button {\n      width: 23px;\n      height: 18px;\n      border-radius: 0;\n      padding: 0;\n      border-left: 1px solid #b4b4b4;\n      border-right: 1px solid #b4b4b4;\n      margin-left: -1px;\n      border-top: 0;\n      border-bottom: 0;\n      background: #fafafa;\n\n      &.is-disabled {\n        color: #bfbfbf;\n      }\n    }\n  }\n\n  .nav-group-item:active {\n    background-color: #116cd6 !important;\n    color: #fff;\n  }\n}\n\n.nav-group-item {\n  padding: 0 5px;\n\n  &.sortable-chosen {\n    color: #000;\n\n    &.is-active {\n      background: #116cd6 !important;\n      color: #fff;\n    }\n  }\n\n  &.is-active {\n    background: #116cd6;\n    color: #fff;\n  }\n}\n\n.form {\n  position: absolute !important;\n  right: 20px;\n  top: 20px;\n  width: 328px;\n  height: 236px;\n}\n"
  },
  {
    "path": "src/renderer/windows/PatternManagerWindow/entry.jsx",
    "content": "'use strict'\n\nrequire('../../photon/css/photon.min.css')\n\nimport React from 'react'\nimport ReactDOM from 'react-dom'\nimport {Provider} from 'react-redux'\nimport PatternManagerWindow from './'\nimport store from 'Redux/store'\nimport * as actions from 'Redux/actions'\nimport {remote, ipcRenderer} from 'electron'\n\nrequire('../../styles/global.scss')\n\nwindow.$ = window.jQuery = require('jquery');\n\nipcRenderer.on('action', (evt, action) => {\n  if (type === 'delInstance') {\n    remote.getCurrentWindow().close()\n    return\n  }\n\n  store.skipPersist = true\n  store.dispatch(actions[action]())\n  store.skipPersist = false\n})\n\nReactDOM.render(\n  <Provider store={store}>\n    <PatternManagerWindow/>\n  </Provider>,\n  document.body.appendChild(document.createElement('div'))\n)\n"
  },
  {
    "path": "src/renderer/windows/PatternManagerWindow/index.jsx",
    "content": "import React from 'react'\nimport {connect} from 'react-redux'\nimport {createPattern, updatePattern, removePattern} from 'Redux/actions'\nimport {List} from 'immutable'\n\nrequire('./app.scss')\n\nconst connectionKey = getParameterByName('arg')\n\nclass App extends React.Component {\n  constructor(props, context) {\n    super(props, context)\n    this.state = {index: 0}\n  }\n\n  handleChange(property, e) {\n    this.setState({[property]: e.target.value})\n  }\n\n  select(index) {\n    this.setState({\n      index,\n      name: null,\n      value: null\n    })\n  }\n\n  renderPatternForm(activePattern) {\n    if (!activePattern) {\n      return null\n    }\n    return <div\n    key={this.state.indexKey}\n    className=\"form nt-box\"\n    style={{display: activePattern ? 'block' : 'none'}}\n    >\n      <div className=\"nt-form-row nt-form-row--vertical\">\n        <label htmlFor=\"name\">Name:</label>\n        <input\n          type=\"text\" id=\"name\"\n          readOnly={!activePattern}\n          value={typeof this.state.name === 'string' ? this.state.name : activePattern.get('name')}\n          onChange={this.handleChange.bind(this, 'name')}\n          />\n      </div>\n      <div className=\"nt-form-row nt-form-row--vertical\">\n        <label htmlFor=\"value\">Pattern:</label>\n        <input\n          type=\"text\" id=\"value\"\n          readOnly={!activePattern}\n          value={typeof this.state.value === 'string' ? this.state.value : activePattern.get('value')}\n          onChange={this.handleChange.bind(this, 'value')}\n          />\n      </div>\n      <div className=\"nt-button-group nt-button-group--pull-right\" style={{margin: '10px auto 0'}}>\n        <button\n          disabled={this.state.name === '' || this.state.value === ''}\n          className=\"nt-button nt-button--primary\"\n          onClick={() => {\n            this.props.updatePattern(connectionKey, this.state.index, {\n              name: this.state.name || activePattern.get('name'),\n              value: this.state.value || activePattern.get('value')\n            })\n            alert('Save Successfully')\n          }}\n          >Save</button>\n      </div>\n    </div>\n  }\n\n  render() {\n    const {patterns, createPattern, removePattern} = this.props\n    const activePattern = patterns.get(this.state.index)\n    return (<div className=\"window\">\n      <div className=\"patternList\">\n        <div>{\n          patterns.map((pattern, index) => {\n            return (<a\n              key={pattern.get('key')}\n              className={'nav-group-item' + (index === this.state.index ? ' is-active' : '')}\n              onClick={() => this.select(index)}\n              >\n              <span>{pattern.get('name')}</span>\n            </a>)\n          })\n        }</div>\n        <footer>\n          <button\n            onClick={() => {\n              const index = patterns.size\n              createPattern(connectionKey)\n              this.select(index)\n            }}\n            >+</button>\n          <button\n            className={activePattern ? '' : 'is-disabled'}\n            onClick={() => {\n              if (activePattern) {\n                removePattern(connectionKey, this.state.index)\n                this.select(this.state.index > 0 ? this.state.index - 1 : 0)\n              }\n            }}\n            >-</button>\n        </footer>\n      </div>\n      {this.renderPatternForm(activePattern)}\n    </div>)\n  }\n}\n\nfunction mapStateToProps(state) {\n  return {\n    patterns: state.patterns.get(connectionKey, List())\n  }\n}\n\nconst mapDispatchToProps = {\n  updatePattern,\n  createPattern,\n  removePattern\n}\n\nexport default connect(mapStateToProps, mapDispatchToProps)(App)\n\nfunction getParameterByName(name) {\n  name = name.replace(/[\\[]/, '\\\\[').replace(/[\\]]/, '\\\\]')\n  const regex = new RegExp('[\\\\?&]' + name + '=([^&#]*)')\n  const results = regex.exec(location.search)\n  return results === null ? '' : decodeURIComponent(results[1].replace(/\\+/g, ' '))\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"allowJs\": true,\n    \"target\": \"es6\",\n    \"lib\": [\n      \"es6\"\n    ],\n    \"moduleResolution\": \"node\",\n    \"types\": [\n      \"node\"\n    ],\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"module\": \"commonjs\",\n    \"jsx\": \"preserve\",\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true\n  },\n  \"include\": [\n    \"./src/**/*\"\n  ]\n}\n"
  },
  {
    "path": "webpack.config.js",
    "content": "'use strict';\n\nconst {resolve} = require('path')\nconst {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst MiniCssExtractPlugin = require('mini-css-extract-plugin')\nconst {CheckerPlugin} = require('awesome-typescript-loader')\nconst webpack = require('webpack')\n\nconst mode = process.env.NODE_ENV === 'production' ? 'production' : 'development'\nconst watch = process.env.WEBPACK_WATCH === 'true'\n\nconst distPath = resolve(__dirname, 'dist')\n\nconst base = {\n  mode, watch,\n  output: {\n    path: distPath,\n    chunkFilename: '[name].chunk.js',\n    filename: '[name].js'\n  },\n  node: {\n    Buffer: false,\n    buffer: false,\n    __dirname: false,\n    __filename: false,\n  },\n  module: {\n    rules: [{\n      test: /\\.(ts|tsx)$/,\n      use: [{\n        loader: 'awesome-typescript-loader',\n        options: {\n          reportFiles: ['src/**/*.{ts,tsx}'],\n          useCache: true,\n          useBabel: true,\n          babelCore: '@babel/core'\n        }\n      }],\n      exclude: /node_modules/\n    }, {\n      test: /\\.(js|jsx)$/,\n      exclude: /node_modules/,\n      use: ['babel-loader']\n    }, {\n      test: /\\.scss$/,\n      use: [\n        MiniCssExtractPlugin.loader,\n        'css-loader',\n        'sass-loader'\n      ]\n    }, {\n      test: /\\.css$/,\n      use: [\n        MiniCssExtractPlugin.loader,\n        'css-loader'\n      ]\n    }, {\n      test: /\\.(png|jpg)$/,\n      use: [{\n        loader: \"file-loader\"\n      }]\n    }, {\n      test: /\\.(eot|woff|ttf)$/,\n      use: [{\n        loader: \"file-loader\"\n      }]\n    }]\n  },\n  externals: {\n    'system': '{}', // jsonlint\n    'file': '{}' // jsonlint\n  },\n}\n\nconst renderPlugins = [\n  new HtmlWebpackPlugin({title: 'Medis', chunks: ['main'], filename: 'main.html'}),\n  new HtmlWebpackPlugin({title: 'Manage Patterns', chunks: ['patternManager'], filename: 'patternManager.html'}),\n  new MiniCssExtractPlugin({filename: '[name].css'}),\n  new CheckerPlugin(),\n  new webpack.ProvidePlugin({React: 'react'}),\n]\nif (mode === 'production') {\n  renderPlugins.push(new BundleAnalyzerPlugin())\n}\nconst renderer = Object.assign({}, base, {\n  target: 'electron-renderer',\n  output: Object.assign({}, base.output, {\n    path: resolve(base.output.path, 'renderer')\n  }),\n  entry: {\n    main: resolve(__dirname, 'src/renderer/windows/MainWindow/entry.jsx'),\n    patternManager: resolve(__dirname, 'src/renderer/windows/PatternManagerWindow/entry.jsx')\n  },\n  plugins: renderPlugins,\n  resolve: {\n    alias: {\n      Redux: resolve(__dirname, 'src/renderer/redux/'),\n      Utils: resolve(__dirname, 'src/renderer/utils/'),\n    },\n    extensions: ['.js', '.jsx', '.ts', '.tsx']\n  }\n})\n\nconst main = Object.assign({}, base, {\n  target: 'electron-main',\n  output: Object.assign({}, base.output, {\n    path: resolve(base.output.path, 'main')\n  }),\n  entry: {\n    index: resolve(__dirname, 'src/main/index.ts')\n  },\n  resolve: {\n    extensions: ['.js', '.jsx', '.ts', '.tsx']\n  }\n})\n\nmodule.exports = [main, renderer]\n"
  }
]