[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\n    [\"env\", {\n      \"targets\": { \"node\": 7 },\n      \"useBuiltIns\": true\n    }],\n    \"stage-0\",\n    \"react\"\n  ],\n  \"plugins\": [\"add-module-exports\",\n    [\"import\",\n      {\n        \"libraryName\": \"antd\",\n        \"style\": \"css\"\n      }\n    ]\n  ],\n  \"env\": {\n    \"production\": {\n      \"presets\": [\"react-optimize\"],\n      \"plugins\": [\"dev-expression\"]\n    },\n    \"development\": {\n      \"plugins\": [\n        \"transform-class-properties\",\n        \"transform-es2015-classes\",\n        [\"flow-runtime\", {\n          \"assert\": true,\n          \"annotate\": true\n        }]\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": ".dockerignore",
    "content": "# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n.eslintcache\n\n# Dependency directory\n# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git\nnode_modules\napp/node_modules\n\n# OSX\n.DS_Store\n\n# flow-typed\nflow-typed/npm/*\n!flow-typed/npm/module_vx.x.x.js\n\n# App packaged\nrelease\napp/main.prod.js\napp/main.prod.js.map\napp/renderer.prod.js\napp/renderer.prod.js.map\napp/style.css\napp/style.css.map\ndist\ndll\nmain.js\nmain.js.map\n\n.idea\nnpm-debug.log.*\n.*.dockerfile"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".eslintignore",
    "content": "# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n.eslintcache\n\n# Dependency directory\n# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git\nnode_modules\napp/node_modules\n\n# OSX\n.DS_Store\n\n# flow-typed\nflow-typed/npm/*\n!flow-typed/npm/module_vx.x.x.js\n\n# App packaged\nrelease\napp/main.prod.js\napp/main.prod.js.map\napp/renderer.prod.js\napp/renderer.prod.js.map\napp/style.css\napp/style.css.map\ndist\ndll\nmain.js\nmain.js.map\n\n.idea\nnpm-debug.log.*\n__snapshots__\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"parser\": \"babel-eslint\",\n  \"parserOptions\": {\n    \"sourceType\": \"module\",\n    \"allowImportExportEverywhere\": true\n  },\n  \"extends\": \"airbnb\",\n  \"env\": {\n    \"browser\": true,\n    \"node\": true\n  },\n  \"rules\": {\n    \"arrow-parens\": [\"off\"],\n    \"compat/compat\": \"error\",\n    \"consistent-return\": \"off\",\n    \"comma-dangle\": \"off\",\n    \"generator-star-spacing\": \"off\",\n    \"import/no-unresolved\": \"error\",\n    \"import/no-extraneous-dependencies\": \"off\",\n    \"jsx-a11y/anchor-is-valid\": \"off\",\n    \"no-console\": \"off\",\n    \"no-use-before-define\": \"off\",\n    \"no-multi-assign\": \"off\",\n    \"promise/param-names\": \"error\",\n    \"promise/always-return\": \"error\",\n    \"promise/catch-or-return\": \"error\",\n    \"promise/no-native\": \"off\",\n    \"react/sort-comp\": [\"error\", {\n      \"order\": [\"type-annotations\", \"static-methods\", \"lifecycle\", \"everything-else\", \"render\"]\n    }],\n    \"react/jsx-no-bind\": \"off\",\n    \"react/jsx-filename-extension\": [\"error\", { \"extensions\": [\".js\", \".jsx\"] }],\n    \"react/prefer-stateless-function\": \"off\"\n  },\n  \"plugins\": [\n    \"flowtype\",\n    \"import\",\n    \"promise\",\n    \"compat\",\n    \"react\"\n  ],\n  \"settings\": {\n    \"import/resolver\": {\n      \"webpack\": {\n        \"config\": \"webpack.config.eslint.js\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": ".flowconfig",
    "content": "[ignore]\n<PROJECT_ROOT>/node_modules/*\n<PROJECT_ROOT>/app/main.prod.js\n<PROJECT_ROOT>/app/main.prod.js.map\n<PROJECT_ROOT>/app/dist/.*\n<PROJECT_ROOT>/resources/.*\n<PROJECT_ROOT>/release/.*\n<PROJECT_ROOT>/dll/.*\n<PROJECT_ROOT>/release/.*\n<PROJECT_ROOT>/git/.*\n\n[include]\n\n[libs]\n\n[options]\nesproposal.class_static_fields=enable\nesproposal.class_instance_fields=enable\nesproposal.export_star_as=enable\nmodule.name_mapper.extension='css' -> '<PROJECT_ROOT>/internals/flow/CSSModule.js.flow'\nmodule.name_mapper.extension='styl' -> '<PROJECT_ROOT>/internals/flow/CSSModule.js.flow'\nmodule.name_mapper.extension='scss' -> '<PROJECT_ROOT>/internals/flow/CSSModule.js.flow'\nmodule.name_mapper.extension='png' -> '<PROJECT_ROOT>/internals/flow/WebpackAsset.js.flow'\nmodule.name_mapper.extension='jpg' -> '<PROJECT_ROOT>/internals/flow/WebpackAsset.js.flow'\nsuppress_comment=\\\\(.\\\\|\\n\\\\)*\\\\$FlowFixMe\nsuppress_comment=\\\\(.\\\\|\\n\\\\)*\\\\$FlowIssue\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text eol=lf\n*.png binary\n*.jpg binary\n*.jpeg binary\n*.ico binary\n*.icns binary\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n.eslintcache\n\n# Dependency directory\n# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git\nnode_modules\napp/node_modules\n\n# OSX\n.DS_Store\n\n# flow-typed\nflow-typed/npm/*\n!flow-typed/npm/module_vx.x.x.js\n\n# App packaged\nrelease\napp/main.prod.js\napp/main.prod.js.map\napp/renderer.prod.js\napp/renderer.prod.js.map\napp/style.css\napp/style.css.map\ndist\ndll\nmain.js\nmain.js.map\n\n.idea\nnpm-debug.log.*\n"
  },
  {
    "path": ".stylelintrc",
    "content": "{\n  \"extends\": \"stylelint-config-standard\"\n}\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: true\n\nlanguage: node_js\n\nnode_js:\n  - 8\n  - 7\n\ncache:\n  yarn: true\n  directories:\n    - node_modules\n    - app/node_modules\n\naddons:\n  apt:\n    sources:\n      - ubuntu-toolchain-r-test\n    packages:\n      - g++-4.8\n      - icnsutils\n      - graphicsmagick\n      - xz-utils\n      - xorriso\n\ninstall:\n  - export CXX=\"g++-4.8\"\n  - yarn\n  - cd app && yarn && cd ..\n  - \"/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16\"\n\nbefore_script:\n  - export DISPLAY=:99.0\n  - sh -e /etc/init.d/xvfb start &\n  - sleep 3\n\nscript:\n  - node --version\n  - yarn lint\n  - yarn package\n  - yarn test\n  - yarn test-e2e\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"javascript.validate.enable\": false,\n  \"flow.useNPMPackagedFlow\": true,\n  \"search.exclude\": {\n    \".git\": true,\n    \".eslintcache\": true,\n    \"app/dist\": true,\n    \"app/main.prod.js\": true,\n    \"app/main.prod.js.map\": true,\n    \"bower_components\": true,\n    \"dll\": true,\n    \"flow-typed\": true,\n    \"release\": true,\n    \"node_modules\": true,\n    \"npm-debug.log.*\": true,\n    \"test/**/__snapshots__\": true,\n    \"yarn.lock\": true\n  }\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "* 2018.5.5\n\n修改页面样式，当全屏时隐藏掉左侧菜单栏和顶部接口切换栏\n\n* 2018.07.13\n\n修改线路下拉框不能出现滚动条的 bug\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015-present C. T. Lin\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\n"
  },
  {
    "path": "README.md",
    "content": "### i视频\n\n#### 产品介绍\n\n> 基于 Electron 开发的跨平台客户端版本的视频播放器，该播放器包括国内主流视频平台视频资源，你不用去单独下载各个平台的客户端，只需要使用这一个客户端就能查看所有平台的视频，并且内置了各大视频网站 VIP 资源。\n\n#### 使用方法\n\n1. 下载客户端 \n* [Mac](https://github.com/phobal/ivideo/releases/download/v1.1.4/ivideo-1.1.4.dmg.zip)\n* [Windows](https://github.com/phobal/ivideo/releases/download/v1.1.4/ivideo.Setup.1.1.4.exe.zip)\n* [Linux](https://github.com/phobal/ivideo/releases/download/1.0.0/linux-unpacked.v1.0.0.zip)\n\n2. 选择视频资源  \n\n比方说看腾讯视频上的 VIP 才能看的《下一站,别离》\n\n![](./resources/showcase01.jpg)\n\n点击进去以后提示需要开通VIP才能看\n\n![](./resources/showcase02.jpg)\n\n3. 选择资源播放接口  \n\n![](./resources/showcase03.jpg)\n\n点击【确定】按钮就可以播放了，如果遇到无法播放的情况，请多换几条线路试试\n\n![](./resources/showcase04.jpg)\n\n### 技术栈\n\n* Electron\n* React\n* Redux\n\n### 如何启动\n\n> node version >= 7.6\n\n1. clone 项目到本地\n\n``` bash\n\ngit clone https://github.com/phobal/ivideo.git\n\n```\n\n2. 进入项目 ` cd ivideo`\n3. 安装依赖 `yarn install`(如果没有的话，请全局安装yarn, `npm i yarn -g`)\n4. 打开开发环境 `yarn start`\n\n### 如何编译\n\n* 编译全平台 ` yarn package-all`\n* 编译当前平台 `yarn package`\n* windows: `yarn package-win`\n* Linux `yarn package-linux`\n\n编译出来的包都放在 `release` 目录下\n\n该项目是基于 [electron-react-boilerplate](https://github.com/chentsulin/electron-react-boilerplate) 脚手架 进行创建，感谢 @[chentsulin](https://github.com/chentsulin)\n\n\n# 最后请大家低调使用，祝大家看得舒心\n## 本项目仅作为个人学习用途，如有侵权请联系我删除该仓库\n"
  },
  {
    "path": "app/.eslintrc",
    "content": "{\n  \"rules\": {\n    \"flowtype/boolean-style\": [\"error\", \"boolean\"],\n    \"flowtype/define-flow-type\": \"warn\",\n    \"flowtype/delimiter-dangle\": [\"error\", \"never\"],\n    \"flowtype/generic-spacing\": [\"error\", \"never\"],\n    \"flowtype/no-primitive-constructor-types\": \"error\",\n    \"flowtype/no-weak-types\": \"warn\",\n    \"flowtype/object-type-delimiter\": [\"error\", \"comma\"],\n    \"flowtype/require-parameter-type\": \"off\",\n    \"flowtype/require-return-type\": \"off\",\n    \"flowtype/require-valid-file-annotation\": \"off\",\n    \"flowtype/semi\": [\"error\", \"always\"],\n    \"flowtype/space-after-type-colon\": [\"error\", \"always\"],\n    \"flowtype/space-before-generic-bracket\": [\"error\", \"never\"],\n    \"flowtype/space-before-type-colon\": [\"error\", \"never\"],\n    \"flowtype/union-intersection-spacing\": [\"error\", \"always\"],\n    \"flowtype/use-flow-type\": \"error\",\n    \"flowtype/valid-syntax\": \"error\"\n  }\n}\n"
  },
  {
    "path": "app/actions/source.js",
    "content": "import * as api from '../utils/fetch';\n\nexport function getAllVideoSource() {\n  return (dispatch) => {\n    api.source.getAllVideoSource().then((res) =>\n      dispatch({\n        type: 'GETALLVIDEOSOURCE',\n        payload: res.data\n      }));\n  };\n}\n"
  },
  {
    "path": "app/app.global.css",
    "content": "/*\n * @NOTE: Prepend a `~` to css file paths that are in your node_modules\n *        See https://github.com/webpack-contrib/sass-loader#imports\n */\n@import \"~font-awesome/css/font-awesome.css\";\n@import \"~rc-menu/assets/index.css\";\n/* @import \"~antd/lib/style/index.css\"; */\n@import '~rc-select/assets/index.css';\n* {\n  margin: 0;\n  padding: 0;\n}\nbody {\n  position: relative;\n  color:#000;\n  height: 100vh;\n  /* background-color: #232c39; */\n  /* background-image: linear-gradient(45deg, rgba(0, 216, 255, 0.5) 10%, rgba(0, 1, 127, 0.7)); */\n  font-family: Arial, Helvetica, Helvetica Neue, serif;\n  overflow-y: hidden;\n}\n\nh2 {\n  margin: 0;\n  font-size: 2.25rem;\n  font-weight: bold;\n  letter-spacing: -0.025em;\n  color: #fff;\n}\n\np {\n  font-size: 24px;\n}\n\nli {\n  list-style: none;\n}\n\na {\n  color: white;\n  opacity: 0.75;\n  text-decoration: none;\n}\n\na:hover {\n  opacity: 1;\n  text-decoration: none;\n  cursor: pointer;\n}\n.rc-select-dropdown-menu {\n  max-height: 400px;\n}"
  },
  {
    "path": "app/app.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>i视频</title>\n    <script>\n      (function() {\n        if (!process.env.HOT) {\n          const link = document.createElement('link');\n          link.rel = 'stylesheet';\n          link.href = './dist/style.css';\n          // HACK: Writing the script path should be done with webpack\n          document.getElementsByTagName('head')[0].appendChild(link);\n        }\n      }());\n    </script>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script>\n      {\n        const scripts = [];\n\n        // Dynamically insert the DLL script in development env in the\n        // renderer process\n        if (process.env.NODE_ENV === 'development') {\n          scripts.push('../dll/renderer.dev.dll.js');\n        }\n\n        // Dynamically insert the bundled app script in the renderer process\n        const port = process.env.PORT || 1212;\n        scripts.push(\n          (process.env.HOT)\n            ? 'http://localhost:' + port + '/dist/renderer.dev.js'\n            : './dist/renderer.prod.js'\n        );\n\n        document.write(\n          scripts\n            .map(script => '<script defer src=\"' + script + '\"><\\/script>')\n            .join('')\n        );\n      }\n    </script>\n    <script>\n      var _hmt = _hmt || [];\n      (function() {\n        var hm = document.createElement(\"script\");\n        hm.src = \"https://hm.baidu.com/hm.js?5c4119b9e77388039645dd22dd9b5a26\";\n        var s = document.getElementsByTagName(\"script\")[0]; \n        s.parentNode.insertBefore(hm, s);\n      })();\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "app/components/.githook",
    "content": ""
  },
  {
    "path": "app/containers/App.js",
    "content": "// @flow\nimport * as React from 'react';\n\ntype Props = {\n  children: React.Node\n};\n\nexport default class App extends React.Component<Props> {\n  props: Props;\n\n  render() {\n    return (\n      <div>\n        {this.props.children}\n      </div>\n    );\n  }\n}\n"
  },
  {
    "path": "app/containers/Channel.js",
    "content": "import React from 'react';\nimport Menu, { Item as MenuItem } from 'rc-menu';\n\nconst Channel = ({ channel, handleSwitchChannel }) => {\n  const item = channel.map((d) => <MenuItem key={d.url}>{d.name}</MenuItem>);\n  return <Menu onSelect={handleSwitchChannel}>{item}</Menu>;\n};\n\nexport default Channel;\n"
  },
  {
    "path": "app/containers/Frame.js",
    "content": "// @flow\nimport React from 'react'\n\nimport Channel from './Channel'\nimport ToolBar from './ToolBar'\n\nexport const Frame = ({\n  onComeback,\n  onSourceSelected,\n  onSwitchSource,\n  handleSwitchChannel,\n  channel,\n  url,\n  freeUrl,\n  title,\n  isFullScreen,\n  children\n}) => {\n  const isHiddenStyle = isFullScreen ? { display: 'none' } : { display: 'flex' }\n  return (\n    <div style={{ display: 'flex' }}>\n      <div style={{ minWidth: '150px', ...isHiddenStyle }}>\n        <Channel channel={channel} handleSwitchChannel={handleSwitchChannel} />\n      </div>\n      <div\n        style={{\n          height: '100vh',\n          width: isFullScreen ? '100vw' : 'calc(100vw - 150px)',\n          display: 'flex',\n          flexDirection: 'column'\n        }}\n      >\n        <div style={{ height: '60px', ...isHiddenStyle }}>\n          <ToolBar\n            onComeback={onComeback}\n            onSourceSelected={onSourceSelected}\n            onSwitchSource={onSwitchSource}\n            freeUrl={freeUrl}\n            title={title}\n          />\n        </div>\n        {children}\n      </div>\n    </div>\n  )\n}\n\nexport default Frame\n"
  },
  {
    "path": "app/containers/Root.js",
    "content": "// @flow\nimport React, { Component } from 'react';\nimport { Provider } from 'react-redux';\nimport { ConnectedRouter } from 'react-router-redux';\nimport Routes from '../routes';\n\ntype Props = {\n  store: {},\n  history: {}\n};\n\nexport default class Root extends Component<Props> {\n  render() {\n    return (\n      <Provider store={this.props.store}>\n        <ConnectedRouter history={this.props.history}>\n          <Routes />\n        </ConnectedRouter>\n      </Provider>\n    );\n  }\n}\n"
  },
  {
    "path": "app/containers/ToolBar.js",
    "content": "import React from 'react';\nimport Select, { Option } from 'rc-select';\n// import { Icon, Select, Button } from 'antd';\n\nconst ToolBar = ({ onComeback, onSwitchSource, onSourceSelected, freeUrl, title }) => {\n  const options = freeUrl.map(d => {\n    return (\n      <Option key={d.name} value={d.name}>{d.name}</Option>\n    )\n  })\n  return (\n    <div style={{ display: 'flex', height: '100%', alignItems: 'center', justifyContent: 'space-between', padding: '0 20px' }}>\n      <div type=\"rollback\" style={{ fontSize: '18px', cursor: 'pointer' }} onClick={onComeback}>返回</div>\n      <span style={{ padding: '0 20px', color: 'darkcyan' }}>{title}</span>\n      <div\n        style={{ display: 'flex', alignItems: 'center' }}\n      >\n        <Select\n          style={{ width: '200px' }}\n          placeholder=\"请选择路线\"\n          onSelect={onSourceSelected}\n        >\n          {options}\n        </Select>\n        <div\n          style={{ width: '100px', height: '40px', lineHeight: '40px', cursor: 'pointer', background: '#2196f3', color: '#fff', textAlign: 'center', marginLeft: '20px' }}\n          onClick={onSwitchSource}\n        >确定</div>\n      </div>\n    </div>\n  )\n}\n\nexport default ToolBar;"
  },
  {
    "path": "app/containers/Video.js",
    "content": "// @flow\nimport React, { PureComponent } from 'react';\nimport { connect } from 'react-redux';\nimport { bindActionCreators } from 'redux';\nimport { webview, ipcRenderer } from 'electron';\n\nimport Channel from './Channel';\nimport ToolBar from './ToolBar';\nimport Frame from './Frame';\nimport * as sourceActions from '../actions/source';\n\nclass VideoPlay extends PureComponent<Props> {\n  constructor(props) {\n    super(props);\n    this.handleSwitchChannel = this.handleSwitchChannel.bind(this);\n    this.onComeback = this.onComeback.bind(this);\n    this.onSourceSelected = this.onSourceSelected.bind(this);\n    this.onSwitchSource = this.onSwitchSource.bind(this);\n  }\n  state = {\n    channel: [],\n    url: 'https://v.qq.com',\n    freeUrl: [],\n    selectedUrl: 'http://vip.jlsprh.com/index.php?url=',\n    isFullScreen: false\n  }\n  componentDidMount() {\n    this.props.actions.getAllVideoSource();\n    const webView = this.webview;\n    webView.addEventListener('dom-ready', () => {\n      this.setTitle();\n    });\n    webView.addEventListener('new-window', (obj) => {\n      this.setState({\n        url: `${obj.url}`\n      });\n    });\n    webView.addEventListener('will-navigate', (obj) => {\n      this.setState({\n        url: `${obj.url}`\n      });\n    });\n    ipcRenderer.on('enter-full-screen', (e, msg) => {\n      this.setState({\n        isFullScreen: msg\n      });\n    });\n  }\n  componentWillReceiveProps(nextProps) {\n    const { source } = nextProps;\n    if (source) {\n      this.setState({\n        channel: source.platformlist,\n        freeUrl: source.list\n      });\n    }\n  }\n  handleSwitchChannel(value) {\n    this.setState({\n      url: value.key\n    });\n  }\n  setTitle() {\n    const title = this.webview.getTitle();\n    this.setState({\n      title\n    });\n  }\n  onComeback() {\n    this.webview.goBack();\n  }\n  onSourceSelected(value) {\n    const selectedUrl = this.state.freeUrl.find((d) => {\n      if (d.name === value) {\n        return d.url;\n      }\n    });\n    this.setState({\n      selectedUrl\n    });\n  }\n  onSwitchSource() {\n    const { selectedUrl } = this.state;\n    const currentVideoUrl = this.webview.getURL();\n    this.setState({\n      url: `${selectedUrl.url}${currentVideoUrl}`\n    });\n  }\n  render() {\n    const {\n channel, url, freeUrl, title, isFullScreen \n} = this.state;\n    return (\n      <Frame\n        onComeback={this.onComeback}\n        onSourceSelected={this.onSourceSelected}\n        onSwitchSource={this.onSwitchSource}\n        handleSwitchChannel={this.handleSwitchChannel}\n        {...{\n          channel,\n          url,\n          freeUrl,\n          title,\n          isFullScreen\n        }}\n      >\n        <webview\n          ref={(webview) => {\n            this.webview = webview;\n          }}\n          title=\"腾讯视频\"\n          style={{\n            height: isFullScreen ? '100vh' : 'calc(100vh - 60px)',\n            width: '100%'\n          }}\n          src={url}\n          allowpopups=\"true\"\n          plugins\n        />\n      </Frame>\n    );\n  }\n}\n\nfunction mapDispatchToProps(dispatch) {\n  return {\n    actions: {\n      ...bindActionCreators(sourceActions, dispatch)\n    }\n  };\n}\nfunction mapStateToProps(state) {\n  return {\n    source: state.source\n  };\n}\n\nexport default connect(\n  mapStateToProps,\n  mapDispatchToProps\n)(VideoPlay);\n"
  },
  {
    "path": "app/index.js",
    "content": "import React from 'react';\nimport { render } from 'react-dom';\nimport { AppContainer } from 'react-hot-loader';\nimport Root from './containers/Root';\nimport { configureStore, history } from './store/configureStore';\nimport './app.global.css';\n\nconst store = configureStore();\n\nrender(\n  <AppContainer>\n    <Root store={store} history={history} />\n  </AppContainer>,\n  document.getElementById('root')\n);\n\nif (module.hot) {\n  module.hot.accept('./containers/Root', () => {\n    const NextRoot = require('./containers/Root'); // eslint-disable-line global-require\n    render(\n      <AppContainer>\n        <NextRoot store={store} history={history} />\n      </AppContainer>,\n      document.getElementById('root')\n    );\n  });\n}\n"
  },
  {
    "path": "app/main.dev.js",
    "content": "/* eslint global-require: 0, flowtype-errors/show-errors: 0 */\n\n/**\n * This module executes inside of electron's main process. You can start\n * electron renderer process from here and communicate with the other processes\n * through IPC.\n *\n * When running `npm run build` or `npm run build-main`, this file is compiled to\n * `./app/main.prod.js` using webpack. This gives us some performance wins.\n *\n * @flow\n */\nimport { app, BrowserWindow, ipcMain } from 'electron';\nimport MenuBuilder from './menu';\n\nlet mainWindow = null;\n\nif (process.env.NODE_ENV === 'production') {\n  const sourceMapSupport = require('source-map-support');\n  sourceMapSupport.install();\n}\n\nif (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') {\n  require('electron-debug')();\n  const path = require('path');\n  const p = path.join(__dirname, '..', 'app', 'node_modules');\n  require('module').globalPaths.push(p);\n}\n\nconst installExtensions = async () => {\n  const installer = require('electron-devtools-installer');\n  const forceDownload = !!process.env.UPGRADE_EXTENSIONS;\n  const extensions = [\n    'REACT_DEVELOPER_TOOLS',\n    'REDUX_DEVTOOLS'\n  ];\n\n  return Promise\n    .all(extensions.map(name => installer.default(installer[name], forceDownload)))\n    .catch(console.log);\n};\n\n// Try to append Pepper flash. See https://github.com/electron/electron/blob/master/docs/tutorial/using-pepper-flash-plugin.md\nif (process.platform === 'darwin' && app.getPath(\"pepperFlashSystemPlugin\")) {\n  app.commandLine.appendSwitch(\n    \"ppapi-flash-path\",\n    app.getPath(\"pepperFlashSystemPlugin\")\n  );\n}\n\n\n/**\n * Add event listeners...\n */\n\napp.on('window-all-closed', () => {\n  // Respect the OSX convention of having the application in memory even\n  // after all windows have been closed\n  if (process.platform !== 'darwin') {\n    app.quit();\n  }\n});\n\n\napp.on('ready', async () => {\n  if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') {\n    await installExtensions();\n  }\n\n  mainWindow = new BrowserWindow({\n    show: false,\n    width: 1024,\n    height: 728,\n    webPreferences: {\n      plugins: true\n    }\n  });\n\n  mainWindow.loadURL(`file://${__dirname}/app.html`);\n\n  // @TODO: Use 'ready-to-show' event\n  //        https://github.com/electron/electron/blob/master/docs/api/browser-window.md#using-ready-to-show-event\n  mainWindow.webContents.on('did-finish-load', () => {\n    if (!mainWindow) {\n      throw new Error('\"mainWindow\" is not defined');\n    }\n    mainWindow.show();\n    mainWindow.focus();\n  });\n\n  mainWindow.on('closed', () => {\n    mainWindow = null;\n  });\n\n  mainWindow.on('enter-full-screen', () => {\n    mainWindow.webContents.send('enter-full-screen', true);\n  })\n  mainWindow.on('leave-full-screen', () => {\n    mainWindow.webContents.send('enter-full-screen', false);\n  })\n  const menuBuilder = new MenuBuilder(mainWindow);\n  menuBuilder.buildMenu();\n});\n"
  },
  {
    "path": "app/menu.js",
    "content": "// @flow\nimport { app, Menu, shell, BrowserWindow } from 'electron';\n\nexport default class MenuBuilder {\n  mainWindow: BrowserWindow;\n\n  constructor(mainWindow: BrowserWindow) {\n    this.mainWindow = mainWindow;\n  }\n\n  buildMenu() {\n    if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') {\n      this.setupDevelopmentEnvironment();\n    }\n\n    const template = process.platform === 'darwin'\n      ? this.buildDarwinTemplate()\n      : this.buildDefaultTemplate();\n\n    const menu = Menu.buildFromTemplate(template);\n    Menu.setApplicationMenu(menu);\n\n    return menu;\n  }\n\n  setupDevelopmentEnvironment() {\n    this.mainWindow.openDevTools();\n    this.mainWindow.webContents.on('context-menu', (e, props) => {\n      const { x, y } = props;\n\n      Menu\n        .buildFromTemplate([{\n          label: 'Inspect element',\n          click: () => {\n            this.mainWindow.inspectElement(x, y);\n          }\n        }])\n        .popup(this.mainWindow);\n    });\n  }\n\n  buildDarwinTemplate() {\n    const subMenuAbout = {\n      label: 'Electron',\n      submenu: [\n        { label: 'About ElectronReact', selector: 'orderFrontStandardAboutPanel:' },\n        { type: 'separator' },\n        { label: 'Services', submenu: [] },\n        { type: 'separator' },\n        { label: 'Hide ElectronReact', accelerator: 'Command+H', selector: 'hide:' },\n        { label: 'Hide Others', accelerator: 'Command+Shift+H', selector: 'hideOtherApplications:' },\n        { label: 'Show All', selector: 'unhideAllApplications:' },\n        { type: 'separator' },\n        { label: 'Quit', accelerator: 'Command+Q', click: () => { app.quit(); } }\n      ]\n    };\n    const subMenuEdit = {\n      label: 'Edit',\n      submenu: [\n        { label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' },\n        { label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' },\n        { type: 'separator' },\n        { label: 'Cut', accelerator: 'Command+X', selector: 'cut:' },\n        { label: 'Copy', accelerator: 'Command+C', selector: 'copy:' },\n        { label: 'Paste', accelerator: 'Command+V', selector: 'paste:' },\n        { label: 'Select All', accelerator: 'Command+A', selector: 'selectAll:' }\n      ]\n    };\n    const subMenuViewDev = {\n      label: 'View',\n      submenu: [\n        { label: 'Reload', accelerator: 'Command+R', click: () => { this.mainWindow.webContents.reload(); } },\n        { label: 'Toggle Full Screen', accelerator: 'Ctrl+Command+F', click: () => { this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); } },\n        { label: 'Toggle Developer Tools', accelerator: 'Alt+Command+I', click: () => { this.mainWindow.toggleDevTools(); } }\n      ]\n    };\n    const subMenuViewProd = {\n      label: 'View',\n      submenu: [\n        { label: 'Toggle Full Screen', accelerator: 'Ctrl+Command+F', click: () => { this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); } }\n      ]\n    };\n    const subMenuWindow = {\n      label: 'Window',\n      submenu: [\n        { label: 'Minimize', accelerator: 'Command+M', selector: 'performMiniaturize:' },\n        { label: 'Close', accelerator: 'Command+W', selector: 'performClose:' },\n        { type: 'separator' },\n        { label: 'Bring All to Front', selector: 'arrangeInFront:' }\n      ]\n    };\n    const subMenuHelp = {\n      label: 'Help',\n      submenu: [\n        { label: 'Learn More', click() { shell.openExternal('http://electron.atom.io'); } },\n        { label: 'Documentation', click() { shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme'); } },\n        { label: 'Community Discussions', click() { shell.openExternal('https://discuss.atom.io/c/electron'); } },\n        { label: 'Search Issues', click() { shell.openExternal('https://github.com/atom/electron/issues'); } }\n      ]\n    };\n\n    const subMenuView = process.env.NODE_ENV === 'development'\n      ? subMenuViewDev\n      : subMenuViewProd;\n\n    return [\n      subMenuAbout,\n      subMenuEdit,\n      subMenuView,\n      subMenuWindow,\n      subMenuHelp\n    ];\n  }\n\n  buildDefaultTemplate() {\n    const templateDefault = [{\n      label: '&File',\n      submenu: [{\n        label: '&Open',\n        accelerator: 'Ctrl+O'\n      }, {\n        label: '&Close',\n        accelerator: 'Ctrl+W',\n        click: () => {\n          this.mainWindow.close();\n        }\n      }]\n    }, {\n      label: '&View',\n      submenu: (process.env.NODE_ENV === 'development') ? [{\n        label: '&Reload',\n        accelerator: 'Ctrl+R',\n        click: () => {\n          this.mainWindow.webContents.reload();\n        }\n      }, {\n        label: 'Toggle &Full Screen',\n        accelerator: 'F11',\n        click: () => {\n          this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());\n        }\n      }, {\n        label: 'Toggle &Developer Tools',\n        accelerator: 'Alt+Ctrl+I',\n        click: () => {\n          this.mainWindow.toggleDevTools();\n        }\n      }] : [{\n        label: 'Toggle &Full Screen',\n        accelerator: 'F11',\n        click: () => {\n          this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());\n        }\n      }]\n    }, {\n      label: 'Help',\n      submenu: [{\n        label: 'Learn More',\n        click() {\n          shell.openExternal('http://electron.atom.io');\n        }\n      }, {\n        label: 'Documentation',\n        click() {\n          shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme');\n        }\n      }, {\n        label: 'Community Discussions',\n        click() {\n          shell.openExternal('https://discuss.atom.io/c/electron');\n        }\n      }, {\n        label: 'Search Issues',\n        click() {\n          shell.openExternal('https://github.com/atom/electron/issues');\n        }\n      }]\n    }];\n\n    return templateDefault;\n  }\n}\n"
  },
  {
    "path": "app/package.json",
    "content": "{\n  \"name\": \"electron-react-boilerplate\",\n  \"productName\": \"electron-react-boilerplate\",\n  \"version\": \"1.1.4\",\n  \"description\": \"Electron application boilerplate based on React, React Router, Webpack, React Hot Loader for rapid application development\",\n  \"main\": \"./main.prod.js\",\n  \"author\": {\n    \"name\": \"C. T. Lin\",\n    \"email\": \"chentsulin@gmail.com\",\n    \"url\": \"https://github.com/chentsulin\"\n  },\n  \"scripts\": {\n    \"electron-rebuild\": \"node -r babel-register ../internals/scripts/ElectronRebuild.js\",\n    \"postinstall\": \"npm run electron-rebuild\"\n  },\n  \"license\": \"MIT\",\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "app/reducers/index.js",
    "content": "// @flow\nimport { combineReducers } from 'redux';\nimport { routerReducer as router } from 'react-router-redux';\nimport source from './source';\n\nconst rootReducer = combineReducers({\n  router,\n  source,\n});\n\nexport default rootReducer;\n"
  },
  {
    "path": "app/reducers/source.js",
    "content": "export default function source(state = null, action) {\n  switch(action.type) {\n    case 'GETALLVIDEOSOURCE':\n      return action.payload;\n    default:\n      return state;\n  }\n}"
  },
  {
    "path": "app/routes.js",
    "content": "/* eslint flowtype-errors/show-errors: 0 */\nimport React from 'react';\nimport { Switch, Route } from 'react-router';\nimport App from './containers/App';\nimport Video from './containers/Video';\n\nexport default () => (\n  <App>\n    <Switch>\n      <Route path=\"/\" component={Video} />\n    </Switch>\n  </App>\n);\n"
  },
  {
    "path": "app/store/configureStore.dev.js",
    "content": "import { createStore, applyMiddleware, compose } from 'redux';\nimport thunk from 'redux-thunk';\nimport { createHashHistory } from 'history';\nimport { routerMiddleware, routerActions } from 'react-router-redux';\nimport { createLogger } from 'redux-logger';\nimport rootReducer from '../reducers';\n// import type { counterStateType } from '../reducers/counter';\n\nconst history = createHashHistory();\n\nconst configureStore = (initialState) => {\n  // Redux Configuration\n  const middleware = [];\n  const enhancers = [];\n\n  // Thunk Middleware\n  middleware.push(thunk);\n\n  // Logging Middleware\n  const logger = createLogger({\n    level: 'info',\n    collapsed: true\n  });\n\n  // Skip redux logs in console during the tests\n  if (process.env.NODE_ENV !== 'test') {\n    middleware.push(logger);\n  }\n\n  // Router Middleware\n  const router = routerMiddleware(history);\n  middleware.push(router);\n\n  // Redux DevTools Configuration\n  const actionCreators = {\n    ...routerActions,\n  };\n  // If Redux DevTools Extension is installed use it, otherwise use Redux compose\n  /* eslint-disable no-underscore-dangle */\n  const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__\n    ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({\n      // Options: http://zalmoxisus.github.io/redux-devtools-extension/API/Arguments.html\n      actionCreators,\n    })\n    : compose;\n  /* eslint-enable no-underscore-dangle */\n\n  // Apply Middleware & Compose Enhancers\n  enhancers.push(applyMiddleware(...middleware));\n  const enhancer = composeEnhancers(...enhancers);\n\n  // Create Store\n  const store = createStore(rootReducer, initialState, enhancer);\n\n  if (module.hot) {\n    module.hot.accept('../reducers', () =>\n      store.replaceReducer(require('../reducers'))); // eslint-disable-line global-require\n  }\n\n  return store;\n};\n\nexport default { configureStore, history };\n"
  },
  {
    "path": "app/store/configureStore.js",
    "content": "// @flow\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./configureStore.prod'); // eslint-disable-line global-require\n} else {\n  module.exports = require('./configureStore.dev'); // eslint-disable-line global-require\n}\n"
  },
  {
    "path": "app/store/configureStore.prod.js",
    "content": "// @flow\nimport { createStore, applyMiddleware } from 'redux';\nimport thunk from 'redux-thunk';\nimport { createBrowserHistory } from 'history';\nimport { routerMiddleware } from 'react-router-redux';\nimport rootReducer from '../reducers';\nimport type { counterStateType } from '../reducers/counter';\n\nconst history = createBrowserHistory();\nconst router = routerMiddleware(history);\nconst enhancer = applyMiddleware(thunk, router);\n\nfunction configureStore(initialState?: counterStateType) {\n  return createStore(rootReducer, initialState, enhancer);\n}\n\nexport default { configureStore, history };\n"
  },
  {
    "path": "app/utils/.gitkeep",
    "content": ""
  },
  {
    "path": "app/utils/fetch.js",
    "content": "import axios from 'axios';\n\nconst BASEURL =\n  'https://raw.githubusercontent.com/phobal/ivideo/master/resources/viplist.json';\n\nconst instance = axios.create({\n  baseURL: BASEURL,\n  timeout: 10000\n});\n\nconst createAPI = (url, method, config) => {\n  config = config || {} // eslint-disable-line\n  return instance({\n    url,\n    method,\n    ...config\n  });\n};\n\nconst source = {\n  getAllVideoSource: (config) => createAPI('', 'GET', config)\n};\n\nexport { source };\n"
  },
  {
    "path": "appveyor.yml",
    "content": "os: unstable\n\nenvironment:\n  matrix:\n    - nodejs_version: 8\n    - nodejs_version: 7\n\ncache:\n  - \"%LOCALAPPDATA%/Yarn\"\n  - node_modules -> package.json\n  - app/node_modules -> app/package.json\n\nmatrix:\n  fast_finish: true\n\nbuild: off\n\nversion: '{build}'\n\nshallow_clone: true\n\nclone_depth: 1\n\ninstall:\n  - ps: Install-Product node $env:nodejs_version\n  - set CI=true\n  - yarn\n  - cd app && yarn\n\ntest_script:\n  - node --version\n  - yarn lint\n  - yarn package\n  - yarn test\n  - yarn test-e2e\n"
  },
  {
    "path": "flow-typed/module_vx.x.x.js",
    "content": "declare module 'module' {\n  declare module.exports: any;\n}\n"
  },
  {
    "path": "internals/flow/CSSModule.js.flow",
    "content": "// @flow\n\ndeclare export default { [key: string]: string }"
  },
  {
    "path": "internals/flow/WebpackAsset.js.flow",
    "content": "// @flow\ndeclare export default string\n"
  },
  {
    "path": "internals/mocks/fileMock.js",
    "content": "export default 'test-file-stub';\n"
  },
  {
    "path": "internals/scripts/CheckBuiltsExist.js",
    "content": "// @flow\n// Check if the renderer and main bundles are built\nimport path from 'path';\nimport chalk from 'chalk';\nimport fs from 'fs';\n\nfunction CheckBuildsExist() {\n  const mainPath = path.join(__dirname, '..', '..', 'app', 'main.prod.js');\n  const rendererPath = path.join(__dirname, '..', '..', 'app', 'dist', 'renderer.prod.js');\n\n  if (!fs.existsSync(mainPath)) {\n    throw new Error(chalk.whiteBright.bgRed.bold('The main process is not built yet. Build it by running \"npm run build-main\"'));\n  }\n\n  if (!fs.existsSync(rendererPath)) {\n    throw new Error(chalk.whiteBright.bgRed.bold('The renderer process is not built yet. Build it by running \"npm run build-renderer\"'));\n  }\n}\n\nCheckBuildsExist();\n"
  },
  {
    "path": "internals/scripts/CheckNativeDep.js",
    "content": "// @flow\nimport fs from 'fs';\nimport chalk from 'chalk';\nimport { execSync } from 'child_process';\nimport { dependencies } from '../../package.json';\n\n(() => {\n  if (!dependencies) return;\n\n  const dependenciesKeys = Object.keys(dependencies);\n  const nativeDeps =\n    fs.readdirSync('node_modules')\n      .filter(folder => fs.existsSync(`node_modules/${folder}/binding.gyp`));\n\n  try {\n    // Find the reason for why the dependency is installed. If it is installed\n    // because of a devDependency then that is okay. Warn when it is installed\n    // because of a dependency\n    const dependenciesObject = JSON.parse(execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString());\n    const rootDependencies = Object.keys(dependenciesObject.dependencies);\n    const filteredRootDependencies = rootDependencies\n      .filter(rootDependency => dependenciesKeys.includes(rootDependency));\n\n    if (filteredRootDependencies.length > 0) {\n      const plural = filteredRootDependencies.length > 1;\n      console.log(`\n\n${chalk.whiteBright.bgYellow.bold('Webpack does not work with native dependencies.')}\n${chalk.bold(filteredRootDependencies.join(', '))} ${plural ? 'are native dependencies' : 'is a native dependency'} and should be installed inside of the \"./app\" folder.\n\n\nFirst uninstall the packages from \"./package.json\":\n${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')}\n\n${chalk.bold('Then, instead of installing the package to the root \"./package.json\":')}\n${chalk.whiteBright.bgRed.bold('npm install your-package --save')}\n\n${chalk.bold('Install the package to \"./app/package.json\"')}\n${chalk.whiteBright.bgGreen.bold('cd ./app && npm install your-package --save')}\n\n\nRead more about native dependencies at:\n${chalk.bold('https://github.com/chentsulin/electron-react-boilerplate/wiki/Module-Structure----Two-package.json-Structure')}\n\n\n`);\n\n      process.exit(1);\n    }\n  } catch (e) {\n    console.log('Native dependencies could not be checked');\n  }\n})();\n"
  },
  {
    "path": "internals/scripts/CheckNodeEnv.js",
    "content": "// @flow\nimport chalk from 'chalk';\n\nexport default function CheckNodeEnv(expectedEnv: string) {\n  if (!expectedEnv) {\n    throw new Error('\"expectedEnv\" not set');\n  }\n\n  if (process.env.NODE_ENV !== expectedEnv) {\n    console.log(chalk.whiteBright.bgRed.bold(`\"process.env.NODE_ENV\" must be \"${expectedEnv}\" to use this webpack config`));\n    process.exit(2);\n  }\n}\n"
  },
  {
    "path": "internals/scripts/CheckPortInUse.js",
    "content": "// @flow\nimport chalk from 'chalk';\nimport detectPort from 'detect-port';\n\n(function CheckPortInUse() {\n  const port: string = process.env.PORT || '1212';\n\n  detectPort(port, (err: ?Error, availablePort: number) => {\n    if (port !== String(availablePort)) {\n      throw new Error(chalk.whiteBright.bgRed.bold(`Port \"${port}\" on \"localhost\" is already in use. Please use another port. ex: PORT=4343 npm run dev`));\n    } else {\n      process.exit(0);\n    }\n  });\n}());\n"
  },
  {
    "path": "internals/scripts/ElectronRebuild.js",
    "content": "// @flow\nimport path from 'path';\nimport { execSync } from 'child_process';\nimport fs from 'fs';\nimport dependencies from '../../app/package.json';\n\nconst nodeModulesPath =\n  path.join(__dirname, '..', '..', 'app', 'node_modules');\n\nif (Object.keys(dependencies || {}).length > 0 && fs.existsSync(nodeModulesPath)) {\n  const electronRebuildCmd =\n  '../node_modules/.bin/electron-rebuild --parallel --force --types prod,dev,optional --module-dir .';\n\n  const cmd = process.platform === 'win32'\n    ? electronRebuildCmd.replace(/\\//g, '\\\\')\n    : electronRebuildCmd;\n\n  execSync(cmd, {\n    cwd: path.join(__dirname, '..', '..', 'app')\n  });\n}\n"
  },
  {
    "path": "internals/scripts/RunTests.js",
    "content": "import spawn from 'cross-spawn';\nimport path from 'path';\n\nconst pattern = process.argv[2] === 'e2e'\n  ? 'test/e2e/.+\\\\.spec\\\\.js'\n  : 'test/(?!e2e/)[^/]+/.+\\\\.spec\\\\.js$';\n\nconst result = spawn.sync(\n  path.normalize('./node_modules/.bin/jest'),\n  [pattern, ...process.argv.slice(2)],\n  { stdio: 'inherit' }\n);\n\nprocess.exit(result.status);\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"ivideo\",\n  \"productName\": \"ivideo\",\n  \"version\": \"1.1.4\",\n  \"description\": \"一个视频播放器观看国内主流视频网站，不用单独下载各个平台客户端\",\n  \"scripts\": {\n    \"build\": \"concurrently \\\"npm run build-main\\\" \\\"npm run build-renderer\\\"\",\n    \"build-dll\": \"cross-env NODE_ENV=development node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.renderer.dev.dll.js --colors\",\n    \"build-main\": \"cross-env NODE_ENV=production node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.main.prod.js --colors\",\n    \"build-renderer\": \"cross-env NODE_ENV=production node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.renderer.prod.js --colors\",\n    \"dev\": \"cross-env START_HOT=1 node -r babel-register ./internals/scripts/CheckPortInUse.js && cross-env START_HOT=1 npm run start-renderer-dev\",\n    \"electron-rebuild\": \"electron-rebuild --parallel --force --types prod,dev,optional --module-dir app\",\n    \"flow\": \"flow\",\n    \"flow-typed\": \"rimraf flow-typed/npm && flow-typed install --overwrite || true\",\n    \"lint\": \"cross-env NODE_ENV=development eslint --cache --format=node_modules/eslint-formatter-pretty .\",\n    \"lint-fix\": \"npm run lint -- --fix\",\n    \"lint-styles\": \"stylelint app/*.css app/components/*.css --syntax scss\",\n    \"lint-styles-fix\": \"stylefmt -r app/*.css app/components/*.css\",\n    \"package\": \"npm run build && build --publish never\",\n    \"package-all\": \"npm run build && build -mwl\",\n    \"package-linux\": \"npm run build && build --linux\",\n    \"package-win\": \"npm run build && build --win --x64\",\n    \"postinstall\": \"node -r babel-register internals/scripts/CheckNativeDep.js && npm run flow-typed && npm run build-dll && electron-builder install-app-deps && node node_modules/fbjs-scripts/node/check-dev-engines.js package.json\",\n    \"prestart\": \"npm run build\",\n    \"start\": \"cross-env NODE_ENV=production electron ./app/\",\n    \"start-main-dev\": \"cross-env HOT=1 NODE_ENV=development electron -r babel-register ./app/main.dev\",\n    \"start-renderer-dev\": \"cross-env NODE_ENV=development node --trace-warnings -r babel-register ./node_modules/webpack-dev-server/bin/webpack-dev-server --config webpack.config.renderer.dev.js\",\n    \"test\": \"cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 node --trace-warnings -r babel-register ./internals/scripts/RunTests.js\",\n    \"test-all\": \"npm run lint && npm run flow && npm run build && npm run test && npm run test-e2e\",\n    \"test-e2e\": \"cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 node --trace-warnings -r babel-register ./internals/scripts/RunTests.js e2e\",\n    \"test-watch\": \"npm test -- --watch\"\n  },\n  \"browserslist\": \"electron 1.6\",\n  \"build\": {\n    \"productName\": \"ivideo\",\n    \"appId\": \"org.phobal.ivideo\",\n    \"files\": [\n      \"dist/\",\n      \"node_modules/\",\n      \"app.html\",\n      \"main.prod.js\",\n      \"main.prod.js.map\",\n      \"package.json\"\n    ],\n    \"dmg\": {\n      \"contents\": [\n        {\n          \"x\": 130,\n          \"y\": 220\n        },\n        {\n          \"x\": 410,\n          \"y\": 220,\n          \"type\": \"link\",\n          \"path\": \"/Applications\"\n        }\n      ]\n    },\n    \"win\": {\n      \"target\": [\n        {\n          \"target\": \"nsis\",\n          \"arch\": [\n            \"x64\",\n            \"ia32\"\n          ]\n        }\n      ]\n    },\n    \"linux\": {\n      \"target\": [\n        \"deb\",\n        \"AppImage\"\n      ],\n      \"category\": \"Development\"\n    },\n    \"directories\": {\n      \"buildResources\": \"resources\",\n      \"output\": \"release\"\n    }\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/phobal/ivideo.git\"\n  },\n  \"author\": {\n    \"name\": \"phobal\",\n    \"email\": \"phobal@126.com\",\n    \"url\": \"https://github.com/phobal\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/phobal/ivideo/issues\"\n  },\n  \"keywords\": [\n    \"electron\",\n    \"boilerplate\",\n    \"react\",\n    \"redux\",\n    \"flow\",\n    \"sass\",\n    \"webpack\",\n    \"hot\",\n    \"reload\"\n  ],\n  \"homepage\": \"https://github.com/phobal/ivideo#readme\",\n  \"jest\": {\n    \"moduleNameMapper\": {\n      \"\\\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$\": \"<rootDir>/internals/mocks/fileMock.js\",\n      \"\\\\.(css|less|sass|scss)$\": \"identity-obj-proxy\"\n    },\n    \"moduleFileExtensions\": [\n      \"js\"\n    ],\n    \"moduleDirectories\": [\n      \"node_modules\",\n      \"app/node_modules\"\n    ],\n    \"transform\": {\n      \"^.+\\\\.js$\": \"babel-jest\"\n    },\n    \"setupFiles\": [\n      \"./internals/scripts/CheckBuiltsExist.js\"\n    ]\n  },\n  \"devDependencies\": {\n    \"babel-core\": \"^6.26.0\",\n    \"babel-eslint\": \"^8.2.1\",\n    \"babel-jest\": \"^22.1.0\",\n    \"babel-loader\": \"^7.1.2\",\n    \"babel-plugin-add-module-exports\": \"^0.2.1\",\n    \"babel-plugin-dev-expression\": \"^0.2.1\",\n    \"babel-plugin-flow-runtime\": \"^0.15.0\",\n    \"babel-plugin-import\": \"^1.7.0\",\n    \"babel-plugin-transform-class-properties\": \"^6.24.1\",\n    \"babel-plugin-transform-es2015-classes\": \"^6.24.1\",\n    \"babel-preset-env\": \"^1.6.1\",\n    \"babel-preset-react\": \"^6.24.1\",\n    \"babel-preset-react-hmre\": \"^1.1.1\",\n    \"babel-preset-react-optimize\": \"^1.0.1\",\n    \"babel-preset-stage-0\": \"^6.24.1\",\n    \"babel-register\": \"^6.26.0\",\n    \"chalk\": \"^2.3.0\",\n    \"concurrently\": \"^3.5.1\",\n    \"cross-env\": \"^5.1.3\",\n    \"cross-spawn\": \"^6.0.4\",\n    \"css-loader\": \"^0.28.9\",\n    \"detect-port\": \"^1.2.2\",\n    \"electron\": \"^1.7.11\",\n    \"electron-builder\": \"^19.55.3\",\n    \"electron-devtools-installer\": \"^2.2.3\",\n    \"electron-rebuild\": \"^1.7.3\",\n    \"enzyme\": \"^3.3.0\",\n    \"enzyme-adapter-react-16\": \"^1.1.1\",\n    \"enzyme-to-json\": \"^3.3.1\",\n    \"eslint\": \"^4.16.0\",\n    \"eslint-config-airbnb\": \"^16.1.0\",\n    \"eslint-formatter-pretty\": \"^1.3.0\",\n    \"eslint-import-resolver-webpack\": \"^0.8.4\",\n    \"eslint-plugin-compat\": \"^2.2.0\",\n    \"eslint-plugin-flowtype\": \"^2.42.0\",\n    \"eslint-plugin-import\": \"^2.8.0\",\n    \"eslint-plugin-jest\": \"^21.7.0\",\n    \"eslint-plugin-jsx-a11y\": \"6.0.3\",\n    \"eslint-plugin-promise\": \"^3.6.0\",\n    \"eslint-plugin-react\": \"^7.6.1\",\n    \"express\": \"^4.16.2\",\n    \"extract-text-webpack-plugin\": \"^3.0.2\",\n    \"fbjs-scripts\": \"^0.8.1\",\n    \"file-loader\": \"^1.1.6\",\n    \"flow-bin\": \"^0.64.0\",\n    \"flow-runtime\": \"^0.16.0\",\n    \"flow-typed\": \"^2.3.0\",\n    \"identity-obj-proxy\": \"^3.0.0\",\n    \"jest\": \"^22.1.4\",\n    \"less\": \"^3.0.1\",\n    \"less-loader\": \"^4.1.0\",\n    \"minimist\": \"^1.2.0\",\n    \"node-sass\": \"^4.7.2\",\n    \"npm-logical-tree\": \"^1.2.1\",\n    \"react-test-renderer\": \"^16.2.0\",\n    \"redux-logger\": \"^3.0.6\",\n    \"rimraf\": \"^2.6.2\",\n    \"sass-loader\": \"^6.0.6\",\n    \"sinon\": \"^4.2.2\",\n    \"spectron\": \"^3.8.0\",\n    \"style-loader\": \"^0.20.1\",\n    \"stylefmt\": \"^6.0.0\",\n    \"stylelint\": \"^8.4.0\",\n    \"stylelint-config-standard\": \"^18.0.0\",\n    \"uglifyjs-webpack-plugin\": \"1.1.8\",\n    \"url-loader\": \"^0.6.2\",\n    \"webpack\": \"^3.10.0\",\n    \"webpack-bundle-analyzer\": \"^2.9.2\",\n    \"webpack-dev-server\": \"^2.11.1\",\n    \"webpack-merge\": \"^4.1.1\"\n  },\n  \"dependencies\": {\n    \"axios\": \"^0.18.0\",\n    \"devtron\": \"^1.4.0\",\n    \"electron-debug\": \"^1.5.0\",\n    \"font-awesome\": \"^4.7.0\",\n    \"history\": \"^4.7.2\",\n    \"rc-menu\": \"^6.2.10\",\n    \"rc-select\": \"^7.7.7\",\n    \"react\": \"^16.2.0\",\n    \"react-dom\": \"^16.2.0\",\n    \"react-hot-loader\": \"^4.0.0-beta.13\",\n    \"react-redux\": \"^5.0.6\",\n    \"react-router\": \"^4.2.0\",\n    \"react-router-dom\": \"^4.2.2\",\n    \"react-router-redux\": \"^5.0.0-alpha.6\",\n    \"redux\": \"^3.7.2\",\n    \"redux-thunk\": \"^2.2.0\",\n    \"source-map-support\": \"^0.5.3\"\n  },\n  \"devEngines\": {\n    \"node\": \">=7.x\",\n    \"npm\": \">=4.x\",\n    \"yarn\": \">=0.21.3\"\n  }\n}\n"
  },
  {
    "path": "resources/viplist.json",
    "content": "{\n  \"platformlist\": [\n    {\n      \"name\": \"爱奇艺\",\n      \"url\": \"http://www.iqiyi.com/\"\n    },\n    {\n      \"name\": \"腾讯视频\",\n      \"url\": \"https://v.qq.com/\"\n    },\n    {\n      \"name\": \"芒果\",\n      \"url\": \"https://www.mgtv.com/\"\n    },\n    {\n      \"name\": \"优酷\",\n      \"url\": \"https://www.youku.com/\"\n    },\n    {\n      \"name\": \"搜狐视频\",\n      \"url\": \"https://tv.sohu.com/\"\n    },\n    {\n      \"name\": \"乐视视频\",\n      \"url\": \"https://www.le.com/\"\n    },\n    {\n      \"name\": \"电影天堂\",\n      \"url\": \"http://www.btbtdy.net/\"\n    },\n    {\n      \"name\": \"新视觉影院\",\n      \"url\": \"http://www.yy3080.com/vod-type-id-1-pg-1.html/\"\n    }\n  ],\n  \"list\": [\n    {\n      \"name\": \"5月-21\",\n      \"url\": \"http://jiexi.071811.cc/jx2.php?url=\"\n    },\n    {\n      \"name\": \"9月-2\",\n      \"url\": \"http://jqaaa.com/jx.php?url=\"\n    },\n    {\n      \"name\": \"5月-4\",\n      \"url\": \"http://beaacc.com/api.php?url=\"\n    },\n    {\n      \"name\": \"4.21-4\",\n      \"url\": \"http://www.82190555.com/index.php?url=\"\n    },\n    {\n      \"name\": \"4.21-6\",\n      \"url\": \"http://www.85105052.com/admin.php?url=\"\n    },\n    {\n      \"name\": \"5月-23\",\n      \"url\": \"http://api.baiyug.cn/vip/index.php?url=\"\n    },\n    {\n      \"name\": \"4.21-3-慢\",\n      \"url\": \"https://yooomm.com/index.php?url=\"\n    },\n    {\n      \"name\": \"5月-24\",\n      \"url\": \"http://www.82190555.com/index/qqvod.php?url=\"\n    },\n    {\n      \"name\": \"1\",\n      \"url\": \"http://17kyun.com/api.php?url=\"\n    },\n    {\n      \"name\": \"品优解析-可播但广告\",\n      \"url\": \"http://api.pucms.com/xnflv/?url=\"\n    },\n    {\n      \"name\": \"5月-1\",\n      \"url\": \"http://www.82190555.com/index/qqvod.php?url=\"\n    },\n    {\n      \"name\": \"腾讯可用，金桥解析\",\n      \"url\": \"http://jqaaa.com/jx.php?url=\"\n    },\n    {\n      \"name\": \"速度牛\",\n      \"url\": \"http://api.wlzhan.com/sudu/?url=\"\n    },\n    {\n      \"name\": \"万能接口6\",\n      \"url\": \"http://wwwhe1.177kdy.cn/4.php?pass=1&url=\"\n    },\n    {\n      \"name\": \"花园影视（可能无效）\",\n      \"url\": \"http://j.zz22x.com/jx/?url=\"\n    },\n    {\n      \"name\": \"9月-1\",\n      \"url\": \"http://api.ledboke.com/?url=\"\n    }\n  ]\n}\n"
  },
  {
    "path": "test/.eslintrc",
    "content": "{\n  \"env\": {\n    \"jest/globals\": true\n  },\n  \"plugins\": [\n    \"jest\"\n  ],\n  \"rules\": {\n    \"jest/no-disabled-tests\": \"warn\",\n    \"jest/no-focused-tests\": \"error\",\n    \"jest/no-identical-title\": \"error\"\n  }\n}\n"
  },
  {
    "path": "test/actions/__snapshots__/counter.spec.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`actions should decrement should create decrement action 1`] = `\nObject {\n  \"type\": \"DECREMENT_COUNTER\",\n}\n`;\n\nexports[`actions should increment should create increment action 1`] = `\nObject {\n  \"type\": \"INCREMENT_COUNTER\",\n}\n`;\n"
  },
  {
    "path": "test/actions/counter.spec.js",
    "content": "import { spy } from 'sinon';\nimport * as actions from '../../app/actions/counter';\n\ndescribe('actions', () => {\n  it('should increment should create increment action', () => {\n    expect(actions.increment()).toMatchSnapshot();\n  });\n\n  it('should decrement should create decrement action', () => {\n    expect(actions.decrement()).toMatchSnapshot();\n  });\n\n  it('should incrementIfOdd should create increment action', () => {\n    const fn = actions.incrementIfOdd();\n    expect(fn).toBeInstanceOf(Function);\n    const dispatch = spy();\n    const getState = () => ({ counter: 1 });\n    fn(dispatch, getState);\n    expect(dispatch.calledWith({ type: actions.INCREMENT_COUNTER })).toBe(true);\n  });\n\n  it('should incrementIfOdd shouldnt create increment action if counter is even', () => {\n    const fn = actions.incrementIfOdd();\n    const dispatch = spy();\n    const getState = () => ({ counter: 2 });\n    fn(dispatch, getState);\n    expect(dispatch.called).toBe(false);\n  });\n\n  // There's no nice way to test this at the moment...\n  it('should incrementAsync', done => {\n    const fn = actions.incrementAsync(1);\n    expect(fn).toBeInstanceOf(Function);\n    const dispatch = spy();\n    fn(dispatch);\n    setTimeout(() => {\n      expect(dispatch.calledWith({ type: actions.INCREMENT_COUNTER })).toBe(true);\n      done();\n    }, 5);\n  });\n});\n"
  },
  {
    "path": "test/components/Counter.spec.js",
    "content": "import { spy } from 'sinon';\nimport React from 'react';\nimport Enzyme, { shallow } from 'enzyme';\nimport Adapter from 'enzyme-adapter-react-16';\nimport { BrowserRouter as Router } from 'react-router-dom';\nimport renderer from 'react-test-renderer';\nimport Counter from '../../app/components/Counter';\n\nEnzyme.configure({ adapter: new Adapter() });\n\nfunction setup() {\n  const actions = {\n    increment: spy(),\n    incrementIfOdd: spy(),\n    incrementAsync: spy(),\n    decrement: spy()\n  };\n  const component = shallow(<Counter counter={1} {...actions} />);\n  return {\n    component,\n    actions,\n    buttons: component.find('button'),\n    p: component.find('.counter')\n  };\n}\n\ndescribe('Counter component', () => {\n  it('should should display count', () => {\n    const { p } = setup();\n    expect(p.text()).toMatch(/^1$/);\n  });\n\n  it('should first button should call increment', () => {\n    const { buttons, actions } = setup();\n    buttons.at(0).simulate('click');\n    expect(actions.increment.called).toBe(true);\n  });\n\n  it('should match exact snapshot', () => {\n    const { actions } = setup();\n    const counter = (\n      <div>\n        <Router>\n          <Counter counter={1} {...actions} />\n        </Router>\n      </div>\n    );\n    const tree = renderer\n      .create(counter)\n      .toJSON();\n\n    expect(tree).toMatchSnapshot();\n  });\n\n  it('should second button should call decrement', () => {\n    const { buttons, actions } = setup();\n    buttons.at(1).simulate('click');\n    expect(actions.decrement.called).toBe(true);\n  });\n\n  it('should third button should call incrementIfOdd', () => {\n    const { buttons, actions } = setup();\n    buttons.at(2).simulate('click');\n    expect(actions.incrementIfOdd.called).toBe(true);\n  });\n\n  it('should fourth button should call incrementAsync', () => {\n    const { buttons, actions } = setup();\n    buttons.at(3).simulate('click');\n    expect(actions.incrementAsync.called).toBe(true);\n  });\n});\n"
  },
  {
    "path": "test/components/__snapshots__/Counter.spec.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Counter component should match exact snapshot 1`] = `\n<div>\n  <div>\n    <div\n      className=\"backButton\"\n      data-tid=\"backButton\"\n    >\n      <a\n        href=\"/\"\n        onClick={[Function]}\n      >\n        <i\n          className=\"fa fa-arrow-left fa-3x\"\n        />\n      </a>\n    </div>\n    <div\n      className=\"counter counter\"\n      data-tid=\"counter\"\n    >\n      1\n    </div>\n    <div\n      className=\"btnGroup\"\n    >\n      <button\n        className=\"btn\"\n        data-tclass=\"btn\"\n        onClick={[Function]}\n      >\n        <i\n          className=\"fa fa-plus\"\n        />\n      </button>\n      <button\n        className=\"btn\"\n        data-tclass=\"btn\"\n        onClick={[Function]}\n      >\n        <i\n          className=\"fa fa-minus\"\n        />\n      </button>\n      <button\n        className=\"btn\"\n        data-tclass=\"btn\"\n        onClick={[Function]}\n      >\n        odd\n      </button>\n      <button\n        className=\"btn\"\n        data-tclass=\"btn\"\n        onClick={[Function]}\n      >\n        async\n      </button>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "test/containers/CounterPage.spec.js",
    "content": "import React from 'react';\nimport Enzyme, { mount } from 'enzyme';\nimport Adapter from 'enzyme-adapter-react-16';\nimport { Provider } from 'react-redux';\nimport { createBrowserHistory } from 'history';\nimport { ConnectedRouter } from 'react-router-redux';\nimport CounterPage from '../../app/containers/CounterPage';\nimport { configureStore } from '../../app/store/configureStore';\n\nEnzyme.configure({ adapter: new Adapter() });\n\nfunction setup(initialState) {\n  const store = configureStore(initialState);\n  const history = createBrowserHistory();\n  const provider = (\n    <Provider store={store}>\n      <ConnectedRouter history={history}>\n        <CounterPage />\n      </ConnectedRouter>\n    </Provider>\n  );\n  const app = mount(provider);\n  return {\n    app,\n    buttons: app.find('button'),\n    p: app.find('.counter')\n  };\n}\n\ndescribe('containers', () => {\n  describe('App', () => {\n    it('should display initial count', () => {\n      const { p } = setup();\n      expect(p.text()).toMatch(/^0$/);\n    });\n\n    it('should display updated count after increment button click', () => {\n      const { buttons, p } = setup();\n      buttons.at(0).simulate('click');\n      expect(p.text()).toMatch(/^1$/);\n    });\n\n    it('should display updated count after descrement button click', () => {\n      const { buttons, p } = setup();\n      buttons.at(1).simulate('click');\n      expect(p.text()).toMatch(/^-1$/);\n    });\n\n    it('shouldnt change if even and if odd button clicked', () => {\n      const { buttons, p } = setup();\n      buttons.at(2).simulate('click');\n      expect(p.text()).toMatch(/^0$/);\n    });\n\n    it('should change if odd and if odd button clicked', () => {\n      const { buttons, p } = setup({ counter: 1 });\n      buttons.at(2).simulate('click');\n      expect(p.text()).toMatch(/^2$/);\n    });\n  });\n});\n"
  },
  {
    "path": "test/e2e/e2e.spec.js",
    "content": "import { Application } from 'spectron';\nimport electronPath from 'electron';\nimport path from 'path';\n\njasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;\n\nconst delay = time => new Promise(resolve => setTimeout(resolve, time));\n\ndescribe('main window', function spec() {\n  beforeAll(async () => {\n    this.app = new Application({\n      path: electronPath,\n      args: [path.join(__dirname, '..', '..', 'app')],\n    });\n\n    return this.app.start();\n  });\n\n  afterAll(() => {\n    if (this.app && this.app.isRunning()) {\n      return this.app.stop();\n    }\n  });\n\n  const findCounter = () => this.app.client.element('[data-tid=\"counter\"]');\n\n  const findButtons = async () => {\n    const { value } = await this.app.client.elements('[data-tclass=\"btn\"]');\n    return value.map(btn => btn.ELEMENT);\n  };\n\n  it('should open window', async () => {\n    const { client, browserWindow } = this.app;\n\n    await client.waitUntilWindowLoaded();\n    await delay(500);\n    const title = await browserWindow.getTitle();\n    expect(title).toBe('Hello Electron React!');\n  });\n\n  it('should haven\\'t any logs in console of main window', async () => {\n    const { client } = this.app;\n    const logs = await client.getRenderProcessLogs();\n    // Print renderer process logs\n    logs.forEach(log => {\n      console.log(log.message);\n      console.log(log.source);\n      console.log(log.level);\n    });\n    expect(logs).toHaveLength(0);\n  });\n\n  it('should to Counter with click \"to Counter\" link', async () => {\n    const { client } = this.app;\n\n    await client.click('[data-tid=container] > a');\n    expect(await findCounter().getText()).toBe('0');\n  });\n\n  it('should display updated count after increment button click', async () => {\n    const { client } = this.app;\n\n    const buttons = await findButtons();\n    await client.elementIdClick(buttons[0]); // +\n    expect(await findCounter().getText()).toBe('1');\n  });\n\n  it('should display updated count after descrement button click', async () => {\n    const { client } = this.app;\n\n    const buttons = await findButtons();\n    await client.elementIdClick(buttons[1]); // -\n    expect(await findCounter().getText()).toBe('0');\n  });\n\n  it('shouldnt change if even and if odd button clicked', async () => {\n    const { client } = this.app;\n\n    const buttons = await findButtons();\n    await client.elementIdClick(buttons[2]); // odd\n    expect(await findCounter().getText()).toBe('0');\n  });\n\n  it('should change if odd and if odd button clicked', async () => {\n    const { client } = this.app;\n\n    const buttons = await findButtons();\n    await client.elementIdClick(buttons[0]); // +\n    await client.elementIdClick(buttons[2]); // odd\n    expect(await findCounter().getText()).toBe('2');\n  });\n\n  it('should change if async button clicked and a second later', async () => {\n    const { client } = this.app;\n\n    const buttons = await findButtons();\n    await client.elementIdClick(buttons[3]); // async\n    expect(await findCounter().getText()).toBe('2');\n    await delay(1500);\n    expect(await findCounter().getText()).toBe('3');\n  });\n\n  it('should back to home if back button clicked', async () => {\n    const { client } = this.app;\n    await client.element('[data-tid=\"backButton\"] > a').click();\n\n    expect(await client.isExisting('[data-tid=\"container\"]')).toBe(true);\n  });\n});\n"
  },
  {
    "path": "test/example.js",
    "content": "describe('description', () => {\n  it('should have description', () => {\n    expect(1 + 2).toBe(3);\n  });\n});\n"
  },
  {
    "path": "test/reducers/__snapshots__/counter.spec.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`reducers counter should handle DECREMENT_COUNTER 1`] = `0`;\n\nexports[`reducers counter should handle INCREMENT_COUNTER 1`] = `2`;\n\nexports[`reducers counter should handle initial state 1`] = `0`;\n\nexports[`reducers counter should handle unknown action type 1`] = `1`;\n"
  },
  {
    "path": "test/reducers/counter.spec.js",
    "content": "import counter from '../../app/reducers/counter';\nimport { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../../app/actions/counter';\n\ndescribe('reducers', () => {\n  describe('counter', () => {\n    it('should handle initial state', () => {\n      expect(counter(undefined, {})).toMatchSnapshot();\n    });\n\n    it('should handle INCREMENT_COUNTER', () => {\n      expect(counter(1, { type: INCREMENT_COUNTER })).toMatchSnapshot();\n    });\n\n    it('should handle DECREMENT_COUNTER', () => {\n      expect(counter(1, { type: DECREMENT_COUNTER })).toMatchSnapshot();\n    });\n\n    it('should handle unknown action type', () => {\n      expect(counter(1, { type: 'unknown' })).toMatchSnapshot();\n    });\n  });\n});\n"
  },
  {
    "path": "webpack.config.base.js",
    "content": "/**\n * Base webpack config used across other specific configs\n */\n\nimport path from 'path';\nimport webpack from 'webpack';\nimport { dependencies as externals } from './app/package.json';\n\nexport default {\n  externals: Object.keys(externals || {}),\n\n  module: {\n    rules: [{\n      test: /\\.jsx?$/,\n      exclude: /node_modules/,\n      use: {\n        loader: 'babel-loader',\n        options: {\n          cacheDirectory: true\n        }\n      }\n    }]\n  },\n\n  output: {\n    path: path.join(__dirname, 'app'),\n    // https://github.com/webpack/webpack/issues/1114\n    libraryTarget: 'commonjs2'\n  },\n\n  /**\n   * Determine the array of extensions that should be used to resolve modules.\n   */\n  resolve: {\n    extensions: ['.js', '.jsx', '.json'],\n    modules: [\n      path.join(__dirname, 'app'),\n      'node_modules',\n    ],\n  },\n\n  plugins: [\n    new webpack.EnvironmentPlugin({\n      NODE_ENV: 'production'\n    }),\n\n    new webpack.NamedModulesPlugin(),\n  ],\n};\n"
  },
  {
    "path": "webpack.config.eslint.js",
    "content": "require('babel-register');\n\nmodule.exports = require('./webpack.config.renderer.dev');\n"
  },
  {
    "path": "webpack.config.main.prod.js",
    "content": "/**\n * Webpack config for production electron main process\n */\n\nimport webpack from 'webpack';\nimport merge from 'webpack-merge';\nimport UglifyJSPlugin from 'uglifyjs-webpack-plugin';\nimport { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';\nimport baseConfig from './webpack.config.base';\nimport CheckNodeEnv from './internals/scripts/CheckNodeEnv';\n\nCheckNodeEnv('production');\n\nexport default merge.smart(baseConfig, {\n  devtool: 'source-map',\n\n  target: 'electron-main',\n\n  entry: './app/main.dev',\n\n  output: {\n    path: __dirname,\n    filename: './app/main.prod.js'\n  },\n\n  plugins: [\n    new UglifyJSPlugin({\n      parallel: true,\n      sourceMap: true\n    }),\n\n    new BundleAnalyzerPlugin({\n      analyzerMode: process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',\n      openAnalyzer: process.env.OPEN_ANALYZER === 'true'\n    }),\n\n    /**\n     * Create global constants which can be configured at compile time.\n     *\n     * Useful for allowing different behaviour between development builds and\n     * release builds\n     *\n     * NODE_ENV should be production so that modules do not perform certain\n     * development checks\n     */\n    new webpack.EnvironmentPlugin({\n      NODE_ENV: 'production',\n      DEBUG_PROD: 'false'\n    })\n  ],\n\n  /**\n   * Disables webpack processing of __dirname and __filename.\n   * If you run the bundle in node.js it falls back to these values of node.js.\n   * https://github.com/webpack/webpack/issues/2010\n   */\n  node: {\n    __dirname: false,\n    __filename: false\n  },\n});\n"
  },
  {
    "path": "webpack.config.renderer.dev.dll.js",
    "content": "/**\n * Builds the DLL for development electron renderer process\n */\n\nimport webpack from 'webpack';\nimport path from 'path';\nimport merge from 'webpack-merge';\nimport baseConfig from './webpack.config.base';\nimport { dependencies } from './package.json';\nimport CheckNodeEnv from './internals/scripts/CheckNodeEnv';\n\nCheckNodeEnv('development');\n\nconst dist = path.resolve(process.cwd(), 'dll');\n\nexport default merge.smart(baseConfig, {\n  context: process.cwd(),\n\n  devtool: 'eval',\n\n  target: 'electron-renderer',\n\n  externals: ['fsevents', 'crypto-browserify'],\n\n  /**\n   * Use `module` from `webpack.config.renderer.dev.js`\n   */\n  module: {\n    rules: [\n      {\n        test: /\\.jsx?$/,\n        exclude: /node_modules/,\n        use: {\n          loader: 'babel-loader',\n          options: {\n            cacheDirectory: true,\n            plugins: [\n              // Here, we include babel plugins that are only required for the\n              // renderer process. The 'transform-*' plugins must be included\n              // before react-hot-loader/babel\n              'transform-class-properties',\n              'transform-es2015-classes',\n              'react-hot-loader/babel'\n            ],\n          }\n        }\n      },\n      {\n        test: /\\.global\\.css$/,\n        use: [\n          {\n            loader: 'style-loader'\n          },\n          {\n            loader: 'css-loader',\n            options: {\n              sourceMap: true,\n            },\n          }\n        ]\n      },\n      {\n        test: /^((?!\\.global).)*\\.css$/,\n        use: [\n          {\n            loader: 'style-loader'\n          },\n          {\n            loader: 'css-loader',\n            options: {\n              modules: true,\n              sourceMap: true,\n              importLoaders: 1,\n              localIdentName: '[name]__[local]__[hash:base64:5]',\n            }\n          },\n        ]\n      },\n      // SASS support - compile all .global.scss files and pipe it to style.css\n      {\n        test: /\\.global\\.(scss|sass)$/,\n        use: [\n          {\n            loader: 'style-loader'\n          },\n          {\n            loader: 'css-loader',\n            options: {\n              sourceMap: true,\n            },\n          },\n          {\n            loader: 'sass-loader'\n          }\n        ]\n      },\n      // SASS support - compile all other .scss files and pipe it to style.css\n      {\n        test: /^((?!\\.global).)*\\.(scss|sass)$/,\n        use: [\n          {\n            loader: 'style-loader'\n          },\n          {\n            loader: 'css-loader',\n            options: {\n              modules: true,\n              sourceMap: true,\n              importLoaders: 1,\n              localIdentName: '[name]__[local]__[hash:base64:5]',\n            }\n          },\n          {\n            loader: 'sass-loader'\n          }\n        ]\n      },\n      // WOFF Font\n      {\n        test: /\\.woff(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'application/font-woff',\n          }\n        },\n      },\n      // WOFF2 Font\n      {\n        test: /\\.woff2(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'application/font-woff',\n          }\n        }\n      },\n      // TTF Font\n      {\n        test: /\\.ttf(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'application/octet-stream'\n          }\n        }\n      },\n      // EOT Font\n      {\n        test: /\\.eot(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: 'file-loader',\n      },\n      // SVG Font\n      {\n        test: /\\.svg(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'image/svg+xml',\n          }\n        }\n      },\n      // Common Image Formats\n      {\n        test: /\\.(?:ico|gif|png|jpg|jpeg|webp)$/,\n        use: 'url-loader',\n      }\n    ]\n  },\n\n  entry: {\n    renderer: (\n      Object\n        .keys(dependencies || {})\n        .filter(dependency => dependency !== 'font-awesome')\n    )\n  },\n\n  output: {\n    library: 'renderer',\n    path: dist,\n    filename: '[name].dev.dll.js',\n    libraryTarget: 'var'\n  },\n\n  plugins: [\n    new webpack.DllPlugin({\n      path: path.join(dist, '[name].json'),\n      name: '[name]',\n    }),\n\n    /**\n     * Create global constants which can be configured at compile time.\n     *\n     * Useful for allowing different behaviour between development builds and\n     * release builds\n     *\n     * NODE_ENV should be production so that modules do not perform certain\n     * development checks\n     */\n    new webpack.EnvironmentPlugin({\n      NODE_ENV: 'development'\n    }),\n\n    new webpack.LoaderOptionsPlugin({\n      debug: true,\n      options: {\n        context: path.resolve(process.cwd(), 'app'),\n        output: {\n          path: path.resolve(process.cwd(), 'dll'),\n        },\n      },\n    })\n  ],\n});\n"
  },
  {
    "path": "webpack.config.renderer.dev.js",
    "content": "/* eslint global-require: 0, import/no-dynamic-require: 0 */\n\n/**\n * Build config for development electron renderer process that uses\n * Hot-Module-Replacement\n *\n * https://webpack.js.org/concepts/hot-module-replacement/\n */\n\nimport path from 'path';\nimport fs from 'fs';\nimport webpack from 'webpack';\nimport chalk from 'chalk';\nimport merge from 'webpack-merge';\nimport { spawn, execSync } from 'child_process';\nimport ExtractTextPlugin from 'extract-text-webpack-plugin';\nimport baseConfig from './webpack.config.base';\nimport CheckNodeEnv from './internals/scripts/CheckNodeEnv';\n\nCheckNodeEnv('development');\n\nconst port = process.env.PORT || 1212;\nconst publicPath = `http://localhost:${port}/dist`;\nconst dll = path.resolve(process.cwd(), 'dll');\nconst manifest = path.resolve(dll, 'renderer.json');\n\n/**\n * Warn if the DLL is not built\n */\nif (!(fs.existsSync(dll) && fs.existsSync(manifest))) {\n  console.log(chalk.black.bgYellow.bold('The DLL files are missing. Sit back while we build them for you with \"npm run build-dll\"'));\n  execSync('npm run build-dll');\n}\n\nexport default merge.smart(baseConfig, {\n  devtool: 'inline-source-map',\n\n  target: 'electron-renderer',\n\n  entry: [\n    'react-hot-loader/patch',\n    `webpack-dev-server/client?http://localhost:${port}/`,\n    'webpack/hot/only-dev-server',\n    path.join(__dirname, 'app/index.js'),\n  ],\n\n  output: {\n    publicPath: `http://localhost:${port}/dist/`,\n    filename: 'renderer.dev.js'\n  },\n\n  module: {\n    rules: [\n      {\n        test: /\\.jsx?$/,\n        exclude: /node_modules/,\n        use: {\n          loader: 'babel-loader',\n          options: {\n            cacheDirectory: true,\n            plugins: [\n              // Here, we include babel plugins that are only required for the\n              // renderer process. The 'transform-*' plugins must be included\n              // before react-hot-loader/babel\n              'transform-class-properties',\n              'transform-es2015-classes',\n              'react-hot-loader/babel'\n            ],\n          }\n        }\n      },\n      {\n        test: /\\.less$/,\n        loader: `style!css!less`,\n        include: path.resolve(__dirname, 'node_modules'),\n      },\n      {\n        test: /\\.global\\.css$/,\n        use: [\n          {\n            loader: 'style-loader'\n          },\n          {\n            loader: 'css-loader',\n            options: {\n              sourceMap: true,\n            },\n          }\n        ]\n      },\n      {\n        test: /^((?!\\.global).)*\\.css$/,\n        use: [\n          {\n            loader: 'style-loader'\n          },\n          {\n            loader: 'css-loader',\n            options: {\n              modules: true,\n              sourceMap: true,\n              importLoaders: 1,\n              localIdentName: '[name]__[local]__[hash:base64:5]',\n            }\n          },\n        ]\n      },\n      // SASS support - compile all .global.scss files and pipe it to style.css\n      {\n        test: /\\.global\\.(scss|sass)$/,\n        use: [\n          {\n            loader: 'style-loader'\n          },\n          {\n            loader: 'css-loader',\n            options: {\n              sourceMap: true,\n            },\n          },\n          {\n            loader: 'sass-loader'\n          }\n        ]\n      },\n      // SASS support - compile all other .scss files and pipe it to style.css\n      {\n        test: /^((?!\\.global).)*\\.(scss|sass)$/,\n        use: [\n          {\n            loader: 'style-loader'\n          },\n          {\n            loader: 'css-loader',\n            options: {\n              modules: true,\n              sourceMap: true,\n              importLoaders: 1,\n              localIdentName: '[name]__[local]__[hash:base64:5]',\n            }\n          },\n          {\n            loader: 'sass-loader'\n          }\n        ]\n      },\n      // WOFF Font\n      {\n        test: /\\.woff(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'application/font-woff',\n          }\n        },\n      },\n      // WOFF2 Font\n      {\n        test: /\\.woff2(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'application/font-woff',\n          }\n        }\n      },\n      // TTF Font\n      {\n        test: /\\.ttf(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'application/octet-stream'\n          }\n        }\n      },\n      // EOT Font\n      {\n        test: /\\.eot(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: 'file-loader',\n      },\n      // SVG Font\n      {\n        test: /\\.svg(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'image/svg+xml',\n          }\n        }\n      },\n      // Common Image Formats\n      {\n        test: /\\.(?:ico|gif|png|jpg|jpeg|webp)$/,\n        use: 'url-loader',\n      }\n    ]\n  },\n\n  plugins: [\n    new webpack.DllReferencePlugin({\n      context: process.cwd(),\n      manifest: require(manifest),\n      sourceType: 'var',\n    }),\n\n    new webpack.HotModuleReplacementPlugin({\n      multiStep: true\n    }),\n\n    new webpack.NoEmitOnErrorsPlugin(),\n\n    /**\n     * Create global constants which can be configured at compile time.\n     *\n     * Useful for allowing different behaviour between development builds and\n     * release builds\n     *\n     * NODE_ENV should be production so that modules do not perform certain\n     * development checks\n     *\n     * By default, use 'development' as NODE_ENV. This can be overriden with\n     * 'staging', for example, by changing the ENV variables in the npm scripts\n     */\n    new webpack.EnvironmentPlugin({\n      NODE_ENV: 'development'\n    }),\n\n    new webpack.LoaderOptionsPlugin({\n      debug: true\n    }),\n\n    new ExtractTextPlugin({\n      filename: '[name].css'\n    }),\n  ],\n\n  node: {\n    __dirname: false,\n    __filename: false\n  },\n\n  devServer: {\n    port,\n    publicPath,\n    compress: true,\n    noInfo: true,\n    stats: 'errors-only',\n    inline: true,\n    lazy: false,\n    hot: true,\n    headers: { 'Access-Control-Allow-Origin': '*' },\n    contentBase: path.join(__dirname, 'dist'),\n    watchOptions: {\n      aggregateTimeout: 300,\n      ignored: /node_modules/,\n      poll: 100\n    },\n    historyApiFallback: {\n      verbose: true,\n      disableDotRule: false,\n    },\n    before() {\n      if (process.env.START_HOT) {\n        console.log('Starting Main Process...');\n        spawn(\n          'npm',\n          ['run', 'start-main-dev'],\n          { shell: true, env: process.env, stdio: 'inherit' }\n        )\n          .on('close', code => process.exit(code))\n          .on('error', spawnError => console.error(spawnError));\n      }\n    }\n  },\n});\n"
  },
  {
    "path": "webpack.config.renderer.prod.js",
    "content": "/**\n * Build config for electron renderer process\n */\n\nimport path from 'path';\nimport webpack from 'webpack';\nimport ExtractTextPlugin from 'extract-text-webpack-plugin';\nimport { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';\nimport merge from 'webpack-merge';\nimport UglifyJSPlugin from 'uglifyjs-webpack-plugin';\nimport baseConfig from './webpack.config.base';\nimport CheckNodeEnv from './internals/scripts/CheckNodeEnv';\n\nCheckNodeEnv('production');\n\nexport default merge.smart(baseConfig, {\n  devtool: 'source-map',\n\n  target: 'electron-renderer',\n\n  entry: './app/index',\n\n  output: {\n    path: path.join(__dirname, 'app/dist'),\n    publicPath: './dist/',\n    filename: 'renderer.prod.js'\n  },\n\n  module: {\n    rules: [\n      // Extract all .global.css to style.css as is\n      {\n        test: /\\.global\\.css$/,\n        use: ExtractTextPlugin.extract({\n          publicPath: './',\n          use: {\n            loader: 'css-loader',\n            options: {\n              minimize: true,\n            }\n          },\n          fallback: 'style-loader',\n        })\n      },\n      // Pipe other styles through css modules and append to style.css\n      {\n        test: /^((?!\\.global).)*\\.css$/,\n        use: ExtractTextPlugin.extract({\n          use: {\n            loader: 'css-loader',\n            options: {\n              modules: true,\n              minimize: true,\n              importLoaders: 1,\n              localIdentName: '[name]__[local]__[hash:base64:5]',\n            }\n          }\n        }),\n      },\n      // Add SASS support  - compile all .global.scss files and pipe it to style.css\n      {\n        test: /\\.global\\.(scss|sass)$/,\n        use: ExtractTextPlugin.extract({\n          use: [\n            {\n              loader: 'css-loader',\n              options: {\n                minimize: true,\n              }\n            },\n            {\n              loader: 'sass-loader'\n            }\n          ],\n          fallback: 'style-loader',\n        })\n      },\n      // Add SASS support  - compile all other .scss files and pipe it to style.css\n      {\n        test: /^((?!\\.global).)*\\.(scss|sass)$/,\n        use: ExtractTextPlugin.extract({\n          use: [{\n            loader: 'css-loader',\n            options: {\n              modules: true,\n              minimize: true,\n              importLoaders: 1,\n              localIdentName: '[name]__[local]__[hash:base64:5]',\n            }\n          },\n          {\n            loader: 'sass-loader'\n          }]\n        }),\n      },\n      // WOFF Font\n      {\n        test: /\\.woff(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'application/font-woff',\n          }\n        },\n      },\n      // WOFF2 Font\n      {\n        test: /\\.woff2(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'application/font-woff',\n          }\n        }\n      },\n      // TTF Font\n      {\n        test: /\\.ttf(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'application/octet-stream'\n          }\n        }\n      },\n      // EOT Font\n      {\n        test: /\\.eot(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: 'file-loader',\n      },\n      // SVG Font\n      {\n        test: /\\.svg(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 10000,\n            mimetype: 'image/svg+xml',\n          }\n        }\n      },\n      // Common Image Formats\n      {\n        test: /\\.(?:ico|gif|png|jpg|jpeg|webp)$/,\n        use: 'url-loader',\n      }\n    ]\n  },\n\n  plugins: [\n    /**\n     * Create global constants which can be configured at compile time.\n     *\n     * Useful for allowing different behaviour between development builds and\n     * release builds\n     *\n     * NODE_ENV should be production so that modules do not perform certain\n     * development checks\n     */\n    new webpack.EnvironmentPlugin({\n      NODE_ENV: 'production'\n    }),\n\n    new UglifyJSPlugin({\n      parallel: true,\n      sourceMap: true\n    }),\n\n    new ExtractTextPlugin('style.css'),\n\n    new BundleAnalyzerPlugin({\n      analyzerMode: process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',\n      openAnalyzer: process.env.OPEN_ANALYZER === 'true'\n    }),\n  ],\n});\n"
  }
]