diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..2a94754fa --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +out/* \ No newline at end of file diff --git a/.gitignore b/.gitignore index bb421eb81..ec58aac6a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ -debug.log node_modules build release -npm-debug.log -app.log package-lock.json out +*.log \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3be7339fb..f70e1b1ee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ $ npm run lint ``` When you are ready to commit please be sure to follow the -[commit convention](https://github.com/ajoslin/conventional-changelog/blob/master/conventions/angular.md). +[commit convention](https://github.com/conventional-changelog/conventional-changelog/blob/master/packages/conventional-changelog-angular/convention.md). ## Available Scripts diff --git a/README.md b/README.md index e7a89441f..b89cca927 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,14 @@ A Native Application for your OS to run your own IPFS Node. Built with [Electron ## Install -You will need [Node.js](https://nodejs.org/en/) installed. Preferrably a version `>=4.0`. Also you will need [npm](npmjs.org) `>=3.0`. After that you should run +You will need [Node.js](https://nodejs.org/en/) installed, preferrably a version `>=4.0`. On macOS you may also need Xcode command line tools, which can be installed with + +```bash +xcode-select --install # Install Command Line Tools if you haven't already. +sudo xcode-select --switch /Library/Developer/CommandLineTools # Enable command line tools +``` + +Also you will need [npm](npmjs.org) `>=3.0`. After that you should run ```bash $ git clone https://github.com/ipfs/station.git @@ -28,7 +35,7 @@ $ npm start This launches the app and runs it in your menu bar. Click the IPFS icon to open a console. For example (in OSX): -![](https://ipfs.io/ipfs/QmaufMhYVWPKwhC1jSb4qHBxgiahrq9ct2hgqk5cZxeE7s) +![](https://ipfs.io/ipfs/QmQjPLSWt54MdFzLAxyEvTdaYPtdTAor7A1d5ugcVcmT87) ## Usage diff --git a/package.json b/package.json index fb6143feb..9103701b9 100644 --- a/package.json +++ b/package.json @@ -8,21 +8,22 @@ "electron-compile": "^6.4.2", "electron-is-dev": "^0.3.0", "electron-squirrel-startup": "^1.0.0", + "file-extension": "^4.0.0", "ipfs-api": "^17.1.3", "ipfs-geoip": "^2.3.0", - "ipfs-logo": "github:ipfs/logo", "ipfsd-ctl": "^0.26.0", "menubar": "^5.2.3", + "moment": "^2.19.2", "multiaddr": "^3.0.1", "node-notifier": "^5.1.2", "normalize.css": "^7.0.0", + "pretty-bytes": "^4.0.2", "prop-types": "^15.6.0", "react": "^16.1.1", "react-dnd": "^2.5.4", "react-dnd-html5-backend": "^2.5.4", "react-dom": "^16.1.1", "react-transition-group": "^2.2.1", - "react-widgets": "^4.1.1", "winston": "^3.0.0-rc1" }, "devDependencies": { @@ -97,11 +98,12 @@ "url": "https://github.com/ipfs/station" }, "author": "IPFS", - "authors": [ + "contributors": [ "Kristoffer Ström ", "David Dias ", "Juan Benet ", - "Friedel Ziegelmayer " + "Friedel Ziegelmayer ", + "Henrique Dias " ], "license": "MIT", "bugs": { diff --git a/src/config.js b/src/config.js index 8e853554e..4f19fafe1 100644 --- a/src/config.js +++ b/src/config.js @@ -4,17 +4,49 @@ import fs from 'fs' import os from 'os' import isDev from 'electron-is-dev' import {app} from 'electron' -import {getLogo, macOsMenuBar} from './utils/logo' +import FileHistory from './utils/file-history' + +export const logoIpfsIce = (() => { + const p = path.resolve(path.join(__dirname, 'img')) + + if (os.platform() === 'darwin') { + return path.join(p, 'icons/ice.png') + } + + return path.join(p, 'ipfs-logo-ice.png') +})() + +export const logoIpfsBlack = (() => { + const p = path.resolve(path.join(__dirname, 'img')) + + if (os.platform() === 'darwin') { + return path.join(p, 'icons/black.png') + } + + return path.join(p, 'ipfs-logo-black.png') +})() const isProduction = !isDev const currentURL = (name) => `file://${__dirname}/views/${name}.html` -const ipfsPathFile = path.join(app.getPath('appData'), 'ipfs-electron-app-node-path') +const ipfsAppData = (() => { + const p = path.join(app.getPath('appData'), 'ipfs-station') + + if (!fs.existsSync(p)) { + fs.mkdirSync(p) + } + + return p +})() + +const ipfsPathFile = path.join(ipfsAppData, 'app-node-path') +const ipfsFileHistoryFile = path.join(ipfsAppData, 'file-history.json') const ipfsPath = (() => { let pathIPFS - try { + + if (fs.existsSync(ipfsPathFile)) { pathIPFS = fs.readFileSync(ipfsPathFile, 'utf-8') - } catch (e) { + } else { pathIPFS = path.join(process.env.IPFS_PATH || (process.env.HOME || process.env.USERPROFILE), '.ipfs') } @@ -22,12 +54,19 @@ const ipfsPath = (() => { return pathIPFS })() +export const fileHistory = new FileHistory(ipfsFileHistoryFile) + // Sets up the Logger export const logger = winston.createLogger({ format: winston.format.json(), transports: [ new winston.transports.File({ - filename: path.join(__dirname, 'app.log'), + filename: 'error.log', + level: 'error', + handleExceptions: false + }), + new winston.transports.File({ + filename: 'combined.log', handleExceptions: false }) ] @@ -40,25 +79,13 @@ if (isDev) { })) } -// Default settings for new windows -const window = { - icon: getLogo(), - title: 'IPFS Dashboard', - autoHideMenuBar: true, - width: 800, - height: 500, - webPreferences: { - webSecurity: false - } -} - // Configuration for the MenuBar const menubar = { dir: __dirname, - width: 300, + width: 850, height: 400, index: `file://${__dirname}/views/menubar.html`, - icon: (os.platform() === 'darwin') ? macOsMenuBar : getLogo(), + icon: logoIpfsBlack, tooltip: 'Your IPFS instance', alwaysOnTop: true, preloadWindow: true, @@ -74,10 +101,10 @@ export default { isProduction, logger, menubar, - window, webuiPath: '/webui', ipfsPath, ipfsPathFile, + ipfsFileHistoryFile, urls: { welcome: currentURL('welcome'), settings: currentURL('settings') diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 000000000..662de6419 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,4 @@ +export const PAGES = { + FILES: 'files', + INFO: 'info' +} diff --git a/src/controls/drag-drop.js b/src/controls/drag-drop.js deleted file mode 100644 index 0d35f5b4c..000000000 --- a/src/controls/drag-drop.js +++ /dev/null @@ -1,67 +0,0 @@ -import notifier from 'node-notifier' -import {join} from 'path' -import {logger} from '../config' -import {getIPFS} from './../index' -import {clipboard} from 'electron' - -const iconPath = join(__dirname, '..', '..', 'node_modules', 'ipfs-logo', 'platform-icons/osx-menu-bar@2x.png') - -// TODO: persist this to disk -const filesUploaded = [] - -function notify (title, message) { - notifier.notify({ - appName: 'ipfs.station', - title, - message, - icon: iconPath, - sound: true, - wait: false - }) -} - -function notifyError (message) { - notifier.notify({ - title: 'Error in file upload', - message, - icon: iconPath, - sound: true, - wait: false - }) -} - -export default function dragDrop (event, files) { - const ipfs = getIPFS() - if (!ipfs) { - notifyError('Can\'t upload file, IPFS Node is offline') - return - } - - ipfs - .add(files, {w: files.length > 1}) - .then((res) => { - if (!res) { - notifyError('Failed to upload files') - return - } - - logger.info('Uploading files', {files}) - - res.forEach((file) => { - const url = `https://ipfs.io/ipfs/${file.hash}` - clipboard.writeText(url) - filesUploaded.push(file) - - logger.info('Uploaded file %s', file.path) - - notify( - `Finished uploading ${file.path}`, - `${file.path} was uploaded to ${url}.` - ) - }) - }) - .catch((err) => { - logger.error(err) - notifyError(err.message) - }) -} diff --git a/src/controls/open-browser.js b/src/controls/open-browser.js deleted file mode 100644 index f32c1672c..000000000 --- a/src/controls/open-browser.js +++ /dev/null @@ -1,21 +0,0 @@ -import {shell} from 'electron' -import {apiAddrToUrl} from './utils' -import {logger} from '../config' -import {getIPFS} from './../index' - -export default function openBrowser (cb) { - const ipfs = getIPFS() - if (!ipfs) { - const err = new Error('Cannot open browser, IPFS daemon not running') - logger.error(err) - return - } - - ipfs.config.get('Addresses.API') - .then((res) => { - shell.openExternal(apiAddrToUrl(res)) - }) - .catch((err) => { - return logger.error(err) - }) -} diff --git a/src/controls/open-console.js b/src/controls/open-console.js deleted file mode 100644 index c7e400512..000000000 --- a/src/controls/open-console.js +++ /dev/null @@ -1,22 +0,0 @@ -import {BrowserWindow} from 'electron' -import {apiAddrToUrl} from './utils' -import config from '../config' -import {getIPFS} from './../index' - -export default function openConsole () { - const ipfs = getIPFS() - if (!ipfs) { - const err = new Error('Cannot open console, IPFS daemon not running') - config.logger.error(err) - return - } - - ipfs.config.get('Addresses.API') - .then((res) => { - const consoleWindow = new BrowserWindow(config.window) - consoleWindow.loadURL(apiAddrToUrl(res)) - }) - .catch((err) => { - return config.logger.error(err) - }) -} diff --git a/src/controls/open-file-dialog.js b/src/controls/open-file-dialog.js new file mode 100644 index 000000000..1fc02f6a7 --- /dev/null +++ b/src/controls/open-file-dialog.js @@ -0,0 +1,13 @@ +import {dialog} from 'electron' +import uploadFiles from './upload-files' + +export default function openFileDialog (window, ipfs, dir = false) { + return (event, callback) => { + dialog.showOpenDialog(window, { + properties: [dir ? 'openDirectory' : 'openFile', 'multiSelections'] + }, (files) => { + if (!files || files.length === 0) return + uploadFiles(ipfs, event, files) + }) + } +} diff --git a/src/controls/open-settings.js b/src/controls/open-settings.js deleted file mode 100644 index 63ae68e41..000000000 --- a/src/controls/open-settings.js +++ /dev/null @@ -1,10 +0,0 @@ -import {BrowserWindow} from 'electron' -import config from './../config' - -export default function openSettings () { - const settingsWindow = new BrowserWindow(config.window) - - settingsWindow.loadURL(config.urls.settings) - - settingsWindow.webContents.on('did-finish-load', () => {}) -} diff --git a/src/controls/open-webui.js b/src/controls/open-webui.js new file mode 100644 index 000000000..80e05d95c --- /dev/null +++ b/src/controls/open-webui.js @@ -0,0 +1,11 @@ +import {shell} from 'electron' +import {apiAddrToUrl} from './utils' +import {logger} from '../config' + +export default function openWebUI (ipfs, cb) { + ipfs().config.get('Addresses.API') + .then((res) => { + shell.openExternal(apiAddrToUrl(res)) + }) + .catch(logger.error) +} diff --git a/src/controls/upload-files.js b/src/controls/upload-files.js new file mode 100644 index 000000000..903566786 --- /dev/null +++ b/src/controls/upload-files.js @@ -0,0 +1,18 @@ +import {logger, fileHistory} from '../config' +import {clipboard} from 'electron' + +export default function uploadFiles (ipfs, event, files) { + ipfs() + .add(files, {recursive: true, w: files.length > 1}) + .then((res) => { + logger.info('Uploading files', {files}) + + res.forEach((file) => { + const url = `https://ipfs.io/ipfs/${file.hash}` + clipboard.writeText(url) + logger.info('Uploaded file', {path: file.path}) + fileHistory.add(file.path, file.hash) + }) + }) + .catch(logger.error) +} diff --git a/src/fonts/dripicons.ttf b/src/fonts/dripicons.ttf deleted file mode 100644 index 6c698488e..000000000 Binary files a/src/fonts/dripicons.ttf and /dev/null differ diff --git a/src/fonts/maven_pro_bold-webfont.ttf b/src/fonts/maven_pro_bold-webfont.ttf deleted file mode 100644 index e12caee35..000000000 Binary files a/src/fonts/maven_pro_bold-webfont.ttf and /dev/null differ diff --git a/src/fonts/maven_pro_medium-webfont.ttf b/src/fonts/maven_pro_medium-webfont.ttf deleted file mode 100644 index f7e776b32..000000000 Binary files a/src/fonts/maven_pro_medium-webfont.ttf and /dev/null differ diff --git a/src/fonts/maven_pro_regular-webfont.ttf b/src/fonts/maven_pro_regular-webfont.ttf deleted file mode 100644 index c07720674..000000000 Binary files a/src/fonts/maven_pro_regular-webfont.ttf and /dev/null differ diff --git a/src/fonts/sf-pro-text-bold.otf b/src/fonts/sf-pro-text-bold.otf new file mode 100644 index 000000000..840d8109f Binary files /dev/null and b/src/fonts/sf-pro-text-bold.otf differ diff --git a/src/fonts/sf-pro-text-light.otf b/src/fonts/sf-pro-text-light.otf new file mode 100644 index 000000000..1e1696825 Binary files /dev/null and b/src/fonts/sf-pro-text-light.otf differ diff --git a/src/fonts/sf-pro-text-medium.otf b/src/fonts/sf-pro-text-medium.otf new file mode 100644 index 000000000..4982cb588 Binary files /dev/null and b/src/fonts/sf-pro-text-medium.otf differ diff --git a/src/fonts/sf-pro-text-regular.otf b/src/fonts/sf-pro-text-regular.otf new file mode 100644 index 000000000..65f9ea5ea Binary files /dev/null and b/src/fonts/sf-pro-text-regular.otf differ diff --git a/src/fonts/sf-pro-text-semibold.otf b/src/fonts/sf-pro-text-semibold.otf new file mode 100644 index 000000000..a20e7a0a7 Binary files /dev/null and b/src/fonts/sf-pro-text-semibold.otf differ diff --git a/src/fonts/themify.eot b/src/fonts/themify.eot new file mode 100644 index 000000000..9ec298b9d Binary files /dev/null and b/src/fonts/themify.eot differ diff --git a/src/fonts/themify.svg b/src/fonts/themify.svg new file mode 100644 index 000000000..3d5385441 --- /dev/null +++ b/src/fonts/themify.svg @@ -0,0 +1,362 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/fonts/themify.ttf b/src/fonts/themify.ttf new file mode 100644 index 000000000..5d627e701 Binary files /dev/null and b/src/fonts/themify.ttf differ diff --git a/src/fonts/themify.woff b/src/fonts/themify.woff new file mode 100644 index 000000000..847ebd183 Binary files /dev/null and b/src/fonts/themify.woff differ diff --git a/src/img/icons/black.png b/src/img/icons/black.png new file mode 100644 index 000000000..f0821d62d Binary files /dev/null and b/src/img/icons/black.png differ diff --git a/src/img/icons/black@2.5x.png b/src/img/icons/black@2.5x.png new file mode 100644 index 000000000..1915e7d55 Binary files /dev/null and b/src/img/icons/black@2.5x.png differ diff --git a/src/img/icons/black@2x.png b/src/img/icons/black@2x.png new file mode 100644 index 000000000..80cad9297 Binary files /dev/null and b/src/img/icons/black@2x.png differ diff --git a/src/img/icons/black@3x.png b/src/img/icons/black@3x.png new file mode 100644 index 000000000..d70387837 Binary files /dev/null and b/src/img/icons/black@3x.png differ diff --git a/src/img/icons/ice.png b/src/img/icons/ice.png new file mode 100644 index 000000000..ebcc626ba Binary files /dev/null and b/src/img/icons/ice.png differ diff --git a/src/img/icons/ice@2.5x.png b/src/img/icons/ice@2.5x.png new file mode 100644 index 000000000..36181a34f Binary files /dev/null and b/src/img/icons/ice@2.5x.png differ diff --git a/src/img/icons/ice@2x.png b/src/img/icons/ice@2x.png new file mode 100644 index 000000000..15d8d258c Binary files /dev/null and b/src/img/icons/ice@2x.png differ diff --git a/src/img/icons/ice@3x.png b/src/img/icons/ice@3x.png new file mode 100644 index 000000000..b1e2ff141 Binary files /dev/null and b/src/img/icons/ice@3x.png differ diff --git a/src/img/ipfs-logo-black.png b/src/img/ipfs-logo-black.png new file mode 100644 index 000000000..88d3a1588 Binary files /dev/null and b/src/img/ipfs-logo-black.png differ diff --git a/src/img/ipfs-logo-ice.png b/src/img/ipfs-logo-ice.png new file mode 100644 index 000000000..0fa1909b6 Binary files /dev/null and b/src/img/ipfs-logo-ice.png differ diff --git a/src/img/jellyfish-blur.png b/src/img/jellyfish-blur.png deleted file mode 100644 index ae487d6b8..000000000 Binary files a/src/img/jellyfish-blur.png and /dev/null differ diff --git a/src/img/jellyfish-large.png b/src/img/jellyfish-large.png deleted file mode 100644 index f6a910b05..000000000 Binary files a/src/img/jellyfish-large.png and /dev/null differ diff --git a/src/img/map.svg b/src/img/map.svg new file mode 100644 index 000000000..e87e1c5f6 --- /dev/null +++ b/src/img/map.svg @@ -0,0 +1,190 @@ + + + + Group + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/img/space.jpg b/src/img/space.jpg new file mode 100644 index 000000000..968af0488 Binary files /dev/null and b/src/img/space.jpg differ diff --git a/src/img/stars.png b/src/img/stars.png deleted file mode 100644 index 14f0eae94..000000000 Binary files a/src/img/stars.png and /dev/null differ diff --git a/src/index.js b/src/index.js index 7f4f360b6..5d99fa0f2 100644 --- a/src/index.js +++ b/src/index.js @@ -2,15 +2,15 @@ import menubar from 'menubar' import fs from 'fs' import ipfsd from 'ipfsd-ctl' -import openConsole from './controls/open-console' -import openSettings from './controls/open-settings' -import openBrowser from './controls/open-browser' -import dragDrop from './controls/drag-drop' +import uploadFiles from './controls/upload-files' +import openWebUI from './controls/open-webui' +import openFileDialog from './controls/open-file-dialog' import {join} from 'path' -import {lookupPretty} from 'ipfs-geoip' -import config, {logger} from './config' -import {dialog, BrowserWindow, ipcMain, app} from 'electron' +import config, {logger, fileHistory, logoIpfsIce, logoIpfsBlack} from './config' +import {dialog, ipcMain, shell, app} from 'electron' + +import StatsPoller from './utils/stats-poller' // Handle creating/removing shortcuts on Windows when installing/uninstalling. if (require('electron-squirrel-startup')) { @@ -20,6 +20,7 @@ if (require('electron-squirrel-startup')) { process.on('uncaughtException', (error) => { const msg = error.message || error logger.error(`Uncaught Exception: ${msg}`, error) + dialog.showErrorBox('Uncaught Exception:', msg) process.exit(1) }) @@ -36,45 +37,38 @@ if (config.isProduction) { // Local Variables +let poller = null let IPFS let mb -let shouldPoll = false -const statsCache = {} - -function pollStats (ipfs) { - const next = () => { - setTimeout(() => { - pollStats(ipfs) - }, 1000) - } - if (!shouldPoll || !mb.window || !mb.window.isVisible()) { - return next() +function send (type, arg) { + if (mb && mb.window && mb.window.webContents) { + mb.window.webContents.send(type, arg) } +} - ipfs.swarm.peers() - .then((res) => { - statsCache.peers = res.length - mb.window.webContents.send('stats', statsCache) - }, (err) => { - logger.error(err.stack) - }) - .then(next) - - ipfs.id() - .then((peer) => { - lookupPretty(ipfs, peer.addresses, (err, location) => { - if (err) { - statsCache.location = 'Unknown' - mb.window.webContents.send('stats', statsCache) - return - } - - statsCache.location = location && location.formatted - mb.window.webContents.send('stats', statsCache) - }) - }) - .catch(logger.error) +function stopPolling () { + if (poller) poller.stop() +} + +function startPolling () { + if (poller) poller.start() +} + +function openSettings () { + shell.openExternal(join(config.ipfsPath, 'config')) +} + +function onPollerChange (stats) { + send('stats', stats) +} + +function onFileHistoryChange () { + send('files', fileHistory.toArray()) +} + +function onCloseWindow () { + mb.window.hide() } function onRequestState (node, event) { @@ -85,72 +79,67 @@ function onRequestState (node, event) { status = IPFS ? 'running' : 'starting' } - mb.window.webContents.send('node-status', status) + send('node-status', status) } } function onStartDaemon (node) { logger.info('Start daemon') - mb.window.webContents.send('node-status', 'starting') - node.startDaemon(function (err, ipfsNode) { + send('node-status', 'starting') + + node.startDaemon((err, ipfsNode) => { if (err) throw err - mb.window.webContents.send('node-status', 'running') + poller = new StatsPoller(ipfsNode, logger) + + if (mb.window && mb.window.isVisible()) { + poller.start() + } - shouldPoll = true - pollStats(ipfsNode) + poller.on('change', onPollerChange) + mb.on('show', startPolling) + mb.on('hide', stopPolling) - ipfsNode.version() - .then((res) => { - mb.window.webContents.send('version', res) - }) - .catch((err) => { - logger.error(err) - }) + mb.tray.setImage(logoIpfsIce) + send('node-status', 'running') IPFS = ipfsNode }) } function onStopDaemon (node, done) { logger.info('Stopping daemon') + send('node-status', 'stopping') - const send = (type, msg) => { - if (mb && mb.window && mb.window.webContents) { - mb.window.webContents.send(type, msg) - } + if (poller) { + poller.stop() + poller = null } - send('node-status', 'stopping') - if (shouldPoll) { - delete statsCache.peers - send('stats', statsCache) - shouldPoll = false + if (mb) { + mb.removeListener('show', startPolling) + mb.removeListener('hide', stopPolling) } node.stopDaemon((err) => { - if (err) return logger.error(err.stack) + if (err) { return logger.error(err.stack) } logger.info('Stopped daemon') + mb.tray.setImage(logoIpfsBlack) IPFS = null send('node-status', 'stopped') - send('stats', statsCache) done() }) } -function onCloseWindow () { - mb.window.hide() -} - function onWillQuit (node, event) { logger.info('Shutting down application') if (IPFS == null) return - // Try waiting for the daemon to properly shut down - // before we actually quit + // TODO: Try waiting for the daemon to properly + // shut down before we actually quit event.preventDefault() @@ -162,39 +151,44 @@ function onWillQuit (node, event) { function startTray (node) { logger.info('Starting tray') + // Update File History on change and when it is requested. + ipcMain.on('request-files', onFileHistoryChange) + fileHistory.on('change', onFileHistoryChange) + ipcMain.on('request-state', onRequestState.bind(null, node)) ipcMain.on('start-daemon', onStartDaemon.bind(null, node)) ipcMain.on('stop-daemon', onStopDaemon.bind(null, node, () => {})) - ipcMain.on('drop-files', dragDrop.bind(null)) + ipcMain.on('drop-files', uploadFiles.bind(null, getIPFS)) ipcMain.on('close-tray-window', onCloseWindow) + ipcMain.on('open-webui', openWebUI.bind(null, getIPFS)) ipcMain.on('open-settings', openSettings) - ipcMain.on('open-console', openConsole) - ipcMain.on('open-browser', openBrowser) mb.app.once('will-quit', onWillQuit.bind(null, node)) + + ipcMain.on('open-file-dialog', openFileDialog(mb.window, getIPFS)) + ipcMain.on('open-dir-dialog', openFileDialog(mb.window, getIPFS, true)) } // Initalize a new IPFS node function initialize (path, node) { logger.info('Initialzing new node') - const welcomeWindow = new BrowserWindow(config.window) - - welcomeWindow.loadURL(config.urls.welcome) - welcomeWindow.webContents.on('did-finish-load', () => { - welcomeWindow.webContents.send('setup-config-path', path) + mb.window.loadURL(config.urls.welcome) + mb.window.webContents.on('did-finish-load', () => { + send('setup-config-path', path) }) + mb.showWindow() // Close the application if the welcome dialog is canceled - welcomeWindow.once('close', () => { + mb.window.once('close', () => { if (!node.initialized) mb.app.quit() }) let userPath = path ipcMain.on('setup-browse-path', () => { - dialog.showOpenDialog(welcomeWindow, { + dialog.showOpenDialog(mb.window, { title: 'Select a directory', defaultPath: path, properties: [ @@ -210,7 +204,7 @@ function initialize (path, node) { userPath = join(userPath, '.ipfs') } - welcomeWindow.webContents.send('setup-config-path', userPath) + send('setup-config-path', userPath) }) }) @@ -218,23 +212,22 @@ function initialize (path, node) { ipcMain.on('initialize', (event, { keySize }) => { logger.info(`Initializing new node with key size: ${keySize} in ${userPath}.`) - welcomeWindow.webContents.send('initializing') + send('initializing') node.init({ directory: userPath, - keySize + keySize: keySize }, (err, res) => { if (err) { - return welcomeWindow.webContents.send('initialization-error', String(err)) + return send('initialization-error', String(err)) } fs.writeFileSync(config.ipfsPathFile, path) - welcomeWindow.webContents.send('initialization-complete') - welcomeWindow.webContents.send('node-status', 'stopped') + send('initialization-complete') + send('node-status', 'stopped') - welcomeWindow.close() onStartDaemon(node) - mb.showWindow() + mb.window.loadURL(config.menubar.index) }) }) } @@ -262,15 +255,13 @@ ipfsd.local((err, node) => { mb = menubar(config.menubar) - // Ensure single instance + // Ensure single instance. mb.app.makeSingleInstance(reboot) mb.on('ready', () => { logger.info('Application is ready') - // tray actions - - mb.tray.on('drop-files', dragDrop) + mb.tray.on('drop-files', uploadFiles.bind(null, getIPFS)) mb.tray.setHighlightMode(true) startTray(node) diff --git a/src/js/components/view/directory-input.js b/src/js/components/view/directory-input.js index 1e912e7af..fecb57476 100644 --- a/src/js/components/view/directory-input.js +++ b/src/js/components/view/directory-input.js @@ -4,34 +4,6 @@ import {ipcRenderer} from 'electron' import Icon from './icon' -const styles = { - base: { - display: 'flex', - width: '300px' - }, - icon: { - color: '#FFFFFF', - fontSize: '20px', - marginLeft: '20px', - marginTop: '10px', - position: 'absolute' - }, - button: { - flex: '1', - background: 'rgba(255, 255, 255, 0.15)', - fontSize: '12px', - borderRadius: '25px', - padding: '15px 40px 15px 65px', - border: 'none', - textAlign: 'left', - transition: 'background 0.3s ease-in-out', - ':hover': { - background: 'rgba(255, 255, 255, 0.35)', - cursor: 'pointer' - } - } -} - const onClick = (e) => { e.preventDefault() ipcRenderer.send('setup-browse-path') @@ -39,12 +11,9 @@ const onClick = (e) => { export default function DirectoryInput (props) { return ( -
- - + diff --git a/src/js/components/view/file.js b/src/js/components/view/file.js new file mode 100644 index 000000000..70795de3c --- /dev/null +++ b/src/js/components/view/file.js @@ -0,0 +1,70 @@ +import React from 'react' +import PropTypes from 'prop-types' +import {clipboard} from 'electron' +import moment from 'moment' +import fileExtension from 'file-extension' + +import TextButton from './text-button' +import Icon from './icon' + +export default function File (props) { + const extension = fileExtension(props.name) + let icon = 'file' + if (fileTypes[extension]) { + icon = fileTypes[extension] + } + + return ( +
+
+
+ +
+
+

{props.name}

+

{moment(props.date).fromNow()}

+
+
+ { + clipboard.writeText(`https://ipfs.io/ipfs/${props.hash}`) + }} /> +
+
+
+ ) +} + +File.propTypes = { + name: PropTypes.string.isRequired, + date: PropTypes.string.isRequired, + hash: PropTypes.string.isRequired +} + +const fileTypes = { + png: 'image', + jpg: 'image', + tif: 'image', + tiff: 'image', + bmp: 'image', + gif: 'image', + eps: 'image', + raw: 'image', + cr2: 'image', + nef: 'image', + orf: 'image', + sr2: 'image', + jpeg: 'image', + mp3: 'music-alt', + flac: 'music-alt', + ogg: 'music-alt', + oga: 'music-alt', + aa: 'music-alt', + aac: 'music-alt', + m4p: 'music-alt', + webm: 'music-alt', + mp4: 'video-clapper', + mkv: 'video-clapper', + avi: 'video-clapper', + asf: 'video-clapper', + flv: 'video-clapper' +} diff --git a/src/js/components/view/footer.js b/src/js/components/view/footer.js new file mode 100644 index 000000000..0748cdb89 --- /dev/null +++ b/src/js/components/view/footer.js @@ -0,0 +1,14 @@ +import React from 'react' +import PropTypes from 'prop-types' + +export default function Footer (props) { + return ( +
+ {props.children} +
+ ) +} + +Footer.propTypes = { + children: PropTypes.node.isRequired +} diff --git a/src/js/components/view/header.js b/src/js/components/view/header.js index 349e1776d..63f9854a1 100644 --- a/src/js/components/view/header.js +++ b/src/js/components/view/header.js @@ -1,47 +1,29 @@ import React from 'react' import PropTypes from 'prop-types' -import IconButton from './icon-button' -import IPFSLogo from './ipfs-logo' - -const styles = { - wrapper: { - display: 'flex', - height: '40px' - }, - text: { - alignSelf: 'center', - flex: '1', - paddingTop: '4px' - }, - stopButton: { - background: 'none', - border: 'none', - position: 'absolute', - top: '11px', - right: '0' - } -} - export default function Header (props) { return ( -
-
- +
+
+

{props.title}

+ { props.subtitle !== '' && +

{props.subtitle}

+ } +
+
+ {props.children}
-
) } Header.propTypes = { - onCloseClick: PropTypes.func.isRequired + title: PropTypes.string.isRequired, + children: PropTypes.node, + subtitle: PropTypes.string } Header.defaultProps = { - onCloseClick: () => {} + title: '', + subtitle: '' } diff --git a/src/js/components/view/heartbeat.js b/src/js/components/view/heartbeat.js new file mode 100644 index 000000000..b0139a6e4 --- /dev/null +++ b/src/js/components/view/heartbeat.js @@ -0,0 +1,10 @@ +import React from 'react' +import {resolve, join} from 'path' + +export const logo = resolve(join(__dirname, '../../../img/ipfs-logo-ice.png')) + +export default function Heartbeat () { + return ( + + ) +} diff --git a/src/js/components/view/icon-button.js b/src/js/components/view/icon-button.js index c9b1cf83b..e8ff9d32f 100644 --- a/src/js/components/view/icon-button.js +++ b/src/js/components/view/icon-button.js @@ -1,52 +1,22 @@ import React from 'react' import PropTypes from 'prop-types' -export default function IconButton (props) { - const styles = { - button: { - color: 'rgba(255, 255, 255, 0.8)', - background: 'none', - border: 'none', - flex: '1', - padding: '0 10px', - textAlign: 'center', - fontSize: '12px', - textTransform: 'uppercase', - transition: 'color 0.3s ease-in-out', - cursor: 'pointer', - ':focus': { - outline: 'none' - }, - ':hover': { - color: 'rgba(255, 255, 255, 1)', - cursor: 'pointer' - }, - ...props.style - } - } +import Icon from './icon' +export default function IconButton (props) { return ( - ) } IconButton.propTypes = { - name: PropTypes.string, icon: PropTypes.string, - onClick: PropTypes.func, - style: PropTypes.object, - iconStyle: PropTypes.object + onClick: PropTypes.func } IconButton.defaultProps = { - name: null, icon: '', - onClick () {}, - style: {}, - iconStyle: {} + onClick () {} } diff --git a/src/js/components/view/icon-dropdown-list.js b/src/js/components/view/icon-dropdown-list.js index e6a9ccf0e..7ec9bf06f 100644 --- a/src/js/components/view/icon-dropdown-list.js +++ b/src/js/components/view/icon-dropdown-list.js @@ -1,28 +1,27 @@ import React from 'react' import PropTypes from 'prop-types' -import {DropdownList} from 'react-widgets' import Icon from './icon' -const styles = { - icon: { - position: 'absolute', - marginTop: '10px', - marginLeft: '20px', - fontSize: '20px' +function onChangeWrapper (fn) { + return event => { + return fn(event.target.value) } } export default function IconDropdownList (props) { + let options = props.data.map(el => { + return () + }) + return ( -
- - + +
) } diff --git a/src/js/components/view/icon.js b/src/js/components/view/icon.js index f6f5ff8d1..c92f01823 100644 --- a/src/js/components/view/icon.js +++ b/src/js/components/view/icon.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' export default function Icon (props) { return ( -
+ ) } diff --git a/src/js/components/view/info-block.js b/src/js/components/view/info-block.js new file mode 100644 index 000000000..59ac357a4 --- /dev/null +++ b/src/js/components/view/info-block.js @@ -0,0 +1,69 @@ +import React from 'react' +import PropTypes from 'prop-types' + +import TextButton from '../view/text-button' + +export default function InfoBlock (props) { + let info = null + + if (props.pre) { + if (Array.isArray(props.info)) { + info = [] + props.info.forEach((element, index) => { + info.push((
{element}
)) + }) + } else { + info = (
{props.info}
) + } + } else { + if (Array.isArray(props.info)) { + info = [] + props.info.forEach((element, index) => { + info.push((

{element}

)) + }) + } else { + info = (

{props.info}

) + } + } + + let button = null + + if (props.onClick) { + if (props.button) { + button = (
+ +
) + } + } + + let clickable = props.onClick && !props.button + + return ( +
+
+

{props.children}

+ {info} + {button} +
+
+ ) +} + +InfoBlock.propTypes = { + info: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.array + ]), + children: PropTypes.node.isRequired, + onClick: PropTypes.func, + button: PropTypes.bool, + buttonMessage: PropTypes.string, + pre: PropTypes.bool +} + +InfoBlock.defaultProps = { + pre: false, + button: true, + buttonMessage: 'Copy', + onClick: null +} diff --git a/src/js/components/view/ipfs-logo.js b/src/js/components/view/ipfs-logo.js deleted file mode 100644 index bb3ee2cd6..000000000 --- a/src/js/components/view/ipfs-logo.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react' -import {logoWiderStrokes} from '../../../utils/logo' - -const styles = { - wrapper: { - display: 'flex' - }, - image: { - marginRight: '5px' - } -} - -export default function IPFSLogo () { - return ( -
- - IPFS -
- ) -} diff --git a/src/js/components/view/peer.js b/src/js/components/view/peer.js new file mode 100644 index 000000000..de1e7a6d4 --- /dev/null +++ b/src/js/components/view/peer.js @@ -0,0 +1,19 @@ +import React from 'react' +import PropTypes from 'prop-types' + +export default function Peer (props) { + return ( +
+

{props.id}

+

{props.location.formatted}

+
+ ) +} + +Peer.propTypes = { + id: PropTypes.string.isRequired, + /* addr: PropTypes.string.isRequired, */ + location: PropTypes.shape({ + formatted: PropTypes.string.isRequired + }).isRequired +} diff --git a/src/js/components/view/text-button.js b/src/js/components/view/text-button.js new file mode 100644 index 000000000..e54f03af0 --- /dev/null +++ b/src/js/components/view/text-button.js @@ -0,0 +1,15 @@ +import React from 'react' +import PropTypes from 'prop-types' + +export default function TextButton (props) { + return ( + + ) +} + +TextButton.propTypes = { + text: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired +} diff --git a/src/js/screens/menu.js b/src/js/screens/menu.js index e289df3c0..0325007df 100644 --- a/src/js/screens/menu.js +++ b/src/js/screens/menu.js @@ -5,8 +5,11 @@ import {DragDropContext} from 'react-dnd' import HTML5Backend from 'react-dnd-html5-backend' import StartScreen from './menu/start' -import ProfileScreen from './menu/profile' +import FilesScreen from './menu/files' +import PeersScreen from './menu/peers' +import NodeInfoScreen from './menu/node-info' import Loader from '../components/view/loader' +import {PAGES} from '../../constants' const UNINITIALIZED = 'uninitialized' const RUNNING = 'running' @@ -16,19 +19,13 @@ const STOPPING = 'stopping' class Menu extends Component { state = { status: UNINITIALIZED, + route: PAGES.FILES, connected: false, version: null, stats: {} } - // -- Event Listeners - - _onVersion = (event, arg) => { - this.setState({version: arg}) - } - _onNodeStatus = (event, status) => { - console.log(status) this.setState({status: status}) } @@ -36,73 +33,72 @@ class Menu extends Component { this.setState({stats: stats}) } - _startDaemon () { - console.log('starting daemon') - ipcRenderer.send('start-daemon') - } - - _stopDaemon () { - ipcRenderer.send('stop-daemon') - } - - _closeWindow () { - ipcRenderer.send('close-tray-window') - } - - _openConsole () { - ipcRenderer.send('open-console') - } - - _openBrowser () { - ipcRenderer.send('open-browser') + _onFiles = (event, files) => { + this.setState({files: files}) } - _openSettings () { - ipcRenderer.send('open-settings') + _changeRoute = (route) => { + this.setState({route: route}) } componentDidMount () { // -- Listen to control events - ipcRenderer.on('version', this._onVersion) ipcRenderer.on('node-status', this._onNodeStatus) ipcRenderer.on('stats', this._onStats) + ipcRenderer.on('files', this._onFiles) ipcRenderer.send('request-state') + ipcRenderer.send('request-files') } componentWillUnmount () { // -- Remove control events - ipcRenderer.removeListener('version', this._onVersion) ipcRenderer.removeListener('node-status', this._onNodeStatus) ipcRenderer.removeListener('stats', this._onStats) - ipcRenderer.removeListener('uploading', this._onUploading) - ipcRenderer.removeListener('uploaded', this._onUploaded) + ipcRenderer.removeListener('files', this._onFiles) + } + + _getRouteScreen () { + switch (this.state.route) { + case PAGES.FILES: + return ( + + ) + case PAGES.INFO: + return ( + + ) + default: + return null + } } _getScreen () { switch (this.state.status) { case RUNNING: return ( - +
+
+ {this._getRouteScreen()} +
+
+ +
+
) case STARTING: case STOPPING: return default: return ( - + ) } } diff --git a/src/js/screens/menu/files.js b/src/js/screens/menu/files.js new file mode 100644 index 000000000..51d88046d --- /dev/null +++ b/src/js/screens/menu/files.js @@ -0,0 +1,87 @@ +import React, {Component} from 'react' +import PropTypes from 'prop-types' +import {ipcRenderer} from 'electron' +import {NativeTypes} from 'react-dnd-html5-backend' +import {DropTarget} from 'react-dnd' + +import Header from '../../components/view/header' +import Footer from '../../components/view/footer' +import File from '../../components/view/file' +import IconButton from '../../components/view/icon-button' + +const fileTarget = { + drop (props, monitor) { + const files = monitor.getItem().files + const filesArray = [] + for (let i = 0; i < files.length; i++) { + filesArray.push(files[i].path) + } + + ipcRenderer.send('drop-files', filesArray) + } +} + +class FilesScreen extends Component { + static propTypes = { + connectDropTarget: PropTypes.func.isRequired, + changeRoute: PropTypes.func.isRequired, + isOver: PropTypes.bool.isRequired, + canDrop: PropTypes.bool.isRequired, + files: PropTypes.array + } + + static defaultProps = { + files: [], + onConsoleClick () {}, + onBrowserClick () {} + } + + _selectFileDialog (event) { + ipcRenderer.send('open-file-dialog') + } + + _selectDirectoryDialog (event) { + ipcRenderer.send('open-dir-dialog') + } + + render () { + const {connectDropTarget, isOver, canDrop} = this.props + + const dropper = { + visibility: (isOver && canDrop) ? 'visible' : 'hidden' + } + + const files = this.props.files.map(file => { + return () + }) + + return connectDropTarget( +
+
+ +
+ {files} +
+ +
+ Drop to upload to IPFS +
+ +
+ { this.props.changeRoute('info') }} icon='pulse' /> + +
+ + +
+
+
+ ) + } +} + +export default DropTarget(NativeTypes.FILE, fileTarget, (connect, monitor) => ({ + connectDropTarget: connect.dropTarget(), + isOver: monitor.isOver(), + canDrop: monitor.canDrop() +}))(FilesScreen) diff --git a/src/js/screens/menu/node-info.js b/src/js/screens/menu/node-info.js new file mode 100644 index 000000000..f12cca3a9 --- /dev/null +++ b/src/js/screens/menu/node-info.js @@ -0,0 +1,98 @@ +import React from 'react' +import PropTypes from 'prop-types' +import {clipboard, ipcRenderer} from 'electron' +import prettyBytes from 'pretty-bytes' + +import Heartbeat from '../../components/view/heartbeat' +import Header from '../../components/view/header' +import Footer from '../../components/view/footer' +import IconButton from '../../components/view/icon-button' +import InfoBlock from '../../components/view/info-block' + +function onClickCopy (text) { + return () => clipboard.writeText(text) +} + +function openSettings () { + ipcRenderer.send('open-settings') +} + +function openWebUI () { + ipcRenderer.send('open-webui') +} + +function stopDaemon () { + ipcRenderer.send('stop-daemon') +} + +export default function NodeScreen (props) { + return ( +
+
+ +
+ +
+
+

{prettyBytes(props.repo.RepoSize)}

+

Sharing {props.repo.NumObjects} objects

+
+ + + Peer Id + + + + Location + + + + Protocol Version + + + + Addresses + + + + Settings + + + + Open WebUI + + + + Public Key + +
+ +
+
+ +
+
+
+ ) +} + +NodeScreen.propTypes = { + id: PropTypes.string, + location: PropTypes.string, + protocolVersion: PropTypes.string, + publicKey: PropTypes.string, + addresses: PropTypes.array, + repo: PropTypes.object +} + +NodeScreen.defaultProps = { + id: 'Undefined', + location: 'Unknown', + protocolVersion: 'Undefined', + publicKey: 'Undefined', + addresses: [], + repo: { + RepoSize: 0, + NumObjects: 'NA' + } +} diff --git a/src/js/screens/menu/peers.js b/src/js/screens/menu/peers.js new file mode 100644 index 000000000..d204b66e0 --- /dev/null +++ b/src/js/screens/menu/peers.js @@ -0,0 +1,62 @@ +import React, {Component} from 'react' +import PropTypes from 'prop-types' + +import Peer from '../../components/view/peer' +import Header from '../../components/view/header' +import Footer from '../../components/view/footer' +import IconButton from '../../components/view/icon-button' + +export default class PeersScreen extends Component { + constructor (props) { + super(props) + this.state = { search: null } + } + + onChangeSearch = event => { + this.setState({ search: event.target.value.toLowerCase() }) + } + + render () { + var peers = this.props.peers + + if (this.state.search !== null && this.state.search !== '') { + peers = peers.filter(peer => { + return peer.id.toLowerCase().indexOf(this.state.search) > -1 + }) + } + + peers = peers.map((peer, i) => { + return () + }) + + return ( +
+
+
+ {peers} +
+ +
+ { this.props.changeRoute('files') }} icon='files' /> + +
+ +
+
+
+ ) + } +} + +PeersScreen.propTypes = { + location: PropTypes.string, + peers: PropTypes.array, + changeRoute: PropTypes.func.isRequired +} + +PeersScreen.defaultProps = { + location: 'Unknown', + peers: [] +} diff --git a/src/js/screens/menu/profile.js b/src/js/screens/menu/profile.js deleted file mode 100644 index 55f45b8bc..000000000 --- a/src/js/screens/menu/profile.js +++ /dev/null @@ -1,153 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import {ipcRenderer} from 'electron' -import {NativeTypes} from 'react-dnd-html5-backend' -import {DropTarget} from 'react-dnd' - -import SimpleStat from '../../components/view/simple-stat' -import IconButton from '../../components/view/icon-button' -import Header from '../../components/view/header' -import Icon from '../../components/view/icon' - -const fileTarget = { - drop (props, monitor) { - const files = monitor.getItem().files - const filesArray = [] - for (let i = 0; i < files.length; i++) { - filesArray.push(files[i].path) - } - - ipcRenderer.send('drop-files', filesArray) - } -} - -class ProfileScreen extends Component { - static propTypes = { - peers: PropTypes.number, - location: PropTypes.string, - onStopClick: PropTypes.func, - onConsoleClick: PropTypes.func, - onBrowserClick: PropTypes.func, - onCloseClick: PropTypes.func, - connectDropTarget: PropTypes.func.isRequired, - isOver: PropTypes.bool.isRequired, - canDrop: PropTypes.bool.isRequired - } - - static defaultProps = { - peers: 0, - location: '', - onStopClick () {}, - onConsoleClick () {}, - onBrowserClick () {}, - onSettingsClick () {}, - onCloseClick () {} - } - - render () { - const {connectDropTarget, isOver, canDrop} = this.props - - const styles = { - dropper: { - visibility: (isOver && canDrop) ? 'visible' : 'hidden', - background: 'rgba(255, 255, 255, 0.8)', - position: 'absolute', - top: '40px', - left: 0, - right: 0, - bottom: '70px', - height: '100%', - width: '100%', - color: '#000000', - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center' - }, - wrapper: { - display: 'flex', - width: '100%', - height: '100%', - backgroundColor: '#19b5fe', - color: '#FFFFFF', - flexDirection: 'column', - alignItems: 'center' - }, - image: { - display: 'flex', - flex: '1', - color: '#FFFFFF', - backgroundImage: `url('../img/stars.png')`, - backgroundSize: 'cover', - backgroundPosition: '0 0', - width: '100%', - alignItems: 'center', - justifyContent: 'center', - flexDirection: 'column' - }, - stats: { - display: 'flex', - flex: '1', - backgroundColor: '#FFFFFF', - color: '#000000', - width: '100%', - height: '30%', - justifyContent: 'space-around' - }, - footer: { - display: 'flex', - height: '70px', - justifyContent: 'space-around', - width: '100%' - } - } - - return connectDropTarget( -
-
- Drop to upload to IPFS -
-
-
- -
- {this.props.location} -
-
-
- -
-
- - - -
-
- ) - } -} - -export default DropTarget(NativeTypes.FILE, fileTarget, (connect, monitor) => ({ - connectDropTarget: connect.dropTarget(), - isOver: monitor.isOver(), - canDrop: monitor.canDrop() -}))(ProfileScreen) diff --git a/src/js/screens/menu/start.js b/src/js/screens/menu/start.js index ab089276d..28506ef51 100644 --- a/src/js/screens/menu/start.js +++ b/src/js/screens/menu/start.js @@ -1,64 +1,52 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' +import React from 'react' +import {ipcRenderer} from 'electron' -import Button from '../../components/view/button' -import Header from '../../components/view/header' +import TextButton from '../../components/view/text-button' -export default class StartScreen extends Component { - static propTypes = { - onStartClick: PropTypes.func, - onCloseClick: PropTypes.func - } +function startDaemon () { + ipcRenderer.send('start-daemon') +} - static defaultProps = { - onStartClick () {}, - onCloseClick () {} +const styles = { + wrapper: { + display: 'flex', + width: '100%', + height: '100%', + backgroundColor: '#252525', + color: '#FFFFFF', + flexDirection: 'column', + alignItems: 'center', + padding: '0 20 20' + }, + content: { + display: 'flex', + flex: '1', + margin: '40px 0', + flexDirection: 'column' + }, + text: { + padding: '40px 0', + textAlign: 'center' } +} - render () { - const styles = { - wrapper: { - display: 'flex', - width: '100%', - height: '100%', - backgroundColor: '#19b5fe', - color: '#FFFFFF', - flexDirection: 'column', - alignItems: 'center', - padding: '0 20 20' - }, - content: { - display: 'flex', - flex: '1', - margin: '40px 0', - flexDirection: 'column' - }, - text: { - padding: '40px 0', - textAlign: 'center' - } - } - - return ( -
-
-
- -
- Oh snap, it looks like your node
- is not running yet. Let’s change
- that by clicking that button -
+export default function StartScreen () { + return ( +
+
+ +
+ Oh snap, it looks like your node
+ is not running yet. Let’s change
+ that by clicking that button
-
- ) - } + +
+ ) } diff --git a/src/js/screens/settings.js b/src/js/screens/settings.js deleted file mode 100644 index 035bb0964..000000000 --- a/src/js/screens/settings.js +++ /dev/null @@ -1,11 +0,0 @@ -import React, {Component} from 'react' - -export default class Settings extends Component { - render () { - return ( -
- Settings -
- ) - } -} diff --git a/src/js/screens/setup.js b/src/js/screens/setup.js index 870e41a9e..a489af4a2 100644 --- a/src/js/screens/setup.js +++ b/src/js/screens/setup.js @@ -3,7 +3,6 @@ import {CSSTransition} from 'react-transition-group' import {ipcRenderer} from 'electron' import Intro from './setup/intro' -import Advanced from './setup/advanced' import Loader from '../components/view/loader' const INTRO = 'intro' @@ -63,25 +62,17 @@ export default class Setup extends Component { _getScreen () { switch (this.state.status) { case INTRO: + case ADVANCED: return ( - ) - case ADVANCED: { - return ( - ) - } case ERROR: return (
{this.state.error}
diff --git a/src/js/screens/setup/advanced.js b/src/js/screens/setup/advanced.js deleted file mode 100644 index dd143bf85..000000000 --- a/src/js/screens/setup/advanced.js +++ /dev/null @@ -1,84 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' - -import Icon from '../../components/view/icon' -import Button from '../../components/view/button' -import DirectoryInput from '../../components/view/directory-input' -import IconDropdownList from '../../components/view/icon-dropdown-list' - -export default class Intro extends Component { - static propTypes = { - onInstallClick: PropTypes.func, - configPath: PropTypes.string, - keySizes: PropTypes.arrayOf(PropTypes.number), - keySize: PropTypes.number, - onKeySizeChange: PropTypes.func - } - - static defaultProps = { - onInstallClick () {}, - configPath: '', - keySizes: [], - keySize: 0, - onKeySizeChange () {} - } - - render () { - const styles = { - base: { - display: 'flex', - width: '100%', - height: '100%', - backgroundImage: `url('../img/jellyfish-blur.png')`, - backgroundSize: '100%', - backgroundPosition: '0 0', - color: '#FFFFFF', - flexDirection: 'column', - alignItems: 'center', - padding: '20', - justifyContent: 'space-between' - }, - headline: { - textAlign: 'center', - marginBottom: '20px', - fontSize: '38px' - }, - inputs: { - flex: '0 0 110px', - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between' - }, - button: { - width: '300px', - marginBottom: '30px' - }, - icon: { - display: 'inline', - color: '#19b5fe', - marginRight: '10px', - fontSize: '20px' - } - } - - return ( -
- - Advanced Options - -
- - -
- -
- ) - } -} diff --git a/src/js/screens/setup/intro.js b/src/js/screens/setup/intro.js index 9cc0888d0..f7ef65a03 100644 --- a/src/js/screens/setup/intro.js +++ b/src/js/screens/setup/intro.js @@ -1,81 +1,74 @@ import React, {Component} from 'react' import PropTypes from 'prop-types' -import Icon from '../../components/view/icon' -import Button from '../../components/view/button' +import Heartbeat from '../../components/view/heartbeat' +import TextButton from '../../components/view/text-button' +import DirectoryInput from '../../components/view/directory-input' +import IconDropdownList from '../../components/view/icon-dropdown-list' -const styles = { - base: { - display: 'flex', - width: '100%', - height: '100%', - backgroundImage: `url('../img/jellyfish-large.png')`, - backgroundSize: 'cover', - backgroundPosition: 'center center', - color: '#FFFFFF', - flexDirection: 'column', - alignItems: 'center', - padding: '20', - justifyContent: 'space-between' - }, - headline: { - textAlign: 'center', - marginBottom: '20px', - fontSize: '38px' - }, - actions: { - flex: '0 0 auto', - display: 'flex', - flexDirection: 'column', - alignItems: 'center' - }, - button: { - width: '300px', - marginBottom: '30px' - }, - icon: { - display: 'inline', - color: '#19b5fe', - marginRight: '10px', - fontSize: '20px' - }, - advancedLink: { - cursor: 'pointer', - color: 'rgba(255, 255, 255, 0.5)', - transition: 'color 0.3s ease-in-out', - ':hover': { - color: 'rgba(255, 255, 255, 1)' - } +export default class Intro extends Component { + constructor (props) { + super(props) + this.state = {showAdvanced: false} } -} -export default class Intro extends Component { static propTypes = { onInstallClick: PropTypes.func, - onAdvancedClick: PropTypes.func + configPath: PropTypes.string, + keySizes: PropTypes.arrayOf(PropTypes.number), + keySize: PropTypes.number, + onKeySizeChange: PropTypes.func } static defaultProps = { onInstallClick () {}, - onAdvancedClick () {} + configPath: '', + keySizes: [], + keySize: 0, + onKeySizeChange () {} + } + + onAdvancedClick = () => { + this.setState({ showAdvanced: true }) } render () { + let advanced = null + if (this.state.showAdvanced) { + advanced = ([ + , + + ]) + } + return ( -
- - Welcome to IPFS - -
- - - Advanced Options - +
+
+ +
+
+
+
+

Welcome to the Distributed Web

+

You are about to install IPFS, the InterPlanetary File System.

+
+
+ {advanced} +
+
+ + { !this.state.showAdvanced && + + Advanced Options + + } +
+
) diff --git a/src/js/settings.js b/src/js/settings.js deleted file mode 100644 index d280b2c23..000000000 --- a/src/js/settings.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react' -import {render} from 'react-dom' - -import Settings from './screens/settings' - -document.addEventListener('DOMContentLoaded', () => { - render(, document.getElementById('settings')) -}) diff --git a/src/styles/animations.less b/src/styles/animations.less index f5a3f824b..f4829bf94 100644 --- a/src/styles/animations.less +++ b/src/styles/animations.less @@ -15,3 +15,33 @@ opacity: 0.01; transition: opacity .2s ease-in; } + +@keyframes heartbeat { + 0% { + transform: scale(1); + } + 5% { + transform: scale(1.05); + filter: drop-shadow(0 0 1.05em rgba(95, 203, 207, 0.5)); + } + 10% { + transform: scale(1.025); + filter: drop-shadow(0 0 1.025em rgba(95, 203, 207, 0.25)); + } + 15% { + transform: scale(1.075); + filter: drop-shadow(0 0 1.075em rgba(95, 203, 207, 0.5)); + } + 50% { + transform: scale(1); + } + 100% { + transform: scale(1); + } +} + +.heartbeat { + animation-name: heartbeat; + animation-iteration-count: infinite; + animation-duration: 2.5s; +} \ No newline at end of file diff --git a/src/styles/app.less b/src/styles/app.less index 21f769b4f..c509d785c 100644 --- a/src/styles/app.less +++ b/src/styles/app.less @@ -1,6 +1,11 @@ @import '../../node_modules/normalize.css/normalize.css'; -@import "./animations.less"; -@import "./fonts.less"; -@import "./common.less"; +@import './animations.less'; +@import './fonts.less'; +@import './themify-icons.less'; +@import './common.less'; @import './loader.less'; -@import './react-widgets-theme.less'; +@import './panel.less'; +@import './info-block.less'; +@import './peers.less'; +@import './welcome.less'; +@import './files.less'; diff --git a/src/styles/common.less b/src/styles/common.less index 8f3f5c655..1749a9e90 100644 --- a/src/styles/common.less +++ b/src/styles/common.less @@ -1,3 +1,5 @@ +@import './variables.less'; + *, *:before, *:after { @@ -6,6 +8,134 @@ html, body { - font-family: 'Maven Pro', sans-serif; + font-family: 'SF Pro Text', sans-serif; overflow: hidden; -} \ No newline at end of file + color: #FFF; + background: #252525; +} + +::-webkit-scrollbar { + background-color: transparent; + width: .5em; +} + +::-webkit-scrollbar-thumb:window-inactive, +::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.3); + border-radius: 0.5em; +} + +.dropdown-list, +.directory-input, +.button-text, +input[type="text"] { + border-radius: 0.2em; + padding: 0.5em 1em; + border: 0; + color: #fff; + background: rgba(255, 255, 255, 0.25); + outline: 0; + transition: .2s ease all; +} + +.button-text { + cursor: pointer; + + &:hover { + background: rgba(255, 255, 255, 0.1); + } + + span { + font-size: @normal-font; + } +} + +.directory-input { + cursor: pointer; + + .icon { + margin-right: 0.5em; + } + + a { + font-size: @normal-font; + color: rgba(255, 255, 255, 0.4); + } +} + +.directory-input, +.dropdown-list { + display: flex; + align-items: center; +} + +.dropdown-list { + cursor: pointer; + margin: 1em 0; + + .icon { + margin-right: 0.5em; + } + + select { + padding: 0; + border: none; + box-shadow: none; + background: transparent; + background-image: none; + -webkit-appearance: none; + font-size: 14px; + cursor: pointer; + color: rgba(255, 255, 255, 0.4); + font-family: inherit; + line-height: inherit; + width: 100%; + } + + option { + color: #000 + } + + select:focus { + outline: none; + } +} + +.node { + .sharing { + padding: 0 1em 1em; + + p { + margin: 0; + } + + p:first-of-type { + font-size: @big-font; + } + + p:last-of-type { + font-size: @medium-font; + color: rgba(255, 255, 255, 0.5); + } + } + + .heartbeat { + width: 1.15em; + } +} + +.button-icon { + line-height: 1; + border: 0; + outline: 0; + padding: 0; + background: transparent; + color: rgba(255, 255, 255, 0.4); + cursor: pointer; + margin-left: 0.5em; + transition: .2s ease all; + + &:hover { + color: rgba(255, 255, 255, 0.6); + } +} diff --git a/src/styles/files.less b/src/styles/files.less new file mode 100644 index 000000000..ac314ec4d --- /dev/null +++ b/src/styles/files.less @@ -0,0 +1,18 @@ +.files { + .dropper { + visibility: hidden; + position: fixed; + z-index: 4; + top: 0; + left: 0; + bottom: 0; + height: 100%; + width: 550px; + background: rgba(0,0,0,0.8); + color: #fff; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } +} diff --git a/src/styles/fonts.less b/src/styles/fonts.less index f564fa39f..dd0784694 100644 --- a/src/styles/fonts.less +++ b/src/styles/fonts.less @@ -1,442 +1,15 @@ @charset 'UTF-8'; @font-face { - font-family: 'Maven Pro'; - src: url('../fonts/maven_pro_bold-webfont.ttf') format('truetype'); - font-weight: bold; - font-style: normal; -} - -@font-face { - font-family: 'Maven Pro'; - src: url('../fonts/maven_pro_medium-webfont.ttf') format('truetype'); - font-weight: 500; - font-style: normal; - -} - -@font-face { - font-family: 'Maven Pro'; - src: url('../fonts/maven_pro_regular-webfont.ttf') format('truetype'); + font-family: 'SF Pro Text'; + src: url('../fonts/sf-pro-text-light.otf') format('opentype'); font-weight: normal; font-style: normal; } @font-face { - font-family: 'dripicons'; - src: url('../fonts/dripicons.ttf') format('truetype'); - font-weight: normal; - font-style: normal; - -} - -[data-icon]:before { - font-family: 'dripicons'; - content: attr(data-icon); - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none !important; - speak: none; - display: inline-block; - text-decoration: none; - width: 1em; - line-height: 1em; - -webkit-font-smoothing: antialiased; -} - -[class^='icon-']:before, -[class*=' icon-']:before { - font-family: 'dripicons'; + font-family: 'SF Pro Text'; + src: url('../fonts/sf-pro-text-regular.otf') format('opentype'); + font-weight: bold; font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none !important; - speak: none; - display: inline-block; - text-decoration: none; - width: 1em; - line-height: 1em; - -webkit-font-smoothing: antialiased; -} - - -.icon-align-center:before { - content: '\e000'; -} - -.icon-align-justify:before { - content: '\e001'; -} - -.icon-align-left:before { - content: '\e002'; -} - -.icon-align-right:before { - content: '\e003'; -} - -.icon-arrow-down:before { - content: '\e004'; -} - -.icon-arrow-left:before { - content: '\e005'; -} - -.icon-arrow-thin-down:before { - content: '\e006'; -} - -.icon-arrow-right:before { - content: '\e007'; -} - -.icon-arrow-thin-left:before { - content: '\e008'; -} - -.icon-arrow-thin-up:before { - content: '\e009'; -} - -.icon-arrow-up:before { - content: '\e010'; -} - -.icon-attachment:before { - content: '\e011'; -} - -.icon-arrow-thin-right:before { - content: '\e012'; -} - -.icon-code:before { - content: '\e013'; -} - -.icon-cloud:before { - content: '\e014'; -} - -.icon-chevron-right:before { - content: '\e015'; -} - -.icon-chevron-up:before { - content: '\e016'; -} - -.icon-chevron-down:before { - content: '\e017'; -} - -.icon-chevron-left:before { - content: '\e018'; -} - -.icon-camera:before { - content: '\e019'; -} - -.icon-checkmark:before { - content: '\e020'; -} - -.icon-calendar:before { - content: '\e021'; -} - -.icon-clockwise:before { - content: '\e022'; -} - -.icon-conversation:before { - content: '\e023'; -} - -.icon-direction:before { - content: '\e024'; -} - -.icon-cross:before { - content: '\e025'; -} - -.icon-graph-line:before { - content: '\e026'; -} - -.icon-gear:before { - content: '\e027'; -} - -.icon-graph-bar:before { - content: '\e028'; -} - -.icon-export:before { - content: '\e029'; -} - -.icon-feed:before { - content: '\e030'; -} - -.icon-folder:before { - content: '\e031'; -} - -.icon-forward:before { - content: '\e032'; -} - -.icon-folder-open:before { - content: '\e033'; -} - -.icon-download:before { - content: '\e034'; -} - -.icon-document-new:before { - content: '\e035'; -} - -.icon-document-edit:before { - content: '\e036'; -} - -.icon-document:before { - content: '\e037'; -} - -.icon-gaming:before { - content: '\e038'; -} - -.icon-graph-pie:before { - content: '\e039'; -} - -.icon-heart:before { - content: '\e040'; -} - -.icon-headset:before { - content: '\e041'; -} - -.icon-help:before { - content: '\e042'; -} - -.icon-information:before { - content: '\e043'; -} - -.icon-loading:before { - content: '\e044'; -} - -.icon-lock:before { - content: '\e045'; -} - -.icon-location:before { - content: '\e046'; -} - -.icon-lock-open:before { - content: '\e047'; -} - -.icon-mail:before { - content: '\e048'; -} - -.icon-map:before { - content: '\e049'; -} - -.icon-media-loop:before { - content: '\e050'; -} - -.icon-mobile-portrait:before { - content: '\e051'; -} - -.icon-mobile-landscape:before { - content: '\e052'; -} - -.icon-microphone:before { - content: '\e053'; -} - -.icon-minus:before { - content: '\e054'; -} - -.icon-message:before { - content: '\e055'; -} - -.icon-menu:before { - content: '\e056'; -} - -.icon-media-stop:before { - content: '\e057'; -} - -.icon-media-shuffle:before { - content: '\e058'; -} - -.icon-media-previous:before { - content: '\e059'; -} - -.icon-media-play:before { - content: '\e060'; -} - -.icon-media-next:before { - content: '\e061'; -} - -.icon-media-pause:before { - content: '\e062'; -} - -.icon-monitor:before { - content: '\e063'; -} - -.icon-move:before { - content: '\e064'; -} - -.icon-plus:before { - content: '\e065'; -} - -.icon-phone:before { - content: '\e066'; -} - -.icon-preview:before { - content: '\e067'; -} - -.icon-print:before { - content: '\e068'; -} - -.icon-media-record:before { - content: '\e069'; -} - -.icon-music:before { - content: '\e070'; -} - -.icon-home:before { - content: '\e071'; -} - -.icon-question:before { - content: '\e072'; -} - -.icon-reply:before { - content: '\e073'; -} - -.icon-reply-all:before { - content: '\e074'; -} - -.icon-return:before { - content: '\e075'; -} - -.icon-retweet:before { - content: '\e076'; -} - -.icon-search:before { - content: '\e077'; -} - -.icon-view-thumb:before { - content: '\e078'; -} - -.icon-view-list-large:before { - content: '\e079'; -} - -.icon-view-list:before { - content: '\e080'; -} - -.icon-upload:before { - content: '\e081'; -} - -.icon-user-group:before { - content: '\e082'; -} - -.icon-trash:before { - content: '\e083'; -} - -.icon-user:before { - content: '\e084'; -} - -.icon-thumbs-up:before { - content: '\e085'; -} - -.icon-thumbs-down:before { - content: '\e086'; -} - -.icon-tablet-portrait:before { - content: '\e087'; -} - -.icon-tablet-landscape:before { - content: '\e088'; -} - -.icon-tag:before { - content: '\e089'; -} - -.icon-star:before { - content: '\e090'; -} - -.icon-volume-full:before { - content: '\e091'; -} - -.icon-volume-off:before { - content: '\e092'; -} - -.icon-warning:before { - content: '\e093'; -} - -.icon-window:before { - content: '\e094'; } diff --git a/src/styles/info-block.less b/src/styles/info-block.less new file mode 100644 index 000000000..dfe26bd78 --- /dev/null +++ b/src/styles/info-block.less @@ -0,0 +1,94 @@ +@import './variables.less'; + +.info-block { + padding: 0 1em; + transition: .2s ease background-color; + position: relative; + + &.clickable { + cursor: pointer; + } + + & > div { + padding: 1em 0; + border-top: 1px solid rgba(255, 255, 255, 0.1); + } + + &:hover { + background: #202020; + } + + &:first-of-type { + border-top: 0; + } + + p { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + margin: 0; + } + + .label { + margin-bottom: 0.2em; + font-size: @medium-font; + } + + .info { + color: rgba(255,255,255,0.5); + font-size: @normal-font; + } + + pre { + background-color: rgba(0, 0, 0, 0.1); + padding: 0.5em; + white-space: pre-wrap; + word-wrap: break-word; + border-radius: .2em; + color: rgba(255, 255, 255, 0.4); + } + + button { + opacity: 0; + position: absolute; + top: 50%; + right: 0; + transform: translate(0, -50%); + } + + &:hover button { + opacity: 1; + } + + .button-overlay { + opacity: 0; + transition: .2s ease opacity; + position: absolute; + top: 1em; + right: 1em; + height: calc(~"100% - 2em"); + width: 50%; + background: linear-gradient(to left, #202020 0%, #202020 50%, transparent 100%); + } + + &:hover .button-overlay { + opacity: 1; + } +} + +.info-block.file { + & > div { + display: flex; + align-items: center; + } + + & > div > div:nth-child(2) { + overflow: hidden; + } + + & .icon { + font-size: 1.6em; + margin-right: 0.35em; + color: rgba(255, 255, 255, 0.5) + } +} \ No newline at end of file diff --git a/src/styles/loader.less b/src/styles/loader.less index bad103df0..879b1fefb 100644 --- a/src/styles/loader.less +++ b/src/styles/loader.less @@ -106,7 +106,7 @@ // Presentation ================= body { - background: #19b5fe; + background: #252525; } .spinner{ @@ -116,7 +116,7 @@ body { left: 50%; width: 100px; height: 100px; - background: #19b5fe; + background: #252525; &:after{ content: ''; diff --git a/src/styles/menu.less b/src/styles/menu.less deleted file mode 100644 index 9cfb2ac35..000000000 --- a/src/styles/menu.less +++ /dev/null @@ -1,53 +0,0 @@ -html { - width: 240px; - overflow: hidden; -} - -.padding { - padding: 8px; -} - -.status { - text-align: right; -} - -.value { - text-align: right; -} - -.panel { - padding: 4px; - margin-top: 10px; - margin-bottom: 0; -} - -#logo { - display: table; - width: 100%; -} - -.control { - display: block; - margin-top: 6px; -} - -.toggle { - height: 32px !important; - width: 64px !important; -} - -.cell { - width: 33%; - display: table-cell; - text-align: center; - padding-bottom: 6px; -} - -.version { - text-align: center; - margin-bottom: 4px; -} - -.nomarginbottom { - margin-bottom: 0; -} \ No newline at end of file diff --git a/src/styles/panel.less b/src/styles/panel.less new file mode 100644 index 000000000..1bdafc43e --- /dev/null +++ b/src/styles/panel.less @@ -0,0 +1,73 @@ +@import './variables.less'; + +.panel { + height: 100%; + overflow-y: auto; + overflow-x: hidden; + + .header, + .footer { + padding: 1em; + } + + .header { + display: flex; + + .title { + font-size: @medium-font; + color: rgba(255, 255, 255, 0.5); + margin: 0 + } + + .subtitle { + margin: 0; + font-size: @big-font; + } + + & > div:first-child { + margin-right: auto; + } + } + + .main { + padding-bottom: 3.5em; + } + + .footer { + height: 5.25em; + display: flex; + background: linear-gradient(to top, #252525 0%, #252525 45%, transparent 100%); + position: fixed; + bottom: 0; + right: 0.5em; + padding-top: 3em; + box-sizing: border-box; + width: calc(~"300px - 0.5em"); + + .right { + margin-left: auto; + } + + input[type="text"] { + font-size: @normal-font; + margin-top: -0.5em; + } + } + + &.left-panel { + width: 550px; + background: #000; + } + + &.left-panel .footer { + background: linear-gradient(to top, #000000 0%, #000000 45%, transparent 100%); + left: 0; + width: calc(~"550px - 0.5em") + } + + &.right-panel { + background-color: #252525; + color: #fff; + width: 300px; + } +} \ No newline at end of file diff --git a/src/styles/peers.less b/src/styles/peers.less new file mode 100644 index 000000000..935e60c35 --- /dev/null +++ b/src/styles/peers.less @@ -0,0 +1,32 @@ +.peer { + margin: 0.2em 1em; + padding: 0.5em; + cursor: pointer; + border-radius: 0.2em; + border: 1px solid black; + transition: 0.2s ease all; + + &:hover { + border-color: rgba(255, 255, 255, 0.3); + } + + & > p { + text-overflow: ellipsis; + overflow: hidden; + } + + .label { + font-weight: bold; + color: rgba(255, 255, 255, 0.4); + } + + .id { + margin: 0; + } + + .addr { + margin: 0.5em 0 0 0; + font-size: 0.9em; + color: rgba(255, 255, 255, 0.4) + } +} diff --git a/src/styles/react-widgets-theme.less b/src/styles/react-widgets-theme.less deleted file mode 100644 index 4f72b6f94..000000000 --- a/src/styles/react-widgets-theme.less +++ /dev/null @@ -1,94 +0,0 @@ -@rw-font-path: '../../lib/fonts'; -@rw-img-path: '../img'; -@rw-css-prefix: rw-i; -@rw-version: '4.1.0'; - -@gray-base: #000; -@gray-darker: lighten(@gray-base, 13.5%); // #222 -@gray-dark: lighten(@gray-base, 20%); // #333 -@gray: lighten(@gray-base, 33.5%); // #555 -@gray-light: lighten(@gray-base, 46.7%); // #777 -@gray-lighter: lighten(@gray-base, 93.5%); // #eee - - -@text-color: #fff; - -@border-radius: 2px; -@border-radius-sm: 0x; - -@btn-bg: #fff; -@btn-color: #fff; -@btn-border: #ccc; - -@state-bg-select: darken(@btn-border, 12%); -@state-border-select: darken(@btn-border, 12%); -@state-color-select: #fff; - -@state-bg-hover: rgba(255, 255, 255, 0.25); -@state-border-hover: darken(@btn-border, 12%); -@state-color-hover: #333; - -@state-bg-focus: @widget-bg; -@state-border-focus: #66afe9; -@state-color-focus: #333; - -@widget-bg: #fff; -@widget-border: #ccc; -@line-height: 1.429em; - -@input-color: #fff; -@input-height: 2.286em; -@input-padding: 0.429em 0.857em; -@input-bg: rgba(255, 255, 255, 0.15); -@input-bg-disabled: @gray-lighter; - -@input-bg-gradient: none; -@input-color-placeholder: #999; - -@input-border-radius: @border-radius; -@input-border: #ccc; -@input-border-width: 0; - -@input-bg-hover: rgba(255, 255, 255, 0.25); -@input-border-hover: @dropdown-border; - -@input-border-focus: @state-border-focus; - -@dropdown-bg: @input-bg; -@dropdown-border: @input-border; - -@list-bg-hover: rgba(255, 255, 255, 0.25); -@list-border-hover: @state-border-hover; - -@popup-bg: @widget-bg; -@popup-border: @widget-border; -@popup-zindex: 1005; - -@import '../../node_modules/react-widgets/lib/less/react-widgets.less'; - -.rw-dropdownlist { - border-radius: 25px; - padding-right: 40px; - - .rw-input { - font-size: 12px; - padding: 0 0 0 65px; - height: 44px; - line-height: 44px; - } - - .rw-select { - width: 40px; - - .rw-i-caret-down { - font-size: 22px; - line-height: 44px; - } - } -} - -.rw-widget.rw-state-focus, -.rw-widget.rw-state-focus:hover { - box-shadow: none; - border-color: transparent; -} \ No newline at end of file diff --git a/src/styles/themify-icons.less b/src/styles/themify-icons.less new file mode 100644 index 000000000..847e86abe --- /dev/null +++ b/src/styles/themify-icons.less @@ -0,0 +1,1082 @@ +@font-face { + font-family: 'themify'; + src:url('../fonts/themify.eot?-fvbane'); + src:url('../fonts/themify.eot?#iefix-fvbane') format('embedded-opentype'), + url('../fonts/themify.woff?-fvbane') format('woff'), + url('../fonts/themify.ttf?-fvbane') format('truetype'), + url('../fonts/themify.svg?-fvbane#themify') format('svg'); + font-weight: normal; + font-style: normal; +} + +[class^="ti-"], [class*=" ti-"] { + font-family: 'themify'; + font-size: 1.25em; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.ti-wand:before { + content: "\e600"; +} +.ti-volume:before { + content: "\e601"; +} +.ti-user:before { + content: "\e602"; +} +.ti-unlock:before { + content: "\e603"; +} +.ti-unlink:before { + content: "\e604"; +} +.ti-trash:before { + content: "\e605"; +} +.ti-thought:before { + content: "\e606"; +} +.ti-target:before { + content: "\e607"; +} +.ti-tag:before { + content: "\e608"; +} +.ti-tablet:before { + content: "\e609"; +} +.ti-star:before { + content: "\e60a"; +} +.ti-spray:before { + content: "\e60b"; +} +.ti-signal:before { + content: "\e60c"; +} +.ti-shopping-cart:before { + content: "\e60d"; +} +.ti-shopping-cart-full:before { + content: "\e60e"; +} +.ti-settings:before { + content: "\e60f"; +} +.ti-search:before { + content: "\e610"; +} +.ti-zoom-in:before { + content: "\e611"; +} +.ti-zoom-out:before { + content: "\e612"; +} +.ti-cut:before { + content: "\e613"; +} +.ti-ruler:before { + content: "\e614"; +} +.ti-ruler-pencil:before { + content: "\e615"; +} +.ti-ruler-alt:before { + content: "\e616"; +} +.ti-bookmark:before { + content: "\e617"; +} +.ti-bookmark-alt:before { + content: "\e618"; +} +.ti-reload:before { + content: "\e619"; +} +.ti-plus:before { + content: "\e61a"; +} +.ti-pin:before { + content: "\e61b"; +} +.ti-pencil:before { + content: "\e61c"; +} +.ti-pencil-alt:before { + content: "\e61d"; +} +.ti-paint-roller:before { + content: "\e61e"; +} +.ti-paint-bucket:before { + content: "\e61f"; +} +.ti-na:before { + content: "\e620"; +} +.ti-mobile:before { + content: "\e621"; +} +.ti-minus:before { + content: "\e622"; +} +.ti-medall:before { + content: "\e623"; +} +.ti-medall-alt:before { + content: "\e624"; +} +.ti-marker:before { + content: "\e625"; +} +.ti-marker-alt:before { + content: "\e626"; +} +.ti-arrow-up:before { + content: "\e627"; +} +.ti-arrow-right:before { + content: "\e628"; +} +.ti-arrow-left:before { + content: "\e629"; +} +.ti-arrow-down:before { + content: "\e62a"; +} +.ti-lock:before { + content: "\e62b"; +} +.ti-location-arrow:before { + content: "\e62c"; +} +.ti-link:before { + content: "\e62d"; +} +.ti-layout:before { + content: "\e62e"; +} +.ti-layers:before { + content: "\e62f"; +} +.ti-layers-alt:before { + content: "\e630"; +} +.ti-key:before { + content: "\e631"; +} +.ti-import:before { + content: "\e632"; +} +.ti-image:before { + content: "\e633"; +} +.ti-heart:before { + content: "\e634"; +} +.ti-heart-broken:before { + content: "\e635"; +} +.ti-hand-stop:before { + content: "\e636"; +} +.ti-hand-open:before { + content: "\e637"; +} +.ti-hand-drag:before { + content: "\e638"; +} +.ti-folder:before { + content: "\e639"; +} +.ti-flag:before { + content: "\e63a"; +} +.ti-flag-alt:before { + content: "\e63b"; +} +.ti-flag-alt-2:before { + content: "\e63c"; +} +.ti-eye:before { + content: "\e63d"; +} +.ti-export:before { + content: "\e63e"; +} +.ti-exchange-vertical:before { + content: "\e63f"; +} +.ti-desktop:before { + content: "\e640"; +} +.ti-cup:before { + content: "\e641"; +} +.ti-crown:before { + content: "\e642"; +} +.ti-comments:before { + content: "\e643"; +} +.ti-comment:before { + content: "\e644"; +} +.ti-comment-alt:before { + content: "\e645"; +} +.ti-close:before { + content: "\e646"; +} +.ti-clip:before { + content: "\e647"; +} +.ti-angle-up:before { + content: "\e648"; +} +.ti-angle-right:before { + content: "\e649"; +} +.ti-angle-left:before { + content: "\e64a"; +} +.ti-angle-down:before { + content: "\e64b"; +} +.ti-check:before { + content: "\e64c"; +} +.ti-check-box:before { + content: "\e64d"; +} +.ti-camera:before { + content: "\e64e"; +} +.ti-announcement:before { + content: "\e64f"; +} +.ti-brush:before { + content: "\e650"; +} +.ti-briefcase:before { + content: "\e651"; +} +.ti-bolt:before { + content: "\e652"; +} +.ti-bolt-alt:before { + content: "\e653"; +} +.ti-blackboard:before { + content: "\e654"; +} +.ti-bag:before { + content: "\e655"; +} +.ti-move:before { + content: "\e656"; +} +.ti-arrows-vertical:before { + content: "\e657"; +} +.ti-arrows-horizontal:before { + content: "\e658"; +} +.ti-fullscreen:before { + content: "\e659"; +} +.ti-arrow-top-right:before { + content: "\e65a"; +} +.ti-arrow-top-left:before { + content: "\e65b"; +} +.ti-arrow-circle-up:before { + content: "\e65c"; +} +.ti-arrow-circle-right:before { + content: "\e65d"; +} +.ti-arrow-circle-left:before { + content: "\e65e"; +} +.ti-arrow-circle-down:before { + content: "\e65f"; +} +.ti-angle-double-up:before { + content: "\e660"; +} +.ti-angle-double-right:before { + content: "\e661"; +} +.ti-angle-double-left:before { + content: "\e662"; +} +.ti-angle-double-down:before { + content: "\e663"; +} +.ti-zip:before { + content: "\e664"; +} +.ti-world:before { + content: "\e665"; +} +.ti-wheelchair:before { + content: "\e666"; +} +.ti-view-list:before { + content: "\e667"; +} +.ti-view-list-alt:before { + content: "\e668"; +} +.ti-view-grid:before { + content: "\e669"; +} +.ti-uppercase:before { + content: "\e66a"; +} +.ti-upload:before { + content: "\e66b"; +} +.ti-underline:before { + content: "\e66c"; +} +.ti-truck:before { + content: "\e66d"; +} +.ti-timer:before { + content: "\e66e"; +} +.ti-ticket:before { + content: "\e66f"; +} +.ti-thumb-up:before { + content: "\e670"; +} +.ti-thumb-down:before { + content: "\e671"; +} +.ti-text:before { + content: "\e672"; +} +.ti-stats-up:before { + content: "\e673"; +} +.ti-stats-down:before { + content: "\e674"; +} +.ti-split-v:before { + content: "\e675"; +} +.ti-split-h:before { + content: "\e676"; +} +.ti-smallcap:before { + content: "\e677"; +} +.ti-shine:before { + content: "\e678"; +} +.ti-shift-right:before { + content: "\e679"; +} +.ti-shift-left:before { + content: "\e67a"; +} +.ti-shield:before { + content: "\e67b"; +} +.ti-notepad:before { + content: "\e67c"; +} +.ti-server:before { + content: "\e67d"; +} +.ti-quote-right:before { + content: "\e67e"; +} +.ti-quote-left:before { + content: "\e67f"; +} +.ti-pulse:before { + content: "\e680"; +} +.ti-printer:before { + content: "\e681"; +} +.ti-power-off:before { + content: "\e682"; +} +.ti-plug:before { + content: "\e683"; +} +.ti-pie-chart:before { + content: "\e684"; +} +.ti-paragraph:before { + content: "\e685"; +} +.ti-panel:before { + content: "\e686"; +} +.ti-package:before { + content: "\e687"; +} +.ti-music:before { + content: "\e688"; +} +.ti-music-alt:before { + content: "\e689"; +} +.ti-mouse:before { + content: "\e68a"; +} +.ti-mouse-alt:before { + content: "\e68b"; +} +.ti-money:before { + content: "\e68c"; +} +.ti-microphone:before { + content: "\e68d"; +} +.ti-menu:before { + content: "\e68e"; +} +.ti-menu-alt:before { + content: "\e68f"; +} +.ti-map:before { + content: "\e690"; +} +.ti-map-alt:before { + content: "\e691"; +} +.ti-loop:before { + content: "\e692"; +} +.ti-location-pin:before { + content: "\e693"; +} +.ti-list:before { + content: "\e694"; +} +.ti-light-bulb:before { + content: "\e695"; +} +.ti-Italic:before { + content: "\e696"; +} +.ti-info:before { + content: "\e697"; +} +.ti-infinite:before { + content: "\e698"; +} +.ti-id-badge:before { + content: "\e699"; +} +.ti-hummer:before { + content: "\e69a"; +} +.ti-home:before { + content: "\e69b"; +} +.ti-help:before { + content: "\e69c"; +} +.ti-headphone:before { + content: "\e69d"; +} +.ti-harddrives:before { + content: "\e69e"; +} +.ti-harddrive:before { + content: "\e69f"; +} +.ti-gift:before { + content: "\e6a0"; +} +.ti-game:before { + content: "\e6a1"; +} +.ti-filter:before { + content: "\e6a2"; +} +.ti-files:before { + content: "\e6a3"; +} +.ti-file:before { + content: "\e6a4"; +} +.ti-eraser:before { + content: "\e6a5"; +} +.ti-envelope:before { + content: "\e6a6"; +} +.ti-download:before { + content: "\e6a7"; +} +.ti-direction:before { + content: "\e6a8"; +} +.ti-direction-alt:before { + content: "\e6a9"; +} +.ti-dashboard:before { + content: "\e6aa"; +} +.ti-control-stop:before { + content: "\e6ab"; +} +.ti-control-shuffle:before { + content: "\e6ac"; +} +.ti-control-play:before { + content: "\e6ad"; +} +.ti-control-pause:before { + content: "\e6ae"; +} +.ti-control-forward:before { + content: "\e6af"; +} +.ti-control-backward:before { + content: "\e6b0"; +} +.ti-cloud:before { + content: "\e6b1"; +} +.ti-cloud-up:before { + content: "\e6b2"; +} +.ti-cloud-down:before { + content: "\e6b3"; +} +.ti-clipboard:before { + content: "\e6b4"; +} +.ti-car:before { + content: "\e6b5"; +} +.ti-calendar:before { + content: "\e6b6"; +} +.ti-book:before { + content: "\e6b7"; +} +.ti-bell:before { + content: "\e6b8"; +} +.ti-basketball:before { + content: "\e6b9"; +} +.ti-bar-chart:before { + content: "\e6ba"; +} +.ti-bar-chart-alt:before { + content: "\e6bb"; +} +.ti-back-right:before { + content: "\e6bc"; +} +.ti-back-left:before { + content: "\e6bd"; +} +.ti-arrows-corner:before { + content: "\e6be"; +} +.ti-archive:before { + content: "\e6bf"; +} +.ti-anchor:before { + content: "\e6c0"; +} +.ti-align-right:before { + content: "\e6c1"; +} +.ti-align-left:before { + content: "\e6c2"; +} +.ti-align-justify:before { + content: "\e6c3"; +} +.ti-align-center:before { + content: "\e6c4"; +} +.ti-alert:before { + content: "\e6c5"; +} +.ti-alarm-clock:before { + content: "\e6c6"; +} +.ti-agenda:before { + content: "\e6c7"; +} +.ti-write:before { + content: "\e6c8"; +} +.ti-window:before { + content: "\e6c9"; +} +.ti-widgetized:before { + content: "\e6ca"; +} +.ti-widget:before { + content: "\e6cb"; +} +.ti-widget-alt:before { + content: "\e6cc"; +} +.ti-wallet:before { + content: "\e6cd"; +} +.ti-video-clapper:before { + content: "\e6ce"; +} +.ti-video-camera:before { + content: "\e6cf"; +} +.ti-vector:before { + content: "\e6d0"; +} +.ti-themify-logo:before { + content: "\e6d1"; +} +.ti-themify-favicon:before { + content: "\e6d2"; +} +.ti-themify-favicon-alt:before { + content: "\e6d3"; +} +.ti-support:before { + content: "\e6d4"; +} +.ti-stamp:before { + content: "\e6d5"; +} +.ti-split-v-alt:before { + content: "\e6d6"; +} +.ti-slice:before { + content: "\e6d7"; +} +.ti-shortcode:before { + content: "\e6d8"; +} +.ti-shift-right-alt:before { + content: "\e6d9"; +} +.ti-shift-left-alt:before { + content: "\e6da"; +} +.ti-ruler-alt-2:before { + content: "\e6db"; +} +.ti-receipt:before { + content: "\e6dc"; +} +.ti-pin2:before { + content: "\e6dd"; +} +.ti-pin-alt:before { + content: "\e6de"; +} +.ti-pencil-alt2:before { + content: "\e6df"; +} +.ti-palette:before { + content: "\e6e0"; +} +.ti-more:before { + content: "\e6e1"; +} +.ti-more-alt:before { + content: "\e6e2"; +} +.ti-microphone-alt:before { + content: "\e6e3"; +} +.ti-magnet:before { + content: "\e6e4"; +} +.ti-line-double:before { + content: "\e6e5"; +} +.ti-line-dotted:before { + content: "\e6e6"; +} +.ti-line-dashed:before { + content: "\e6e7"; +} +.ti-layout-width-full:before { + content: "\e6e8"; +} +.ti-layout-width-default:before { + content: "\e6e9"; +} +.ti-layout-width-default-alt:before { + content: "\e6ea"; +} +.ti-layout-tab:before { + content: "\e6eb"; +} +.ti-layout-tab-window:before { + content: "\e6ec"; +} +.ti-layout-tab-v:before { + content: "\e6ed"; +} +.ti-layout-tab-min:before { + content: "\e6ee"; +} +.ti-layout-slider:before { + content: "\e6ef"; +} +.ti-layout-slider-alt:before { + content: "\e6f0"; +} +.ti-layout-sidebar-right:before { + content: "\e6f1"; +} +.ti-layout-sidebar-none:before { + content: "\e6f2"; +} +.ti-layout-sidebar-left:before { + content: "\e6f3"; +} +.ti-layout-placeholder:before { + content: "\e6f4"; +} +.ti-layout-menu:before { + content: "\e6f5"; +} +.ti-layout-menu-v:before { + content: "\e6f6"; +} +.ti-layout-menu-separated:before { + content: "\e6f7"; +} +.ti-layout-menu-full:before { + content: "\e6f8"; +} +.ti-layout-media-right-alt:before { + content: "\e6f9"; +} +.ti-layout-media-right:before { + content: "\e6fa"; +} +.ti-layout-media-overlay:before { + content: "\e6fb"; +} +.ti-layout-media-overlay-alt:before { + content: "\e6fc"; +} +.ti-layout-media-overlay-alt-2:before { + content: "\e6fd"; +} +.ti-layout-media-left-alt:before { + content: "\e6fe"; +} +.ti-layout-media-left:before { + content: "\e6ff"; +} +.ti-layout-media-center-alt:before { + content: "\e700"; +} +.ti-layout-media-center:before { + content: "\e701"; +} +.ti-layout-list-thumb:before { + content: "\e702"; +} +.ti-layout-list-thumb-alt:before { + content: "\e703"; +} +.ti-layout-list-post:before { + content: "\e704"; +} +.ti-layout-list-large-image:before { + content: "\e705"; +} +.ti-layout-line-solid:before { + content: "\e706"; +} +.ti-layout-grid4:before { + content: "\e707"; +} +.ti-layout-grid3:before { + content: "\e708"; +} +.ti-layout-grid2:before { + content: "\e709"; +} +.ti-layout-grid2-thumb:before { + content: "\e70a"; +} +.ti-layout-cta-right:before { + content: "\e70b"; +} +.ti-layout-cta-left:before { + content: "\e70c"; +} +.ti-layout-cta-center:before { + content: "\e70d"; +} +.ti-layout-cta-btn-right:before { + content: "\e70e"; +} +.ti-layout-cta-btn-left:before { + content: "\e70f"; +} +.ti-layout-column4:before { + content: "\e710"; +} +.ti-layout-column3:before { + content: "\e711"; +} +.ti-layout-column2:before { + content: "\e712"; +} +.ti-layout-accordion-separated:before { + content: "\e713"; +} +.ti-layout-accordion-merged:before { + content: "\e714"; +} +.ti-layout-accordion-list:before { + content: "\e715"; +} +.ti-ink-pen:before { + content: "\e716"; +} +.ti-info-alt:before { + content: "\e717"; +} +.ti-help-alt:before { + content: "\e718"; +} +.ti-headphone-alt:before { + content: "\e719"; +} +.ti-hand-point-up:before { + content: "\e71a"; +} +.ti-hand-point-right:before { + content: "\e71b"; +} +.ti-hand-point-left:before { + content: "\e71c"; +} +.ti-hand-point-down:before { + content: "\e71d"; +} +.ti-gallery:before { + content: "\e71e"; +} +.ti-face-smile:before { + content: "\e71f"; +} +.ti-face-sad:before { + content: "\e720"; +} +.ti-credit-card:before { + content: "\e721"; +} +.ti-control-skip-forward:before { + content: "\e722"; +} +.ti-control-skip-backward:before { + content: "\e723"; +} +.ti-control-record:before { + content: "\e724"; +} +.ti-control-eject:before { + content: "\e725"; +} +.ti-comments-smiley:before { + content: "\e726"; +} +.ti-brush-alt:before { + content: "\e727"; +} +.ti-youtube:before { + content: "\e728"; +} +.ti-vimeo:before { + content: "\e729"; +} +.ti-twitter:before { + content: "\e72a"; +} +.ti-time:before { + content: "\e72b"; +} +.ti-tumblr:before { + content: "\e72c"; +} +.ti-skype:before { + content: "\e72d"; +} +.ti-share:before { + content: "\e72e"; +} +.ti-share-alt:before { + content: "\e72f"; +} +.ti-rocket:before { + content: "\e730"; +} +.ti-pinterest:before { + content: "\e731"; +} +.ti-new-window:before { + content: "\e732"; +} +.ti-microsoft:before { + content: "\e733"; +} +.ti-list-ol:before { + content: "\e734"; +} +.ti-linkedin:before { + content: "\e735"; +} +.ti-layout-sidebar-2:before { + content: "\e736"; +} +.ti-layout-grid4-alt:before { + content: "\e737"; +} +.ti-layout-grid3-alt:before { + content: "\e738"; +} +.ti-layout-grid2-alt:before { + content: "\e739"; +} +.ti-layout-column4-alt:before { + content: "\e73a"; +} +.ti-layout-column3-alt:before { + content: "\e73b"; +} +.ti-layout-column2-alt:before { + content: "\e73c"; +} +.ti-instagram:before { + content: "\e73d"; +} +.ti-google:before { + content: "\e73e"; +} +.ti-github:before { + content: "\e73f"; +} +.ti-flickr:before { + content: "\e740"; +} +.ti-facebook:before { + content: "\e741"; +} +.ti-dropbox:before { + content: "\e742"; +} +.ti-dribbble:before { + content: "\e743"; +} +.ti-apple:before { + content: "\e744"; +} +.ti-android:before { + content: "\e745"; +} +.ti-save:before { + content: "\e746"; +} +.ti-save-alt:before { + content: "\e747"; +} +.ti-yahoo:before { + content: "\e748"; +} +.ti-wordpress:before { + content: "\e749"; +} +.ti-vimeo-alt:before { + content: "\e74a"; +} +.ti-twitter-alt:before { + content: "\e74b"; +} +.ti-tumblr-alt:before { + content: "\e74c"; +} +.ti-trello:before { + content: "\e74d"; +} +.ti-stack-overflow:before { + content: "\e74e"; +} +.ti-soundcloud:before { + content: "\e74f"; +} +.ti-sharethis:before { + content: "\e750"; +} +.ti-sharethis-alt:before { + content: "\e751"; +} +.ti-reddit:before { + content: "\e752"; +} +.ti-pinterest-alt:before { + content: "\e753"; +} +.ti-microsoft-alt:before { + content: "\e754"; +} +.ti-linux:before { + content: "\e755"; +} +.ti-jsfiddle:before { + content: "\e756"; +} +.ti-joomla:before { + content: "\e757"; +} +.ti-html5:before { + content: "\e758"; +} +.ti-flickr-alt:before { + content: "\e759"; +} +.ti-email:before { + content: "\e75a"; +} +.ti-drupal:before { + content: "\e75b"; +} +.ti-dropbox-alt:before { + content: "\e75c"; +} +.ti-css3:before { + content: "\e75d"; +} +.ti-rss:before { + content: "\e75e"; +} +.ti-rss-alt:before { + content: "\e75f"; +} \ No newline at end of file diff --git a/src/styles/variables.less b/src/styles/variables.less new file mode 100644 index 000000000..72ed1104e --- /dev/null +++ b/src/styles/variables.less @@ -0,0 +1,6 @@ +@big-font: 35px; +@medium-font: 18px; +@normal-font: 14px; + +@red: #F64D4D; +@yellow: #FFE564; diff --git a/src/styles/welcome.less b/src/styles/welcome.less new file mode 100644 index 000000000..c51302f46 --- /dev/null +++ b/src/styles/welcome.less @@ -0,0 +1,47 @@ +@import './variables.less'; + +.intro { + .left-panel { + display: flex; + align-items: center; + text-align: center; + justify-content: space-around; + } + + .right-panel .main { + padding: 1em; + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; + } + + .right-panel .main > div { + display: flex; + flex-direction: column; + justify-content: space-between; + } + + .title { + font-size: @big-font; + margin: 0; + } + + .subtitle { + font-size: @medium-font; + color: rgba(255, 255, 255, 0.5); + margin-top: 0.2em + } + + .heartbeat { + width: 300px; + } + + a.advanced-options { + text-align: center; + font-size: @normal-font; + margin: 0.5em 0 0 0; + cursor: pointer; + color: rgba(255, 255, 255, 0.4) + } +} diff --git a/src/utils/file-history.js b/src/utils/file-history.js new file mode 100644 index 000000000..c08f21d80 --- /dev/null +++ b/src/utils/file-history.js @@ -0,0 +1,38 @@ +import fs from 'fs' +import {EventEmitter} from 'events' + +export default class FileHistory extends EventEmitter { + constructor (location) { + super() + let history = [] + + if (fs.existsSync(location)) { + history = JSON.parse(fs.readFileSync(location)) + } else { + history = [] + fs.writeFileSync(location, JSON.stringify(history)) + } + + this.history = history + this.location = location + } + + _save () { + fs.writeFileSync(this.location, JSON.stringify(this.history)) + } + + add (name, hash) { + this.history.unshift({ + name: name, + hash: hash, + date: new Date() + }) + + this._save() + this.emit('change', this.history) + } + + toArray () { + return this.history + } +} diff --git a/src/utils/logo.js b/src/utils/logo.js deleted file mode 100644 index af166a949..000000000 --- a/src/utils/logo.js +++ /dev/null @@ -1,24 +0,0 @@ -import {resolve, join} from 'path' - -const logosDir = resolve(__dirname, '../../node_modules/ipfs-logo') - -export const logoWiderStrokes = join(logosDir, 'raster', 'ipfs-logo-wider-strokes-white.png') -export const macOsMenuBar = join(logosDir, 'platform-icons', 'osx-menu-bar@2x.png') - -export function getLogo (options = {}) { - Object.assign(options, { - size: 512, - color: 'ice', - text: false, - outline: false - }) - - let filename = 'ipfs-logo' - - if (options.text) filename += '-text' - filename += `-${options.size}-${options.color}` - if (options.outline) filename += '-outline' - filename += '.png' - - return join(logosDir, 'raster-generated', filename) -} diff --git a/src/utils/stats-poller.js b/src/utils/stats-poller.js new file mode 100644 index 000000000..ede5bd790 --- /dev/null +++ b/src/utils/stats-poller.js @@ -0,0 +1,84 @@ +import {EventEmitter} from 'events' +import {lookupPretty} from 'ipfs-geoip' + +export default class StatsPoller extends EventEmitter { + constructor (ipfs, logger) { + super() + this.ipfs = ipfs + this.logger = logger + this.shouldPoll = false + this.statsCache = {} + this.locationsCache = {} + + this._poller() + } + + _poller () { + const next = () => setTimeout(() => this._poller(), 1000) + + if (!this.shouldPoll) { + return next() + } + + this.ipfs.swarm.peers() + .then((res) => { + res = res.sort((a, b) => a.peer.toB58String() > b.peer.toB58String()) + + let peers = [] + + res.forEach((rawPeer) => { + let peer = { + id: rawPeer.peer.toB58String(), + addr: rawPeer.addr.toString(), + location: { + formatted: 'Unknown' + } + } + + if (!this.locationsCache[peer.id]) { + lookupPretty(this.ipfs, [peer.addr], (err, result) => { + if (err) { return } + this.locationsCache[peer.id] = result + }) + } else { + peer.location = this.locationsCache[peer.id] + } + + peers.push(peer) + }) + + this.statsCache.peers = peers + this.emit('change', this.statsCache) + }, this.logger.error) + .then(next) + + this.ipfs.id() + .then((peer) => { + this.statsCache.node = peer + this.statsCache.node.location = 'Unknown' + + lookupPretty(this.ipfs, peer.addresses, (err, location) => { + if (err) { return } + + this.statsCache.node.location = location && location.formatted + this.emit('change', this.statsCache) + }) + }) + .catch(this.logger.error) + + this.ipfs.repo.stat() + .then(repo => { + this.statsCache.repo = repo + this.emit('change', this.statsCache) + }) + .catch(this.logger.error) + } + + stop () { + this.shouldPoll = false + } + + start () { + this.shouldPoll = true + } +} diff --git a/src/views/settings.html b/src/views/settings.html deleted file mode 100644 index 1e002f996..000000000 --- a/src/views/settings.html +++ /dev/null @@ -1,11 +0,0 @@ - - - IPFS app Settings - - - - -
- - -