From 9212def6d1c5a7c70ffbed119b4f1990856b93c7 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Fri, 1 Dec 2017 22:41:51 +0000 Subject: [PATCH 01/51] add files --- package.json | 7 +- src/config.js | 16 +- src/constants.js | 4 + src/controls/drag-drop.js | 38 +- src/img/icons/add.svg | 13 + src/img/icons/arrow.svg | 13 + src/img/icons/folder.svg | 12 + src/img/icons/logo.svg | 15 + src/img/icons/more.svg | 16 + src/img/icons/off.svg | 15 + src/img/icons/warning.svg | 19 + src/img/map.svg | 190 +++++ src/index.js | 116 ++- src/js/components/view/file.js | 49 ++ src/js/components/view/footer.js | 14 + src/js/components/view/header.js | 44 +- src/js/components/view/icon-button.js | 42 +- src/js/components/view/icon.js | 2 +- src/js/components/view/info-block.js | 71 ++ src/js/components/view/peer.js | 19 + src/js/components/view/text-button.js | 15 + src/js/screens/menu.js | 64 +- src/js/screens/menu/files.js | 93 +++ src/js/screens/menu/node-info.js | 57 ++ src/js/screens/menu/peers.js | 45 + src/js/screens/menu/start.js | 2 +- src/js/screens/setup/advanced.js | 2 +- src/js/screens/setup/intro.js | 9 +- src/styles/app.less | 83 ++ src/styles/common.less | 4 +- src/styles/fonts.less | 437 +--------- src/styles/info-block.less | 84 ++ src/styles/loader.less | 4 +- src/styles/panel.less | 60 ++ src/styles/peers.less | 32 + src/styles/themify-icons.less | 1082 +++++++++++++++++++++++++ 36 files changed, 2203 insertions(+), 585 deletions(-) create mode 100644 src/constants.js create mode 100644 src/img/icons/add.svg create mode 100644 src/img/icons/arrow.svg create mode 100644 src/img/icons/folder.svg create mode 100644 src/img/icons/logo.svg create mode 100644 src/img/icons/more.svg create mode 100644 src/img/icons/off.svg create mode 100644 src/img/icons/warning.svg create mode 100644 src/img/map.svg create mode 100644 src/js/components/view/file.js create mode 100644 src/js/components/view/footer.js create mode 100644 src/js/components/view/info-block.js create mode 100644 src/js/components/view/peer.js create mode 100644 src/js/components/view/text-button.js create mode 100644 src/js/screens/menu/files.js create mode 100644 src/js/screens/menu/node-info.js create mode 100644 src/js/screens/menu/peers.js create mode 100644 src/styles/info-block.less create mode 100644 src/styles/panel.less create mode 100644 src/styles/peers.less create mode 100644 src/styles/themify-icons.less diff --git a/package.json b/package.json index fb6143feb..c04037ca5 100644 --- a/package.json +++ b/package.json @@ -7,15 +7,17 @@ "dependencies": { "electron-compile": "^6.4.2", "electron-is-dev": "^0.3.0", + "menubar": "^5.2.3", "electron-squirrel-startup": "^1.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", @@ -101,7 +103,8 @@ "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..1d9f340c9 100644 --- a/src/config.js +++ b/src/config.js @@ -8,7 +8,18 @@ import {getLogo, macOsMenuBar} from './utils/logo' 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 = (() => { + let 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 @@ -55,7 +66,7 @@ const window = { // Configuration for the MenuBar const menubar = { dir: __dirname, - width: 300, + width: 800, height: 400, index: `file://${__dirname}/views/menubar.html`, icon: (os.platform() === 'darwin') ? macOsMenuBar : getLogo(), @@ -78,6 +89,7 @@ export default { 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 index 0d35f5b4c..56eb2396f 100644 --- a/src/controls/drag-drop.js +++ b/src/controls/drag-drop.js @@ -1,39 +1,14 @@ -import notifier from 'node-notifier' -import {join} from 'path' import {logger} from '../config' -import {getIPFS} from './../index' +import {getIPFS, appendFile} 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') + // FAILED TO UPLOAD FILES return } @@ -41,7 +16,7 @@ export default function dragDrop (event, files) { .add(files, {w: files.length > 1}) .then((res) => { if (!res) { - notifyError('Failed to upload files') + // FAILED TO UPLOAD FILES return } @@ -54,14 +29,11 @@ export default function dragDrop (event, files) { logger.info('Uploaded file %s', file.path) - notify( - `Finished uploading ${file.path}`, - `${file.path} was uploaded to ${url}.` - ) + appendFile(file.path, file.hash) }) }) .catch((err) => { logger.error(err) - notifyError(err.message) + // FAILED TO UPLOAD FILES }) } diff --git a/src/img/icons/add.svg b/src/img/icons/add.svg new file mode 100644 index 000000000..4fad45f23 --- /dev/null +++ b/src/img/icons/add.svg @@ -0,0 +1,13 @@ + + + + Icons / 24 / White / Add + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/src/img/icons/arrow.svg b/src/img/icons/arrow.svg new file mode 100644 index 000000000..d38ce1ca5 --- /dev/null +++ b/src/img/icons/arrow.svg @@ -0,0 +1,13 @@ + + + + Icons / 24 / White / Arrow + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/src/img/icons/folder.svg b/src/img/icons/folder.svg new file mode 100644 index 000000000..d8ecfbf76 --- /dev/null +++ b/src/img/icons/folder.svg @@ -0,0 +1,12 @@ + + + + Icons / 24 / White / Folder + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/img/icons/logo.svg b/src/img/icons/logo.svg new file mode 100644 index 000000000..7cdd36c24 --- /dev/null +++ b/src/img/icons/logo.svg @@ -0,0 +1,15 @@ + + + + Assets / Logo + Created with Sketch. + + + + + \ No newline at end of file diff --git a/src/img/icons/more.svg b/src/img/icons/more.svg new file mode 100644 index 000000000..a44076f1d --- /dev/null +++ b/src/img/icons/more.svg @@ -0,0 +1,16 @@ + + + + Icons / 24 / White / More + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/src/img/icons/off.svg b/src/img/icons/off.svg new file mode 100644 index 000000000..c9daeb352 --- /dev/null +++ b/src/img/icons/off.svg @@ -0,0 +1,15 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/src/img/icons/warning.svg b/src/img/icons/warning.svg new file mode 100644 index 000000000..755f8e180 --- /dev/null +++ b/src/img/icons/warning.svg @@ -0,0 +1,19 @@ + + + + Group + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file 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/index.js b/src/index.js index 7f4f360b6..eaa404ac0 100644 --- a/src/index.js +++ b/src/index.js @@ -3,14 +3,13 @@ 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 {join} from 'path' import {lookupPretty} from 'ipfs-geoip' import config, {logger} from './config' -import {dialog, BrowserWindow, ipcMain, app} from 'electron' +import {dialog, ipcMain, app} from 'electron' // Handle creating/removing shortcuts on Windows when installing/uninstalling. if (require('electron-squirrel-startup')) { @@ -40,6 +39,7 @@ let IPFS let mb let shouldPoll = false const statsCache = {} +let locationsCache = {} function pollStats (ipfs) { const next = () => { @@ -54,7 +54,34 @@ function pollStats (ipfs) { ipfs.swarm.peers() .then((res) => { - statsCache.peers = res.length + res = res.sort((a, b) => { + return a.peer.toB58String() > b.peer.toB58String() ? 1 : -1 + }) + + let peers = [] + + res.forEach(rawPeer => { + let peer = { + id: rawPeer.peer.toB58String(), + addr: rawPeer.addr.toString(), + location: { + formatted: 'Unknown' + } + } + + if (!locationsCache[peer.id]) { + lookupPretty(IPFS, [peer.addr], (err, result) => { + if (err) return + locationsCache[peer.id] = result + }) + } else { + peer.location = locationsCache[peer.id] + } + + peers.push(peer) + }) + + statsCache.peers = peers mb.window.webContents.send('stats', statsCache) }, (err) => { logger.error(err.stack) @@ -63,18 +90,27 @@ function pollStats (ipfs) { ipfs.id() .then((peer) => { - lookupPretty(ipfs, peer.addresses, (err, location) => { + statsCache.node = peer + statsCache.node.location = 'Unknown' + + lookupPretty(IPFS, peer.addresses, (err, location) => { if (err) { - statsCache.location = 'Unknown' mb.window.webContents.send('stats', statsCache) return } - statsCache.location = location && location.formatted + statsCache.node.location = location && location.formatted mb.window.webContents.send('stats', statsCache) }) }) .catch(logger.error) + + ipfs.repo.stat() + .then(repo => { + statsCache.repo = repo + mb.window.webContents.send('stats', statsCache) + }) + .catch(logger.error) } function onRequestState (node, event) { @@ -89,6 +125,34 @@ function onRequestState (node, event) { } } +let fileHistory = (() => { + let history = [] + + if (fs.existsSync(config.ipfsFileHistoryFile)) { + history = JSON.parse(fs.readFileSync(config.ipfsFileHistoryFile)) + } else { + history = [] + fs.writeFileSync(config.ipfsFileHistoryFile, JSON.stringify(history)) + } + + return history +})() + +export function appendFile (name, hash) { + fileHistory.unshift({ + name, + hash, + date: new Date() + }) + + fs.writeFileSync(config.ipfsFileHistoryFile, JSON.stringify(fileHistory)) + onRequestFiles() +} + +function onRequestFiles () { + mb.window.webContents.send('files', fileHistory) +} + function onStartDaemon (node) { logger.info('Start daemon') mb.window.webContents.send('node-status', 'starting') @@ -163,15 +227,27 @@ function startTray (node) { logger.info('Starting tray') ipcMain.on('request-state', onRequestState.bind(null, node)) + ipcMain.on('request-files', onRequestFiles) 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('close-tray-window', onCloseWindow) - ipcMain.on('open-settings', openSettings) ipcMain.on('open-console', openConsole) ipcMain.on('open-browser', openBrowser) + ipcMain.on('small-window', () => { + mb.window.setSize(350, config.menubar.height) + }) + + ipcMain.on('medium-window', () => { + mb.window.setSize(600, config.menubar.height) + }) + + ipcMain.on('big-window', () => { + mb.window.setSize(800, config.menubar.height) + }) + mb.app.once('will-quit', onWillQuit.bind(null, node)) } @@ -179,22 +255,21 @@ function startTray (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', () => { + mb.window.webContents.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 +285,7 @@ function initialize (path, node) { userPath = join(userPath, '.ipfs') } - welcomeWindow.webContents.send('setup-config-path', userPath) + mb.window.webContents.send('setup-config-path', userPath) }) }) @@ -218,23 +293,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') + mb.window.webContents.send('initializing') node.init({ directory: userPath, keySize }, (err, res) => { if (err) { - return welcomeWindow.webContents.send('initialization-error', String(err)) + return mb.window.webContents.send('initialization-error', String(err)) } fs.writeFileSync(config.ipfsPathFile, path) - welcomeWindow.webContents.send('initialization-complete') - welcomeWindow.webContents.send('node-status', 'stopped') + mb.window.webContents.send('initialization-complete') + mb.window.webContents.send('node-status', 'stopped') - welcomeWindow.close() onStartDaemon(node) - mb.showWindow() + mb.window.loadURL(config.menubar.index) }) }) } diff --git a/src/js/components/view/file.js b/src/js/components/view/file.js new file mode 100644 index 000000000..ef60c6a6f --- /dev/null +++ b/src/js/components/view/file.js @@ -0,0 +1,49 @@ +import React from 'react' +import PropTypes from 'prop-types' +import {clipboard} from 'electron' +import moment from 'moment' + +import TextButton from './text-button' +import Icon from './icon' + +const fileTypes = { + png: 'image', + jpg: 'image', + jpeg: 'image', + gif: 'image', + mp3: 'music-alt', + mp4: 'video-clapper' +} + +export default function File (props) { + let icon = 'file' + let extension = props.name.split('.').pop() + 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 +} 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..0252d609a 100644 --- a/src/js/components/view/header.js +++ b/src/js/components/view/header.js @@ -1,47 +1,27 @@ 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/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.js b/src/js/components/view/icon.js index f6f5ff8d1..74839581d 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..3c8c85d9a --- /dev/null +++ b/src/js/components/view/info-block.js @@ -0,0 +1,71 @@ +import React from 'react' +import PropTypes from 'prop-types' +import {clipboard} from 'electron' + +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.clipboard !== false) { + let copy = (props.clipboardMessage === true) ? props.info : props.clipboard + + button = (
+ { + clipboard.writeText(copy) + }} /> +
) + } + + return ( +
+
+

{props.title}

+ {info} + {button} +
+
+ ) +} + +InfoBlock.propTypes = { + title: PropTypes.string.isRequired, + info: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.array + ]), + clipboard: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.string + ]), + clipboardMessage: PropTypes.string, + pre: PropTypes.bool +} + +InfoBlock.defaultProps = { + pre: false, + clipboard: false, + clipboardMessage: 'Copy' +} 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..61dc6f862 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,6 +19,7 @@ const STOPPING = 'stopping' class Menu extends Component { state = { status: UNINITIALIZED, + route: PAGES.FILES, connected: false, version: null, stats: {} @@ -28,7 +32,6 @@ class Menu extends Component { } _onNodeStatus = (event, status) => { - console.log(status) this.setState({status: status}) } @@ -36,8 +39,15 @@ class Menu extends Component { this.setState({stats: stats}) } + _onFiles = (event, files) => { + this.setState({files: files}) + } + + _changeRoute = (route) => { + this.setState({route: route}) + } + _startDaemon () { - console.log('starting daemon') ipcRenderer.send('start-daemon') } @@ -66,8 +76,10 @@ class Menu extends Component { 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 () { @@ -75,24 +87,46 @@ class Menu extends Component { 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: diff --git a/src/js/screens/menu/files.js b/src/js/screens/menu/files.js new file mode 100644 index 000000000..946763ee9 --- /dev/null +++ b/src/js/screens/menu/files.js @@ -0,0 +1,93 @@ +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 () {} + } + + render () { + const {connectDropTarget, isOver, canDrop} = this.props + + const styles = { + dropper: { + visibility: (isOver && canDrop) ? 'visible' : 'hidden', + position: 'fixed', + top: '0', + left: 0, + right: 0, + bottom: '70px', + height: '100%', + width: '100%', + color: '#000000', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center' + } + } + + let files = this.props.files.map(file => { + return () + }) + + return connectDropTarget( +
+
+ +
+ {files} +
+ +
+ Drop to upload to IPFS +
+ +
+ { this.props.changeRoute('info') }} icon='pulse' /> + +
+ {}} icon='plus' /> + {}} icon='folder' /> +
+
+
+ ) + } +} + +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..b788969ea --- /dev/null +++ b/src/js/screens/menu/node-info.js @@ -0,0 +1,57 @@ +import React from 'react' +import PropTypes from 'prop-types' +import prettyBytes from 'pretty-bytes' + +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' + +export default function NodeScreen (props) { + return ( +
+
+ +
+
+

{prettyBytes(props.repo.RepoSize)}

+

Sharing {props.repo.NumObjects} objects

+
+ + + + + + +
+ +
+
+ +
+
+
+ ) +} + +NodeScreen.propTypes = { + id: PropTypes.string, + location: PropTypes.string, + protocolVersion: PropTypes.string, + publicKey: PropTypes.string, + addresses: PropTypes.array, + repo: PropTypes.object, + stopDaemon: PropTypes.func.isRequired +} + +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..4b3f209b2 --- /dev/null +++ b/src/js/screens/menu/peers.js @@ -0,0 +1,45 @@ +import React 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 function PeersScreen (props) { + var peers = [] + + props.peers.forEach((peer, i) => { + peers.push() + }) + + return ( +
+
+
+ {peers} +
+ +
+ { props.changeRoute('files') }} icon='files' /> + +
+ Search feature +
+
+
+ ) +} + +PeersScreen.propTypes = { + location: PropTypes.string, + peers: PropTypes.array, + changeRoute: PropTypes.func.isRequired +} + +PeersScreen.defaultProps = { + location: 'Unknown', + peers: [] +} diff --git a/src/js/screens/menu/start.js b/src/js/screens/menu/start.js index ab089276d..0d2790a0e 100644 --- a/src/js/screens/menu/start.js +++ b/src/js/screens/menu/start.js @@ -21,7 +21,7 @@ export default class StartScreen extends Component { display: 'flex', width: '100%', height: '100%', - backgroundColor: '#19b5fe', + backgroundColor: '#252525', color: '#FFFFFF', flexDirection: 'column', alignItems: 'center', diff --git a/src/js/screens/setup/advanced.js b/src/js/screens/setup/advanced.js index dd143bf85..83bd2c26b 100644 --- a/src/js/screens/setup/advanced.js +++ b/src/js/screens/setup/advanced.js @@ -29,7 +29,7 @@ export default class Intro extends Component { display: 'flex', width: '100%', height: '100%', - backgroundImage: `url('../img/jellyfish-blur.png')`, + backgroundImage: `url('../img/space.jpg')`, backgroundSize: '100%', backgroundPosition: '0 0', color: '#FFFFFF', diff --git a/src/js/screens/setup/intro.js b/src/js/screens/setup/intro.js index 9cc0888d0..75e81204d 100644 --- a/src/js/screens/setup/intro.js +++ b/src/js/screens/setup/intro.js @@ -1,15 +1,14 @@ import React, {Component} from 'react' import PropTypes from 'prop-types' -import Icon from '../../components/view/icon' -import Button from '../../components/view/button' +import TextButton from '../../components/view/text-button' const styles = { base: { display: 'flex', width: '100%', height: '100%', - backgroundImage: `url('../img/jellyfish-large.png')`, + backgroundImage: `url('../img/space.jpg')`, backgroundSize: 'cover', backgroundPosition: 'center center', color: '#FFFFFF', @@ -67,9 +66,7 @@ export default class Intro extends Component { Welcome to IPFS
- + 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; + } + + & .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/panel.less b/src/styles/panel.less new file mode 100644 index 000000000..1fdc34e63 --- /dev/null +++ b/src/styles/panel.less @@ -0,0 +1,60 @@ +.panel { + height: 100%; + overflow-y: auto; + overflow-x: hidden; + + .header, + .footer { + padding: 1em; + } + + .header { + .title { + font-size: @medium-font; + color: rgba(255, 255, 255, 0.5); + margin: 0 + } + + .subtitle { + margin: 0; + font-size: @big-font; + } + + .buttons { + position: absolute; + top: 1em; + right: 1.5em; + } + } + + .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; + } + } + + &.left-panel { + width: 500px; + background: #000; + } + + &.left-panel .footer { + background: linear-gradient(to top, #000000 0%, #000000 45%, transparent 100%); + left: 0; + width: calc(~"500px - 0.5em") + } +} \ No newline at end of file diff --git a/src/styles/peers.less b/src/styles/peers.less new file mode 100644 index 000000000..e33015d67 --- /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; +} + +.peer > p { + text-overflow: ellipsis; + overflow: hidden; +} + +.peer .label { + font-weight: bold; + color: rgba(255, 255, 255, 0.4); +} + +.peer:hover { + border-color: rgba(255, 255, 255, 0.3); +} + +.peer .id { + margin: 0; +} + +.peer .addr { + margin: 0.5em 0 0 0; + font-size: 0.9em; + color: rgba(255, 255, 255, 0.4) +} 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 From 2aa19c0e89b1ad68790227930a0d8680ec62be48 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Fri, 1 Dec 2017 22:42:25 +0000 Subject: [PATCH 02/51] space --- src/img/space.jpg | Bin 0 -> 4548478 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/img/space.jpg diff --git a/src/img/space.jpg b/src/img/space.jpg new file mode 100644 index 0000000000000000000000000000000000000000..968af0488531a927a490709b689e781d6f4bc308 GIT binary patch literal 4548478 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