[
  {
    "path": ".babelrc",
    "content": "{\n  \"sourceMaps\": true,\n  \"presets\": [\n    \"es2015-minimal\",\n    \"stage-0\"\n  ],\n  \"plugins\": [\n    \"add-module-exports\",\n    [\"transform-decorators-legacy\"],\n    [\"transform-react-jsx\", { \"pragma\": \"h\" }]\n  ]\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = tab\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[{package.json,*.yml}]\nindent_style = space\nindent_size = 2\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n/npm-debug.log\n/dist\n/build\n/app\n/design\n/.env\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) Jason Miller <jason@developit.ca> (http://jasonformat.com)\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\nall copies 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\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Dropfox\n\nA dropbox client powered by [Preact], [Electron] and [Photon].\n\n> ### [Download Dropfox ➞](https://github.com/developit/dropfox/releases)\n\n<img src=\"http://i.imgur.com/fN1PmUN.png\" width=\"717\">\n\n\n> **Note:** building the app requires a Dropbox API Key, specified as `DROPBOX_API_KEY` env var.\n>\n> If you need a key, [generate one here](https://www.dropbox.com/developers/apps/).\n\n\n## Installation\n\n```sh\nnpm install\n```\n\n\n### Run for Development\n\nRuns a local copy of Electron (via electron-prebuilt), rendering the app with Live-Reload / [HMR] via [webpack-dev-server].\n\n> **Note:** you may need to reload _(Cmd/Ctrl + R)_ after the initial Webpack build completes.\n\n```sh\nnpm start\n```\n\n\n### Build\n\nTo build the app for OS X, Linux, and Windows, using [electron-packager]:\n\n```sh\nnpm run build\n```\n\n\n### Platform-Specific Builds\n\nYou can also build the codebase, and then package it only for a given platform:\n\n```sh\n# build the electron & web source:\nnpm run build:all\n\n# generate the package for your platform(s):\nnpm run build:electron:osx\nnpm run build:electron:linux\nnpm run build:electron:win\n```\n\n\n## License\n\nMIT © [Jason Miller](http://jasonformat.com)\n\n[webpack-dev-server]: https://webpack.github.io/docs/webpack-dev-server.html\n[HMR]: https://webpack.github.io/docs/hot-module-replacement.html\n[preact]: https://github.com/developit/preact\n[electron]: https://github.com/atom/electron\n[photon]: https://github.com/connors/photon\n[electron-packager]: https://github.com/maxogden/electron-packager\n"
  },
  {
    "path": "electron/backend.js",
    "content": "import fs from 'fs';\nimport request from 'request';\n\nexport function upload(path, url, callback) {\n\tfunction done(err) {\n\t\tif (callback) callback(err);\n\t\tcallback = null;\n\t}\n\n\tfs.createReadStream(path)\n\t\t.on('error', done)\n\t\t.pipe(\n\t\t\trequest.put(url, done)\n\t\t\t\t.on('error', done)\n\t\t\t\t.on('finish', () => done() )\n\t\t)\n\t\t.on('finish', () => done() );\n};\n"
  },
  {
    "path": "electron/index.js",
    "content": "import { parse as parseUrl } from 'url';\nimport app from 'app';\nimport BrowserWindow from 'browser-window';\nimport menu from './menu';\n\nconst HOST = `localhost:${process.env.PORT || 19998}`;\n\nconst DEV = process.env.NODE_ENV==='development';\n\n// adds debug features like hotkeys for triggering dev tools and reload\nif (DEV) {\n\ttry { require('electron-debug')(); }catch(err){}\n}\n\n// prevent window being garbage collected\nlet mainWindow;\n\napp.on('ready', () => {\n\tmainWindow = createMainWindow();\n\toverrideWindowOpen();\n});\n\nfunction createMainWindow() {\n\tconst win = new BrowserWindow({\n\t\twidth: DEV ? 1200 : 800,\n\t\theight: DEV ? 600 : 500,\n\t\tminWidth: 500,\n\t\tminHeight: 200,\n\t\twebgl: false,\n\t\tacceptFirstMouse: true,\n\t\ttitleBarStyle: 'hidden',\n\t\tshow: false\n\t});\n\n\tmenu(win);\n\n\twin.on('closed', () => {\n\t\tmainWindow = null;\n\t});\n\n\tif (DEV) {\n\t\twin.loadURL(`http://${HOST}/`);\n\t\twin.toggleDevTools();\n\t}\n\telse {\n\t\twin.loadURL(`file://${__dirname}/web/index.html`);\n\t}\n\n\tsetTimeout( () => win.show(), 150);\n\n\treturn win;\n}\n\nfunction overrideWindowOpen() {\n\tmainWindow.webContents.on('new-window', (e, url, name, disp, options) => {\n\t\tlet host = parseUrl(url).host;\n\t\tif (host && host!==HOST) {\n\t\t\tObject.assign(options, {\n\t\t\t\twidth: 400,\n\t\t\t\theight: 500,\n\t\t\t\tcenter: true,\n\t\t\t\tframe: true,\n\t\t\t\tresizable: false,\n\t\t\t\ttitle: `Loading ${host}...`,\n\t\t\t\ttitleBarStyle: 'visible',\n\t\t\t\talwaysOnTop: true,\n\t\t\t\tuseContentSize: true,\n\t\t\t\tskipTaskbar: true,\n\t\t\t\tnodeIntegration: false,\n\t\t\t\twebPreferences: {\n\t\t\t\t\t'web-security': true\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\telse {\n\t\t\tconsole.warn(`Allowing node integration for URL: ${url}`);\n\t\t}\n\t});\n}\n\n// quit if all windows are closed\napp.on('window-all-closed', () => app.quit() );\n\n/*\napp.on('window-all-closed', () => {\n\tif (process.platform !== 'darwin') {\n\t\tapp.quit();\n\t}\n});\n\napp.on('activate-with-no-open-windows', () => {\n\tif (!mainWindow) {\n\t\tmainWindow = createMainWindow();\n\t}\n});\n*/\n"
  },
  {
    "path": "electron/menu.js",
    "content": "import { openExternal } from 'shell';\nimport app from 'app';\nimport Menu from 'menu';\n\nconst MAC = process.platform==='darwin';\nconst DEV = process.env.NODE_ENV==='dev';\nconst MENU = [];\n\nexport default win => {\n\tMenu.setApplicationMenu(\n\t\tMenu.buildFromTemplate(\n\t\t\tfilterDev(MENU)\n\t\t)\n\t);\n};\n\nlet filterDev = menu => menu.filter( item => (DEV || item.devOnly!==true)).map( item => {\n\tlet d = Object.assign({}, item);\n\tif (d.submenu) d.submenu = filterDev(d.submenu);\n\treturn d;\n});\n\n\nMENU.push(\n\t{\n\t\tlabel: 'Edit',\n\t\tsubmenu: [\n\t\t\t{\n\t\t\t\tlabel: 'Undo',\n\t\t\t\taccelerator: 'CmdOrCtrl+Z',\n\t\t\t\trole: 'undo'\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Redo',\n\t\t\t\taccelerator: 'Shift+CmdOrCtrl+Z',\n\t\t\t\trole: 'redo'\n\t\t\t},\n\t\t\t{\n\t\t\t\ttype: 'separator'\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Cut',\n\t\t\t\taccelerator: 'CmdOrCtrl+X',\n\t\t\t\trole: 'cut'\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Copy',\n\t\t\t\taccelerator: 'CmdOrCtrl+C',\n\t\t\t\trole: 'copy'\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Paste',\n\t\t\t\taccelerator: 'CmdOrCtrl+V',\n\t\t\t\trole: 'paste'\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Select All',\n\t\t\t\taccelerator: 'CmdOrCtrl+A',\n\t\t\t\trole: 'selectall'\n\t\t\t},\n\t\t]\n\t},\n\t{\n\t\tlabel: 'View',\n\t\tsubmenu: [\n\t\t\t{\n\t\t\t\tlabel: 'Reload',\n\t\t\t\taccelerator: 'CmdOrCtrl+R',\n\t\t\t\tclick(item, focusedWindow) {\n\t\t\t\t\tif (focusedWindow)\n\t\t\t\t\t\tfocusedWindow.reload();\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Toggle Full Screen',\n\t\t\t\taccelerator: MAC ? 'Ctrl+Command+F' : 'F11',\n\t\t\t\tclick(item, focusedWindow) {\n\t\t\t\t\tif (focusedWindow)\n\t\t\t\t\t\tfocusedWindow.setFullScreen(!focusedWindow.isFullScreen());\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tdevOnly: true,\n\t\t\t\tlabel: 'Toggle Developer Tools',\n\t\t\t\taccelerator: MAC ? 'Alt+Command+I' : 'Ctrl+Shift+I',\n\t\t\t\tclick(item, focusedWindow) {\n\t\t\t\t\tif (focusedWindow)\n\t\t\t\t\t\tfocusedWindow.toggleDevTools();\n\t\t\t\t}\n\t\t\t},\n\t\t]\n\t},\n\t{\n\t\tlabel: 'Window',\n\t\trole: 'window',\n\t\tsubmenu: [\n\t\t\t{\n\t\t\t\tlabel: 'Minimize',\n\t\t\t\taccelerator: 'CmdOrCtrl+M',\n\t\t\t\trole: 'minimize'\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Close',\n\t\t\t\taccelerator: 'CmdOrCtrl+W',\n\t\t\t\trole: 'close'\n\t\t\t},\n\t\t]\n\t},\n\t{\n\t\tlabel: 'Help',\n\t\trole: 'help',\n\t\tsubmenu: [\n\t\t\t{\n\t\t\t\tlabel: 'Developer Website',\n\t\t\t\tclick: () => openExternal('http://jasonformat.com')\n\t\t\t},\n\t\t]\n\t}\n);\n\n\nif (MAC) {\n\tlet appName = app.getName();\n\tMENU.unshift({\n\t\tlabel: appName,\n\t\tsubmenu: [\n\t\t\t{\n\t\t\t\tlabel: `About ${appName}`,\n\t\t\t\trole: 'about'\n\t\t\t},\n\t\t\t{\n\t\t\t\ttype: 'separator'\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Services',\n\t\t\t\trole: 'services',\n\t\t\t\tsubmenu: []\n\t\t\t},\n\t\t\t{\n\t\t\t\ttype: 'separator'\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: `Hide ${appName}`,\n\t\t\t\taccelerator: 'Command+H',\n\t\t\t\trole: 'hide'\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Hide Others',\n\t\t\t\taccelerator: 'Command+Shift+H',\n\t\t\t\trole: 'hideothers'\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Show All',\n\t\t\t\trole: 'unhide'\n\t\t\t},\n\t\t\t{\n\t\t\t\ttype: 'separator'\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: 'Quit',\n\t\t\t\taccelerator: 'Command+Q',\n\t\t\t\tclick: () => app.quit()\n\t\t\t}\n\t\t]\n\t});\n\n\t// Window menu.\n\tMENU[3].submenu.push(\n\t\t{\n\t\t\ttype: 'separator'\n\t\t},\n\t\t{\n\t\t\tlabel: 'Bring All to Front',\n\t\t\trole: 'front'\n\t\t}\n\t);\n}\n"
  },
  {
    "path": "electron/package.json",
    "content": "{\n  \"name\": \"Dropfox\",\n  \"productName\": \"Dropfox\",\n  \"version\": \"1.2.0\",\n  \"description\": \"Dropbox client, powered by Preact & Electron.\",\n  \"main\": \"index.js\",\n  \"author\": {\n    \"name\": \"Jason Miller\",\n    \"email\": \"jason@developit.ca\",\n    \"url\": \"http://jasonformat.com\"\n  },\n  \"electronVersion\": \"0.37.5\",\n  \"files\": [\n    \"web/index.html\",\n    \"web/bundle.js\",\n    \"web/style.css\"\n  ],\n  \"dependencies\": {\n    \"request\": \"^2.70.0\",\n    \"tmp\": \"0.0.28\"\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"dropfox\",\n  \"productName\": \"Dropfox\",\n  \"version\": \"1.2.0\",\n  \"description\": \"Dropbox client, powered by Preact & Electron.\",\n  \"license\": \"MIT\",\n  \"repository\": \"developit/dropfox\",\n  \"author\": \"Jason Miller <jason@developit.ca> (http://jasonformat.com)\",\n  \"engines\": {\n    \"node\": \">=4\"\n  },\n  \"electronVersion\": \"0.37.5\",\n  \"scripts\": {\n    \"test\": \"eslint src\",\n    \"start\": \"NODE_ENV=development npm-run-all build:transpilewrap --parallel dev thenelectron\",\n    \"dev\": \"webpack-dev-server --hot --inline --progress\",\n    \"thenelectron\": \"sleep 2; electron ./app\",\n    \"clean\": \"rm -rf app && rm -rf dist && mkdir -p app/web/assets\",\n    \"build\": \"npm-run-all build:all build:electron-all\",\n    \"build:test\": \"npm-run-all build:all build:electron:osx && ./dist/${npm_package_productName}-darwin-x64/${npm_package_productName}.app/Contents/MacOS/Dropfox\",\n    \"build:all\": \"npm-run-all clean build:assets build:transpilewrap build:transpile build:packagejson build:install\",\n    \"build:assets\": \"ncp src/assets app/web/assets\",\n    \"build:packagejson\": \"ncp electron/package.json app/package.json\",\n    \"build:install\": \"cd app && npm i --production && cd ..\",\n    \"build:transpilewrap\": \"babel electron -s inline -d app\",\n    \"build:transpile\": \"NODE_ENV=production webpack -p\",\n    \"build:electron-all\": \"npm-run-all build:electron:*\",\n    \"build:electron:osx\": \"npm run build:electron -- --icon ./src/assets/icon.icns --platform=darwin && electron-installer-dmg ./dist/${npm_package_productName}-darwin-x64/${npm_package_productName}.app $npm_package_productName --out=./dist/${npm_package_productName}-darwin-x64 --icon=./src/assets/icon.icns --overwrite\",\n    \"build:electron:linux\": \"npm run build:electron -- --icon ./src/assets/icon.png --platform=linux\",\n    \"build:electron:win\": \"npm run build:electron -- --icon ./src/assets/icon.ico --platform=win32\",\n    \"build:electron\": \"electron-packager ./app --out=dist --overwrite --asar --prune --arch=all\"\n  },\n  \"dependencies\": {\n    \"decko\": \"^1.1.3\",\n    \"dropbox\": \"robertknight/dropbox-js#5568865764d9deab69836569a69d72d200c88293\",\n    \"neatime\": \"^1.0.0\",\n    \"photon\": \"connors/photon#v0.1.2-alpha\",\n    \"praline\": \"^0.3.1\",\n    \"preact\": \"^4.5.1\",\n    \"preact-photon\": \"^1.1.1\",\n    \"wildemitter\": \"^1.2.0\"\n  },\n  \"devDependencies\": {\n    \"autoprefixer\": \"^6.3.6\",\n    \"babel-cli\": \"^6.7.5\",\n    \"babel-core\": \"^6.7.6\",\n    \"babel-loader\": \"^6.2.4\",\n    \"babel-plugin-add-module-exports\": \"^0.1.2\",\n    \"babel-plugin-transform-decorators-legacy\": \"^1.3.4\",\n    \"babel-plugin-transform-react-jsx\": \"^6.7.5\",\n    \"babel-preset-es2015\": \"^6.6.0\",\n    \"babel-preset-es2015-minimal\": \"^1.1.0\",\n    \"babel-preset-stage-0\": \"^6.5.0\",\n    \"css-loader\": \"^0.23.1\",\n    \"electron-debug\": \"^0.6.0\",\n    \"electron-installer-dmg\": \"^0.1.0\",\n    \"electron-packager\": \"^6.0.1\",\n    \"electron-prebuilt\": \"^0.37.5\",\n    \"extract-text-webpack-plugin\": \"^1.0.1\",\n    \"file-loader\": \"^0.8.4\",\n    \"html-webpack-plugin\": \"^2.15.0\",\n    \"less\": \"^2.6.1\",\n    \"less-loader\": \"^2.2.1\",\n    \"ncp\": \"^2.0.0\",\n    \"npm-run-all\": \"^1.7.0\",\n    \"postcss-loader\": \"^0.8.2\",\n    \"raw-loader\": \"^0.5.1\",\n    \"redux\": \"^3.4.0\",\n    \"source-map-loader\": \"^0.1.5\",\n    \"style-loader\": \"^0.13.0\",\n    \"url-loader\": \"^0.5.7\",\n    \"webpack\": \"^1.12.14\",\n    \"webpack-dev-server\": \"^1.14.1\"\n  }\n}\n"
  },
  {
    "path": "src/assets/oauth_receiver.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\t<head>\n\t\t<script>\n\t\t\twindow.addEventListener('load', function() {\n\t\t\t\tvar opener = window.parent!==window.top ? window.parent : window.opener;\n\t\t\t\tif (!opener) return;\n\t\t\t\ttry {\n\t\t\t\t\topener.postMessage(JSON.stringify({\n\t\t\t\t\t\t_dropboxjs_oauth_info: location.href\n\t\t\t\t\t}), location.origin);\n\t\t\t\t\twindow.close();\n\t\t\t\t} catch (e) {}\n\t\t\t});\n\t\t</script>\n\t</head>\n\t<body>\n\t\t<h1>Dropbox sign-in successful</h1>\n\t\t<p>Please close this window.</p>\n\t</body>\n</html>\n"
  },
  {
    "path": "src/components/app.js",
    "content": "import { bind, memoize } from 'decko';\nimport { parallel } from 'praline';\nimport { Component, h, render } from 'preact';\nimport { Header, Title, Footer, Icon, Button, ButtonGroup } from 'preact-photon';\nimport Sidebar from './sidebar';\nimport PathBar from './path-bar';\nimport FileList from './file-list';\nimport { createMenu, createDomMenu, showMenu } from 'menu';\nimport dropbox from 'dropbox-client';\n\nexport default class App extends Component {\n\tstate = {\n\t\tpath: '/',\n\t\tloading: false,\n\t\thistory: [],\n\t\tfiles: []\n\t};\n\n\tcomponentDidMount() {\n\t\tdropbox.init( err => {\n\t\t\tif (err) return alert(err);\n\t\t\tthis.navigate('/');\n\t\t});\n\t}\n\n\t@bind\n\tnavigate(to, { go=1 }={}) {\n\t\tif (typeof to==='number') { go=to; to=''; }\n\t\tlet { path, history } = this.state;\n\t\tif (to[0]=='/') {\n\t\t\tpath = to;\n\t\t}\n\t\telse {\n\t\t\tpath = path.replace(/\\/+$/,'') + '/' + to.replace(/^\\/+/,'');\n\t\t}\n\t\twhile ( path !== (path = path.replace(/[^\\/]+\\/\\.\\.\\//)) );\n\t\tif (go) {\n\t\t\tif (go===-1) {\n\t\t\t\thistory.pop();\n\t\t\t\tpath = history[history.length-1];\n\t\t\t}\n\t\t\telse {\n\t\t\t\thistory.push(path);\n\t\t\t}\n\t\t}\n\t\tthis.setState({ path, loading:true, history });\n\t\tdropbox.readdir(path, (err, names, dir, files) => {\n\t\t\tthis.setState({ files, loading:false });\n\t\t});\n\t}\n\n\t@bind\n\tsearch(search) {\n\t\tthis.setState({ search, loading:true });\n\t\tdropbox.search(this.state.path, search, (err, files) => {\n\t\t\tthis.setState({ files, loading:false });\n\t\t});\n\t}\n\n\t@bind\n\thandleSearch({ target:{value:search} }) {\n\t\tif (search) {\n\t\t\tthis.search(search);\n\t\t}\n\t\telse {\n\t\t\tthis.navigate(0);\n\t\t}\n\t}\n\n\t@bind\n\thandleFile(e) {\n\t\tlet file = e.file || this.menuFile;\n\t\tif (file.isFolder) {\n\t\t\treturn this.navigate(file.name, { go:1 });\n\t\t}\n\t\telse {\n\t\t\tthis.openFile(file);\n\t\t}\n\t}\n\n\topenFile(file) {\n\t\tthis.setState({ loading:true });\n\t\tdropbox.open(file.path, {\n\t\t\tautoSync: true,\n\t\t\tonUpload: () => {\n\t\t\t\tconsole.log('File changed and uploaded. Reloading list.');\n\t\t\t\tthis.navigate(0);\n\t\t\t}\n\t\t}, (err, localPath) => {\n\t\t\tthis.setState({ loading:false });\n\t\t\tif (err) return alert(String(err));\n\t\t});\n\t}\n\n\t@bind\n\tto(...args) {\n\t\treturn () => this.navigate(...args);\n\t}\n\n\tgetActions() {\n\t\treturn this.actions || (this.actions = {\n\t\t\tgo: ::this.navigate,\n\t\t\tto: ::this.to\n\t\t});\n\t}\n\n\tonDragOver(e) {\n\t\te.dataTransfer.dropEffect = 'copy';\n\t\te.preventDefault();\n\t\te.stopPropagation();\n\t\treturn false;\n\t}\n\n\t@bind\n\tonDrop(e) {\n\t\tlet { path } = this.state,\n\t\t\tfiles = [].slice.call(e.dataTransfer.files);\n\t\tthis.setState({ loading:true });\n\t\tparallel( files.map( f => cb => {\n\t\t\tlet basename = (f.path.match(/([^\\/]+)\\/?$/g) || [])[0] || '';\n\t\t\tdropbox.upload(f.path, `${path}/${basename}`, cb);\n\t\t}), (err, ...results) => {\n\t\t\tthis.navigate(0);\n\t\t});\n\n\t\te.preventDefault();\n\t\te.stopPropagation();\n\t\treturn false;\n\t}\n\n\t@bind\n\tshowFilesMenu(e) {\n\t\tlet { left, top } = e.target.getBoundingClientRect();\n\t\tshowMenu(createMenu([\n\t\t\t{ label: 'New File', click: () => this.newFile() },\n\t\t\t{ label: 'New Folder', click: () => this.newDirectory() },\n\t\t\t{ type: 'separator' }\n\t\t]));\t\t// , left, top\n\t}\n\n\tnewFile(name, path=this.state.path) {\n\t\t// @TODO: prompt is not supported in Atom, use my custom modal.\n\t\tif (!name) name = prompt('Enter a name for the new file:');\n\t\tif (!name) return;\n\t\tdropbox.writeFile(`${path}/${basename}`, '', err => {\n\t\t\tif (err) console.error(err);\n\t\t\tthis.navigate(0);\n\t\t});\n\t}\n\n\tnewDirectory(name, path=this.state.path) {\n\t\t// @TODO: prompt is not supported in Atom, use my custom modal.\n\t\tif (!name) name = prompt('Enter a name for the new folder:');\n\t\tif (!name) return;\n\t\tdropbox.mkdir(`${path}/${basename}`, err => {\n\t\t\tif (err) console.error(err);\n\t\t\tthis.navigate(0);\n\t\t});\n\t}\n\n\t@bind\n\tspawnContextMenu(e) {\n\t\tlet t = e.target;\n\t\tif (e && e.button!==2) return;\n\t\twhile (!t.hasAttribute('contextmenu') && (t=t.parentNode));\n\t\tlet id = t.getAttribute('contextmenu'),\n\t\t\tdom = document.getElementById(id),\n\t\t\tmenu = dom && createDomMenu(dom);\n\t\tif (menu) {\n\t\t\tthis.menuFile = e.file;\n\t\t\tsetTimeout( () => showMenu(menu), 100);\n\t\t}\n\t}\n\n\tno() {\n\t\talert('Nobody uses this button');\n\t}\n\n\trender({}, { files, path, search, loading }) {\n\t\tlet actions = this.getActions();\n\t\treturn (\n\t\t\t<div id=\"app\" class=\"window\">\n\t\t\t\t<Header>\n\t\t\t\t\t<Title>Dropfox</Title>\n\n\t\t\t\t\t<div class=\"toolbar-actions\">\n\t\t\t\t\t\t<ButtonGroup>\n\t\t\t\t\t\t\t<Button icon=\"left-open-big\" onClick={ this.to(-1) } />\n\t\t\t\t\t\t\t<Button icon=\"right-open-big\" onClick={ this.no } />\n\t\t\t\t\t\t</ButtonGroup>\n\n\t\t\t\t\t\t<PathBar path={path} go={this.navigate} />\n\n\t\t\t\t\t\t<Button dropdown disabled={loading} class=\"pull-right\" onMouseDown={this.showFilesMenu}>\n\t\t\t\t\t\t\t<Icon name=\"cog\" />\n\t\t\t\t\t\t</Button>\n\n\t\t\t\t\t\t<label class=\"toolbar-input pull-right\">\n\t\t\t\t\t\t\t<span class=\"icon icon-search\"></span>\n\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\tclass=\"form-control\"\n\t\t\t\t\t\t\t\ttype=\"search\"\n\t\t\t\t\t\t\t\tplaceholder=\"Search...\"\n\t\t\t\t\t\t\t\tvalue={ search }\n\t\t\t\t\t\t\t\tonSearch={ this.handleSearch } />\n\t\t\t\t\t\t</label>\n\t\t\t\t\t</div>\n\t\t\t\t</Header>\n\n\t\t\t\t<div class=\"window-content\">\n\t\t\t\t\t<div class=\"pane-group\">\n\t\t\t\t\t\t<Sidebar path={path} actions={actions} />\n\t\t\t\t\t\t<div class=\"pane\" onDragOver={this.onDragOver} onDragEnter={this.onDragOver} onDrop={this.onDrop}>\n\t\t\t\t\t\t\t<FileList\n\t\t\t\t\t\t\t\tfiles={files}\n\t\t\t\t\t\t\t\taction={this.handleFile}\n\t\t\t\t\t\t\t\tonContextMenu={this.spawnContextMenu}\n\t\t\t\t\t\t\t\tcontextmenu=\"file-menu\" />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\n\t\t\t\t<Footer>\n\t\t\t\t\t<Icon name={ loading?'switch':'cloud' } class=\"pull-right square\" />\n\t\t\t\t\t<Title>\n\t\t\t\t\t\t{ files.length.toLocaleString() + ' items' }\n\t\t\t\t\t</Title>\n\t\t\t\t</Footer>\n\n\t\t\t\t<menu type=\"context\" id=\"file-menu\" style=\"display:none;\">\n\t\t\t\t\t<menuitem label=\"Open\" onClick={ this.handleFile } />\n\t\t\t\t\t<menuitem label=\"Download\" disabled />\n\t\t\t\t\t<menuitem label=\"Properties\" disabled />\n\t\t\t\t\t<menu label=\"Open with...\">\n\t\t\t\t\t\t<menuitem label=\"Atom\" />\n\t\t\t\t\t\t<menuitem label=\"Sublime Text\" />\n\t\t\t\t\t\t<menuitem label=\"TextWrangler\" />\n\t\t\t\t\t</menu>\n\t\t\t\t</menu>\n\t\t\t</div>\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "src/components/file-list.js",
    "content": "import { bind, memoize } from 'decko';\nimport { h, Component } from 'preact';\nimport neatime from 'neatime';\nimport { Icon } from 'preact-photon';\n\nconst time = memoize( str => neatime(new Date(str)).replace(/^(\\d+[a-z])$/g,'$1 ago') );\n\nexport default class FileList extends Component {\n\t@bind\n\thandleKey(e) {\n\t\tif (e.code==='ArrowUp' || e.keyCode===38) {\n\t\t\tthis.move(-1);\n\t\t}\n\t\telse if (e.code==='ArrowDown' || e.keyCode===40) {\n\t\t\tthis.move(1);\n\t\t}\n\t\telse if (e.code==='Enter' || e.keyCode===13) {\n\t\t\tthis.openSelected();\n\t\t}\n\t\telse {\n\t\t\treturn;\n\t\t}\n\t\te.preventDefault();\n\t\treturn false;\n\t}\n\n\topenSelected() {\n\t\tlet { action } = this.props,\n\t\t\t{ selected } = this.state;\n\t\tif (action && selected) {\n\t\t\taction({ file: selected });\n\t\t}\n\t}\n\n\tmove(delta) {\n\t\tlet { files } = this.props,\n\t\t\t{ selected } = this.state,\n\t\t\tindex = files.indexOf(selected);\n\t\tif (index===-1 && delta<0) index = files.length;\n\t\tindex += delta;\n\t\tif (index>=0 && index<files.length) {\n\t\t\tselected = files[index];\n\t\t\tthis.setState({ selected });\n\t\t}\n\t}\n\n\tcomponentDidMount() {\n\t\taddEventListener('keydown', this.handleKey);\n\t}\n\n\tcomponentWillUnmount() {\n\t\tremoveEventListener('keydown', this.handleKey);\n\t}\n\n\tcomponentDidUpdate() {\n\t\tlet selected = this.base && this.base.querySelector('.selected');\n\t\tif (selected) selected.scrollIntoViewIfNeeded();\n\t}\n\n\trender({ files, action, ...props }, { selected }) {\n\t\treturn (\n\t\t\t<div class=\"cotable file-list\">\n\t\t\t\t<table>\n\t\t\t\t\t<thead>\n\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t<th width=\"40\"></th>\n\t\t\t\t\t\t\t<th>Name</th>\n\t\t\t\t\t\t\t<th width=\"80\">Size</th>\n\t\t\t\t\t\t\t<th width=\"100\">Modified</th>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t</thead>\n\t\t\t\t</table>\n\t\t\t\t<table class=\"striped\">\n\t\t\t\t\t<tbody>{\n\t\t\t\t\t\tfiles.map( file => (\n\t\t\t\t\t\t\t<FileListItem\n\t\t\t\t\t\t\t\tselected={selected && selected.path===file.path}\n\t\t\t\t\t\t\t\tfile={file}\n\t\t\t\t\t\t\t\tonAction={action}\n\t\t\t\t\t\t\t\tonSelect={this.linkState('selected', 'file')}\n\t\t\t\t\t\t\t\t{...props}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t))\n\t\t\t\t\t}</tbody>\n\t\t\t\t</table>\n\t\t\t</div>\n\t\t);\n\t}\n}\n\nconst FileListItem = ({ showThumbs=false, file, selected, onContextMenu, onSelect, onAction, ...props }) => (\n\t<tr {...props} file={file} class={{selected}} onContextMenu={ fileProxy(file, onContextMenu) } onMouseDown={ fileProxy(file, onSelect) } onDblClick={ fileProxy(file, onAction) }>\n\t\t<td width=\"40\">{ showThumbs && file.hasThumbnail ? (\n\t\t\t<div style={\"width:1.8em; height:1.4em; background:url(\"+dropbox.thumbnailUrl(file.path)+\") center/contain;\"} />\n\t\t) : (\n\t\t\t<Icon class=\"square\" name={ file.isFile ? 'doc-text-inv' : 'folder' } />\n\t\t) }</td>\n\t\t<td>{ file.name }</td>\n\t\t<td width=\"80\">{ file.humanSize }</td>\n\t\t<td width=\"100\">{ time(file.modifiedAt) }</td>\n\t</tr>\n);\n\n\nfunction fileProxy(file, fn) {\n\treturn e => {\n\t\te.file = file;\n\t\tif (fn) return fn(e);\n\t};\n}\n"
  },
  {
    "path": "src/components/path-bar.js",
    "content": "import { h, Component } from 'preact';\nimport { ButtonGroup, Button } from 'preact-photon';\n\nconst EXISTS = x => x;\n\nexport default ({ path, go }) => {\n\tlet parts = path.split('/').filter(EXISTS);\n\n\treturn (\n\t\t<ButtonGroup>\n\t\t\t<Button icon=\"home\" onClick={ () => go('/') }>Home</Button>\n\t\t\t{ parts.map( (dir, i) => (\n\t\t\t\t<Button onClick={ () => go('/'+parts.slice(0,i+1).join('/')) }>{ dir }</Button>\n\t\t\t) ) }\n\t\t</ButtonGroup>\n\t);\n};\n"
  },
  {
    "path": "src/components/sidebar.js",
    "content": "import { h, Component } from 'preact';\nimport { NavGroup } from 'preact-photon';\n\nexport default class Sidebar extends Component {\n\t// shouldComponentUpdate() {\n\t// \treturn false;\n\t// }\n\n\tpaths = [\n\t\t{ path:'/', icon:'home', label:'Home' },\n\t\t{ path:'/Photos', icon:'picture' },\n\t\t{ path:'/Music', icon:'note-beamed' },\n\t\t{ path:'/Public', icon:'globe' },\n\t\t{ path:'/Apps', icon:'cloud' }\n\t];\n\n\trender({ path, actions }) {\n\n\t\treturn (\n\t\t\t<div class=\"pane pane-sm sidebar\">\n\t\t\t\t<NavGroup>\n\t\t\t\t\t<NavGroup.Title>Places</NavGroup.Title>\n\t\t\t\t\t{ this.paths.map( item => (\n\t\t\t\t\t\t<SidebarItem {...item} actions={actions} active={ path===item.path } />\n\t\t\t\t\t)) }\n\t\t\t\t</NavGroup>\n\t\t\t</div>\n\t\t);\n\t}\n}\n\n\nconst SidebarItem = ({ active, icon, path, label, actions:{ to }, children }) => {\n\treturn <NavGroup.Item icon={ icon } onclick={ to(path) } class={{ active }}>{ children || label || path.replace('/','') }</NavGroup.Item>\n};\n"
  },
  {
    "path": "src/index.js",
    "content": "import { h, render } from 'preact';\nimport './styles/index.less';\n\nlet root;\nfunction init() {\n\tlet App = require('./components/app');\n\troot = render(<App />, document.body, root);\n}\ninit();\n\nif (module.hot) module.hot.accept('./components/app', () => requestAnimationFrame(init) );\n"
  },
  {
    "path": "src/lib/dropbox-client.js",
    "content": "import Emitter from 'wildemitter';\nimport { debounce } from 'decko';\nimport { Client, AuthDriver } from 'dropbox';\nimport remoteRequire from 'remote-require';\n\nconst request = remoteRequire('request');\nconst fs = remoteRequire('fs');\nconst tmp = remoteRequire('tmp');\nconst shell = remoteRequire('shell');\n\nconst dropbox = new Client({\n\tkey: API_KEY\n});\n\nconst CLEANUPS = {};\n\nconst NOOP = ()=>{};\n\nexport default dropbox;\n\nObject.assign(dropbox, new Emitter());\nObject.assign(dropbox, Emitter.prototype);\n\ndropbox.authDriver(new AuthDriver.Popup({\n\treceiverUrl: 'https://dropfox.firebaseapp.com/dropbox/oauth_receiver.html'\n\t//receiverUrl: location.href.replace(/[^/]+$/,'') + 'assets/oauth_receiver.html'\n}));\n\nexport function init(callback=NOOP) {\n\tdropbox.authenticate({ interactive: false }, err => {\n\t\tif (err) return callback(err);\n\n\t\tif (dropbox.isAuthenticated()) {\n\t\t\tdropbox.emit('init');\n\t\t\treturn callback();\n\t\t}\n\n\t\tdropbox.authenticate( err => {\n\t\t\tif (err) return callback(err);\n\n\t\t\tdropbox.emit('init');\n\t\t\tcallback();\n\t\t});\n\t});\n}\n\nexport function stream(path, callback) {\n\tpath = path.replace(/^\\//,'');\n\tlet url = `${dropbox._urls.getFile}/${path}?access_token=${dropbox.credentials().token}`,\n\t\tbasename = (path.match(/([^\\/]+)\\/?$/g) || [])[0] || '',\n\t\terror, localPath,\n\t\tdone = debounce(() => callback ? callback(error, localPath) : (callback = null));\n\ttmp.dir( (err, target, fd, cleanup) => {\n\t\tlocalPath = target + '/' + basename;\n\t\tCLEANUPS[localPath] = cleanup;\n\t\trequest.get(url)\n\t\t\t.on('error', err => { error = err; done(); })\n\t\t\t.pipe(\n\t\t\t\tfs.createWriteStream(localPath)\n\t\t\t\t\t.on('error', err => { error = err; done(); })\n\t\t\t\t\t.on('finish', done)\n\t\t\t);\n\t});\n}\n\nexport function open(path, options, callback) {\n\tif (typeof options==='function') {\n\t\t[callback, options] = [options, callback];\n\t}\n\toptions = options || {};\n\tstream(path, (err, localPath) => {\n\t\tif (!err) {\n\t\t\tshell.openItem(localPath);\n\n\t\t\tif (options.autoSync) {\n\t\t\t\twatchAndUpload(localPath, path, options.onSync || options.onUpload);\n\t\t\t}\n\t\t}\n\t\tcallback(err, localPath);\n\t});\n}\n\n\nfunction watchAndUpload(localPath, path, onUpload) {\n\tlet start = Date.now();\n\tfs.watch(localPath, {\n\t\tpersistent: false\n\t}, debounce(1000, changeType => {\n\t\tif (Date.now()-start < 3000) return;\n\t\t// console.log('changed', changeType);\n\n\t\tupload(localPath, path, onUpload);\n\t}));\n}\n\n\nlet backend = remoteRequire('./backend');\nexport function upload(localPath, path, callback) {\n\tlet url = `${dropbox._urls.putFile}/${path}?access_token=${dropbox.credentials().token}`;\n\tbackend.upload(localPath, url, err => {\n\t\tcallback(err);\n\t});\n}\n\n\nObject.assign(dropbox, { init, stream, open, upload });\n\n// console.log(window.dropbox = dropbox);\n"
  },
  {
    "path": "src/lib/menu.js",
    "content": "import remote from 'remote';\nconst Menu = remote.require('menu');\nconst MenuItem = remote.require('menu-item');\n\nexport function createMenu(template) {\n\tlet menu = Menu.buildFromTemplate(template);\n\treturn menu;\n}\n\nexport function showMenu(menu, x, y) {\n\tmenu.popup(remote.getCurrentWindow(), x, y);\n}\n\nexport function createDomMenu(element) {\n\tlet menu = new Menu();\n\t[].forEach.call(element.children, child => {\n\t\tlet item = {};\n\t\tif (child.nodeName==='separator') {\n\t\t\titem.type = 'separator';\n\t\t}\n\t\telse {\n\t\t\titem.label = child.textContent;\n\t\t\tfor (let i in child.attributes) {\n\t\t\t\titem[child.attributes[i].name] = child.attributes[i].value;\n\t\t\t}\n\t\t\tif (child.nodeName==='MENU') {\n\t\t\t\titem.type = 'submenu';\n\t\t\t\titem.submenu = createDomMenu(child);\n\t\t\t}\n\t\t\telse {\n\t\t\t\titem.click = () => { console.log('click'); child.click(); };\n\t\t\t}\n\t\t}\n\t\tmenu.append(new MenuItem(item));\n\t});\n\treturn menu;\n}\n"
  },
  {
    "path": "src/lib/remote-require.js",
    "content": "import remote from 'remote';\n\n// remote.require(), patched to work in both dev and prod mode:\nexport default function remoteRequire(module) {\n\tif (process.env.NODE_ENV!=='development') {\n\t\tmodule = module.replace(/^\\.\\//, __dirname+'/../');\n\t}\n\treturn remote.require(module);\n}\n"
  },
  {
    "path": "src/styles/index.less",
    "content": "@import (less) '~photon/dist/css/photon.css';\n\n#app {\n\t.toolbar {\n\t\t-webkit-app-region: drag;\n\t\t.form-control {\n\t\t\t-webkit-app-region: no-drag;\n\t\t}\n\t}\n\t.sidebar {\n\t\tflex: 0;\n\t}\n}\n\ntr.selected,\n.table-striped tr:nth-child(even).selected {\n\tcolor: #fff;\n\tbackground-color: #116cd6;\n}\n\n.file-list {\n\tth, td {\n\t\tpadding: 2px 8px;\n\t}\n}\n\n// fix <ButtonGroup><Button /></ButtonGroup>\n.btn-group > .btn:first-child:last-child {\n\tborder-radius: 4px;\n}\n\n// normalize search inputs (but keep the cancel \"X\" button):\ninput[type=\"search\"] {\n\t-webkit-appearance: textfield;\n\t&::-webkit-search-cancel-button {\n\t\t-webkit-appearance: searchfield-cancel-button;\n\t\topacity: 1;\n\t}\n}\n\n.toolbar-input {\n\tposition: relative;\n\toverflow: visible;\n\tmargin: -1px 0 0;\n\tpadding: 0;\n\t.icon {\n\t\tposition: absolute;\n\t\tleft: 10px;\n\t\ttop: 4px;\n\t\tz-index: 1;\n\t\topacity: 0.5;  /* ~matches placeholder */\n\t}\n\t.form-control {\n\t\tpadding: 3px 4px 1px 24px;\n\t\tborder-radius: 12px;\n\t\tbox-shadow: inset 0 1px 2px rgba(0,0,0,0.4),\n\t\t\t\t\t0 0 0 7px rgba(109,179,253,0);\n\n\t\t&:focus {\n\t\t\tbox-shadow: inset 0 1px 2px rgba(0,0,0,0.4),\n\t\t\t\t\t\t0 0 0 1px rgba(109,179,253,0.8);\n\t\t\ttransition: box-shadow 250ms ease-out;\n\t\t\tcursor: text;\n\t\t}\n\t}\n}\n\n.icon {\n\t&.fliph::before {\n\t\tposition: relative;\n\t\tleft: 1px;\n\t\ttransform: scaleX(-1);\n\t}\n\t&.square {\n\t\tdisplay: inline-block;\n\t\twidth: 1.8em;\n\t\ttext-align: center;\n\t}\n}\n\n\n.cotable {\n\tdisplay: flex;\n\tflex-direction: column;\n\tposition: absolute;\n\tleft: 0;\n\ttop: 0;\n\theight: 100%;\n\twidth: 100%;\n\toverflow: hidden;\n}\n.cotable table {\n\ttable-layout: fixed;\n\twidth: 100%;\n}\ntable + table {\n\tdisplay: block;\n\tflex: 1;\n\toverflow-y: auto;\n}\ntable + table tbody {\n\twidth: 100%;\n\tdisplay: table;\n}\ntable + table tr {\n\tbackground: #FFF;\n\n\t&:nth-child(odd):not(.selected):not(:active) {\n\t\tbackground: #F5F5F5;\n\t}\n}\n\n\n.rotate {\n\ttransform-origin: 50% 50%;\n\twill-change: transform;\n\tanimation: rotate 500ms linear infinite;\n}\n@keyframes rotate {\n\t0% { transform:rotate(0deg); }\n\t100% { transform:rotate(360deg); }\n}\n"
  },
  {
    "path": "webpack.config.babel.js",
    "content": "import pkg from './package.json';\nimport path from 'path';\nimport webpack from 'webpack';\nimport ExtractTextPlugin from 'extract-text-webpack-plugin';\nimport HtmlWebpackPlugin from 'html-webpack-plugin';\nimport autoprefixer from 'autoprefixer';\n\nconst ENV = process.env.NODE_ENV || 'development';\n\nmodule.exports = {\n\tcontext: `${__dirname}/src`,\n\tentry: './index.js',\n\n\ttarget: 'web',\n\n\toutput: {\n\t\tpath: `${__dirname}/app/web`,\n\t\t// publicPath: '/',\n\t\tfilename: 'bundle.js'\n\t},\n\n\tresolve: {\n\t\tmodulesDirectories: [\n\t\t\t`${__dirname}/src/lib`,\n\t\t\t`${__dirname}/node_modules`,\n\t\t\t'node_modules'\n\t\t]\n\t\t// alias: {\n\t\t// \t'dropbox': path.resolve(__dirname, 'node_modules/dropbox/lib/dropbox.js')\n\t\t// }\n\t},\n\n\texternals: [{\n\t\t'remote': 'commonjs remote'\n\t}],\n\n\tmodule: {\n\t\tnoParse: [\n\t\t\t/\\b(node_modules|~)\\/dropbox\\//,\n\t\t],\n\n\t\tpreLoaders: [\n\t\t\t{\n\t\t\t\ttest: /\\.(jsx?|css|less)$/,\n\t\t\t\texclude: /(src|webpack\\-dev\\-server|socket\\.io\\-client)/,\n\t\t\t\tloader: 'source-map'\n\t\t\t}\n\t\t],\n\n\t\tloaders: [\n\t\t\t{\n\t\t\t\ttest: /\\.jsx?$/,\n\t\t\t\texclude: /(node_modules|\\/~\\/|webpack\\-dev\\-server|socket\\.io\\-client)/,\n\t\t\t\tloader: 'babel'\n\t\t\t},\n\t\t\t{\n\t\t\t\ttest: /\\.(css|less)$/,\n\t\t\t\tloader: ExtractTextPlugin.extract(\n\t\t\t\t\t'style?sourceMap',\n\t\t\t\t\t'css?sourceMap!postcss!less?sourceMap'\n\t\t\t\t)\n\t\t\t},\n\t\t\t{\n\t\t\t\ttest: /\\.(svg|woff2?|ttf|eot)$/,\n\t\t\t\tloader: 'url'\n\t\t\t}\n\t\t]\n\t},\n\n\tpostcss: () => [\n\t\tautoprefixer({ browsers: 'last 2 versions' })\n\t],\n\n\tplugins: [\n\t\tnew webpack.NoErrorsPlugin(),\n\t\tnew ExtractTextPlugin('style.css', {\n\t\t\tallChunks: true,\n\t\t\tdisable: ENV!=='production'\n\t\t}),\n\t\tnew webpack.optimize.DedupePlugin(),\n\t\tnew webpack.optimize.OccurenceOrderPlugin(),\n\t\tnew webpack.DefinePlugin({\n\t\t\tAPI_KEY: JSON.stringify(process.env.DROPBOX_API_KEY),\n\t\t\t'process.env.NODE_ENV': JSON.stringify(ENV)\n\t\t}),\n\t\tnew HtmlWebpackPlugin({\n\t\t\ttitle: pkg.productName || pkg.name,\n\t\t\tfilename: 'index.html',\n\t\t\tminify: { collapseWhitespace: true }\n\t\t})\n\t],\n\n\t// turn off node shims\n\tnode: {\n\t\tconsole: false,\n\t\tglobal: false,\n\t\tprocess: false,\n\t\tBuffer: false,\n\t\t__filename: false,\n\t\t__dirname: false,\n\t\tsetImmediate: false\n\t},\n\n\tstats: { colors: true },\n\n\t// Create Sourcemaps for the bundle\n\tdevtool: ENV==='development' ? 'inline-source-map' : 'source-map',\n\n\tdevServer: {\n\t\tport: process.env.PORT || 19998,\n\t\tcontentBase: './build',\n\t\tinline: true,\n\t\thot: true,\n\t\thistoryApiFallback: true\n\t}\n\n};\n"
  }
]