├── .eslintignore
├── demo.gif
├── app
├── popup
│ ├── root.jsx
│ ├── img
│ │ ├── stars.png
│ │ ├── offline-icon.png
│ │ ├── jellyfish-blur.png
│ │ └── jellyfish-large.png
│ ├── fonts
│ │ ├── dripicons.ttf
│ │ ├── maven_pro_bold-webfont.ttf
│ │ ├── maven_pro_medium-webfont.ttf
│ │ └── maven_pro_regular-webfont.ttf
│ ├── styles
│ │ ├── common.less
│ │ ├── animations.less
│ │ ├── file-drop.less
│ │ ├── menu.less
│ │ ├── loader.less
│ │ └── fonts.less
│ └── js
│ │ ├── screens
│ │ ├── README.md
│ │ ├── menu
│ │ │ ├── start.js
│ │ │ └── profile.js
│ │ └── menu.js
│ │ └── components
│ │ └── view
│ │ ├── icon.js
│ │ ├── ipfs-logo.js
│ │ ├── header.js
│ │ ├── loader.js
│ │ ├── README.md
│ │ ├── details.js
│ │ ├── icon-button.js
│ │ └── simple-stat.js
└── options
│ ├── root.jsx
│ └── components
│ ├── menu.jsx
│ └── settings.jsx
├── .npmignore
├── chrome
├── assets
│ └── img
│ │ ├── icon-16.png
│ │ ├── icon-48.png
│ │ └── icon-128.png
├── app
│ ├── background
│ │ ├── index.js
│ │ ├── storage-defaults.js
│ │ ├── badge.js
│ │ ├── interceptor.js
│ │ └── api.js
│ ├── popup
│ │ └── index.js
│ └── options
│ │ └── index.js
├── views
│ ├── background.pug
│ ├── options.pug
│ └── popup.pug
├── manifest.prod.json
└── manifest.dev.json
├── .babelrc
├── .gitignore
├── LICENSE
├── webpack
├── replace
│ ├── log-apply-result.js
│ └── JsonpMainTemplate.runtime.js
├── prod.config.js
└── dev.config.js
├── package.json
├── README.md
└── gulpfile.babel.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | dev
4 | webpack/replace
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbaiodias/ipfs-chrome-station/HEAD/demo.gif
--------------------------------------------------------------------------------
/app/popup/root.jsx:
--------------------------------------------------------------------------------
1 | import Menu from './js/screens/menu'
2 |
3 | export default Menu
4 |
--------------------------------------------------------------------------------
/app/options/root.jsx:
--------------------------------------------------------------------------------
1 | import Menu from './components/menu'
2 |
3 | export default Menu
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | .DS_Store
4 |
5 | build/
6 | dev/
7 |
8 | *.zip
--------------------------------------------------------------------------------
/app/popup/img/stars.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbaiodias/ipfs-chrome-station/HEAD/app/popup/img/stars.png
--------------------------------------------------------------------------------
/app/popup/fonts/dripicons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbaiodias/ipfs-chrome-station/HEAD/app/popup/fonts/dripicons.ttf
--------------------------------------------------------------------------------
/app/popup/styles/common.less:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | font-family: 'Maven Pro', sans-serif;
4 | overflow: hidden;
5 | }
6 |
--------------------------------------------------------------------------------
/chrome/assets/img/icon-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbaiodias/ipfs-chrome-station/HEAD/chrome/assets/img/icon-16.png
--------------------------------------------------------------------------------
/chrome/assets/img/icon-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbaiodias/ipfs-chrome-station/HEAD/chrome/assets/img/icon-48.png
--------------------------------------------------------------------------------
/app/popup/img/offline-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbaiodias/ipfs-chrome-station/HEAD/app/popup/img/offline-icon.png
--------------------------------------------------------------------------------
/chrome/assets/img/icon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbaiodias/ipfs-chrome-station/HEAD/chrome/assets/img/icon-128.png
--------------------------------------------------------------------------------
/app/popup/img/jellyfish-blur.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbaiodias/ipfs-chrome-station/HEAD/app/popup/img/jellyfish-blur.png
--------------------------------------------------------------------------------
/app/popup/img/jellyfish-large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbaiodias/ipfs-chrome-station/HEAD/app/popup/img/jellyfish-large.png
--------------------------------------------------------------------------------
/chrome/app/background/index.js:
--------------------------------------------------------------------------------
1 | require('./storage-defaults')
2 | require('./badge')
3 | require('./interceptor')
4 | require('./api')
5 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [ "es2015", "stage-0", "react" ],
3 | "plugins": [ "add-module-exports", "transform-decorators-legacy" ]
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | .DS_Store
4 |
5 | build/
6 | dev/
7 |
8 | *.zip
9 | *.swp
10 |
11 | *.crx
12 | *.pem
13 |
--------------------------------------------------------------------------------
/app/popup/fonts/maven_pro_bold-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbaiodias/ipfs-chrome-station/HEAD/app/popup/fonts/maven_pro_bold-webfont.ttf
--------------------------------------------------------------------------------
/app/popup/fonts/maven_pro_medium-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbaiodias/ipfs-chrome-station/HEAD/app/popup/fonts/maven_pro_medium-webfont.ttf
--------------------------------------------------------------------------------
/app/popup/fonts/maven_pro_regular-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbaiodias/ipfs-chrome-station/HEAD/app/popup/fonts/maven_pro_regular-webfont.ttf
--------------------------------------------------------------------------------
/chrome/views/background.pug:
--------------------------------------------------------------------------------
1 | doctype html
2 |
3 | html
4 | head
5 | script(src=env == 'prod' ? '/background.bundle.js' : 'http://localhost:3000/js/background.bundle.js')
6 |
--------------------------------------------------------------------------------
/chrome/app/popup/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import Root from '../../../app/popup/root'
4 |
5 | ReactDOM.render(
6 | ,
7 | document.querySelector('#root')
8 | )
9 |
--------------------------------------------------------------------------------
/chrome/app/options/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import Root from '../../../app/options/root'
4 |
5 | ReactDOM.render(
6 | ,
7 | document.querySelector('#root')
8 | )
9 |
--------------------------------------------------------------------------------
/app/popup/js/screens/README.md:
--------------------------------------------------------------------------------
1 | # Screens
2 |
3 | A screen is the everything visible at a given point in time inside a single window.
4 | These are implemented as react components, but differ in so far as they are an aggregation
5 | of multiple small components to one larger, complete picture.
6 |
--------------------------------------------------------------------------------
/chrome/views/options.pug:
--------------------------------------------------------------------------------
1 | doctype html
2 |
3 | html
4 | head
5 | meta(charset='UTF-8')
6 | title IPFS Station Options
7 | style.
8 | body { width: 400px; min-height: 220px }
9 | body
10 | #root
11 | script(src=env == 'prod' ? '/options.bundle.js' : 'http://localhost:3000/js/options.bundle.js')
12 |
--------------------------------------------------------------------------------
/app/popup/styles/animations.less:
--------------------------------------------------------------------------------
1 | .fade-enter {
2 | opacity: 0.01;
3 | }
4 |
5 | .fade-enter.fade-enter-active {
6 | opacity: 1;
7 | transition: opacity .3s ease-in;
8 | }
9 |
10 | .fade-leave {
11 | opacity: 1;
12 | }
13 |
14 | .fade-leave.fade-leave-active {
15 | opacity: 0.01;
16 | transition: opacity .2s ease-in;
17 | }
18 |
--------------------------------------------------------------------------------
/chrome/views/popup.pug:
--------------------------------------------------------------------------------
1 | doctype html
2 |
3 | html
4 | head
5 | meta(charset='UTF-8')
6 | title IPFS Station
7 | style.
8 | body { width: 450px; min-height: 250px; background-color: #19b5fe; }
9 |
10 | body
11 | #root
12 | script(src=env == 'prod' ? '/popup.bundle.js' : 'http://localhost:3000/js/popup.bundle.js')
13 |
--------------------------------------------------------------------------------
/app/popup/styles/file-drop.less:
--------------------------------------------------------------------------------
1 | .file-drop {
2 | position: absolute;
3 | top: 40px;
4 | left: 0;
5 | right: 0;
6 | bottom: 70px;
7 | }
8 |
9 | .file-drop .file-drop-target.file-drop-dragging-over-frame,
10 | .file-drop .file-drop-target.file-drop-dragging-over-target {
11 | height: 100%;
12 | width: 100%;
13 | background: rgba(255, 255, 255, 0.8);
14 | }
--------------------------------------------------------------------------------
/app/popup/js/components/view/icon.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react'
2 | import Radium from 'radium'
3 |
4 | @Radium
5 | export default class Icon extends Component {
6 | static propTypes = {
7 | name: PropTypes.string.isRequired
8 | };
9 |
10 | render () {
11 | return (
12 |
13 | )
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/chrome/app/background/storage-defaults.js:
--------------------------------------------------------------------------------
1 | /* global chrome */
2 |
3 | const defaultValues = {
4 | redirecting: false,
5 | host: 'localhost',
6 | port: 8080,
7 | apiPort: 5001,
8 | apiInterval: 5000
9 | }
10 |
11 | chrome.storage.local.set({
12 | running: false,
13 | peersCount: 0
14 | })
15 |
16 | chrome.storage.sync.get(Object.keys(defaultValues), (result) => {
17 | const next = {
18 | ...defaultValues,
19 | ...result
20 | }
21 |
22 | chrome.storage.sync.set(next)
23 | })
24 |
--------------------------------------------------------------------------------
/app/popup/js/components/view/ipfs-logo.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const styles = {
4 | wrapper: {
5 | display: 'flex'
6 | },
7 | image: {
8 | marginRight: '5px'
9 | }
10 | }
11 |
12 | const logoPath = require('ipfs-logo/raster-generated/ipfs-logo-128-white-outline.png')
13 |
14 | export default function IPFSLogo () {
15 | return (
16 |
17 |
23 | IPFS
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/chrome/app/background/badge.js:
--------------------------------------------------------------------------------
1 | /* global chrome */
2 |
3 | chrome.storage.onChanged.addListener(function (changes, namespace) {
4 | Object.keys(changes).forEach(key => {
5 | const storageChange = changes[key]
6 |
7 | if (key === 'running' && storageChange.newValue === false) {
8 | chrome.browserAction.setBadgeBackgroundColor({ color: '#ff0000' })
9 | chrome.browserAction.setBadgeText({ text: 'off' })
10 | } else if (key === 'peersCount') {
11 | chrome.browserAction.setBadgeBackgroundColor({ color: '#0000ff' })
12 | chrome.browserAction.setBadgeText({ text: storageChange.newValue.toString() })
13 | }
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/app/popup/js/components/view/header.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react'
2 | import Radium from 'radium'
3 |
4 | import IPFSLogo from './ipfs-logo'
5 |
6 | @Radium
7 | export default class Header extends Component {
8 | static propTypes = {
9 | style: PropTypes.object
10 | };
11 |
12 | render () {
13 | const styles = {
14 | wrapper: {
15 | display: 'flex',
16 | height: '40px'
17 | },
18 | text: {
19 | alignSelf: 'center',
20 | flex: '1',
21 | paddingTop: '4px'
22 | }
23 | }
24 |
25 | return (
26 |
31 | )
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/popup/styles/menu.less:
--------------------------------------------------------------------------------
1 | html {
2 | width: 240px;
3 | overflow: hidden;
4 | }
5 |
6 | .padding {
7 | padding: 8px;
8 | }
9 |
10 | .status {
11 | text-align: right;
12 | }
13 |
14 | .value {
15 | text-align: right;
16 | }
17 |
18 | .panel {
19 | padding: 4px;
20 | margin-top: 10px;
21 | margin-bottom: 0;
22 | }
23 |
24 | #logo {
25 | display: table;
26 | width: 100%;
27 | }
28 |
29 | .control {
30 | display: block;
31 | margin-top: 6px;
32 | }
33 |
34 | .toggle {
35 | height: 32px !important;
36 | width: 64px !important;
37 | }
38 |
39 | .cell {
40 | width: 33%;
41 | display: table-cell;
42 | text-align: center;
43 | padding-bottom: 6px;
44 | }
45 |
46 | .version {
47 | text-align: center;
48 | margin-bottom: 4px;
49 | }
50 |
51 | .nomarginbottom {
52 | margin-bottom: 0;
53 | }
--------------------------------------------------------------------------------
/chrome/manifest.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.1.2",
3 | "name": "IPFS Station",
4 | "manifest_version": 2,
5 | "description": "This extension let's you access IPFS urls seamlessly from your local IPFS node, and take a look at its stats.",
6 | "browser_action": {
7 | "default_title": "IPFS Station",
8 | "default_popup": "popup.html"
9 | },
10 | "icons": {
11 | "16": "img/icon-16.png",
12 | "48": "img/icon-48.png",
13 | "128": "img/icon-128.png"
14 | },
15 | "background": {
16 | "page": "background.html"
17 | },
18 | "options_ui": {
19 | "page": "options.html",
20 | "chrome_style": true
21 | },
22 | "permissions": [
23 | "webRequest",
24 | "webRequestBlocking",
25 | "tabs",
26 | "storage",
27 | ""
28 | ],
29 | "content_security_policy": "default-src 'self'; script-src 'self' 'unsafe-eval'; connect-src *; style-src * 'unsafe-inline'; img-src 'self' data:;"
30 | }
31 |
--------------------------------------------------------------------------------
/app/popup/js/components/view/loader.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | // TODO: Extract from less file into here
4 | import '../../../styles/loader.less'
5 |
6 | export default function Loader () {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/app/options/components/menu.jsx:
--------------------------------------------------------------------------------
1 | /* global chrome */
2 | import React, {Component} from 'react'
3 | import Settings from './settings'
4 |
5 | const settingsKeys = ['redirecting', 'host', 'port', 'apiPort', 'apiInterval']
6 |
7 | export default class Menu extends Component {
8 | state = {};
9 |
10 | constructor (props) {
11 | super(props)
12 |
13 | this.handleSubmit = this.handleSubmit.bind(this)
14 |
15 | chrome.storage.sync.get(settingsKeys, (settings) => {
16 | console.log('got settings', settings)
17 | this.setState(settings)
18 | })
19 | }
20 |
21 | handleSubmit (values) {
22 | chrome.storage.sync.set(values, () => {
23 | console.log('saved settings', values)
24 | window.close()
25 | })
26 | }
27 |
28 | render () {
29 | return (
30 |
35 | )
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/chrome/manifest.dev.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.1.2",
3 | "name": "IPFS Station",
4 | "manifest_version": 2,
5 | "description": "This extension let's you access IPFS urls seamlessly from your local IPFS node, and take a look at its stats.",
6 | "browser_action": {
7 | "default_title": "IPFS Station",
8 | "default_popup": "popup.html"
9 | },
10 | "icons": {
11 | "16": "img/icon-16.png",
12 | "48": "img/icon-48.png",
13 | "128": "img/icon-128.png"
14 | },
15 | "background": {
16 | "page": "background.html"
17 | },
18 | "options_ui": {
19 | "page": "options.html",
20 | "chrome_style": true
21 | },
22 | "permissions": [
23 | "webRequest",
24 | "webRequestBlocking",
25 | "tabs",
26 | "storage",
27 | ""
28 | ],
29 | "content_security_policy": "default-src 'self' http://localhost:3000; script-src 'self' http://localhost:3000 'unsafe-eval'; connect-src *; style-src * 'unsafe-inline'; img-src 'self' http://localhost:3000 data:;"
30 | }
31 |
--------------------------------------------------------------------------------
/app/popup/js/components/view/README.md:
--------------------------------------------------------------------------------
1 | # View Components
2 |
3 | In this directory only components that have no own state are stored.
4 | Having no state on themselves means they are only responsible for
5 | displaying the data they are given.
6 |
7 | They are usually written as *stateless functional components*,
8 |
9 | ```js
10 | export default function MyComponent ({name}) {
11 | return (
12 | Hello my name is, {name}.
13 | )
14 | }
15 | ```
16 |
17 | As a lot of styling does require the library [Radium](https://github.com/FormidableLabs/radium)
18 | for now these components have to be written like with the `class` notation,
19 |
20 | ```js
21 | @Radium
22 | export default class MyComponent extends Component {
23 | render () {
24 | const stlyes = {
25 | name: {
26 | color: 'read'
27 | }
28 | }
29 |
30 | return (
31 | Hello my name is, {name}.
32 | )
33 | }
34 | }
35 | ```
36 |
37 | until [FormidableLabs/radium#353](https://github.com/FormidableLabs/radium/issues/353) is
38 | resolved.
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Francisco Dias
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/webpack/replace/log-apply-result.js:
--------------------------------------------------------------------------------
1 | /* global chrome */
2 | /*
3 | MIT License http://www.opensource.org/licenses/mit-license.php
4 | Author Tobias Koppers @sokra
5 | */
6 | module.exports = function (updatedModules, renewedModules) {
7 | var unacceptedModules = updatedModules.filter(function (moduleId) {
8 | return renewedModules && renewedModules.indexOf(moduleId) < 0
9 | })
10 |
11 | if (unacceptedModules.length > 0) {
12 | console.warn('[HMR] The following modules couldn\'t be hot updated: (They would need a full reload!)')
13 | unacceptedModules.forEach(function (moduleId) {
14 | console.warn('[HMR] - ' + moduleId)
15 | })
16 |
17 | if (chrome && chrome.runtime && chrome.runtime.reload) {
18 | console.warn('[HMR] extension reload')
19 | chrome.runtime.reload()
20 | } else {
21 | console.warn('[HMR] Can\'t extension reload. not found chrome.runtime.reload.')
22 | }
23 | }
24 |
25 | if (!renewedModules || renewedModules.length === 0) {
26 | console.log('[HMR] Nothing hot updated.')
27 | } else {
28 | console.log('[HMR] Updated modules:')
29 | renewedModules.forEach(function (moduleId) {
30 | console.log('[HMR] - ' + moduleId)
31 | })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/popup/js/components/view/details.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react'
2 | import Radium from 'radium'
3 |
4 | @Radium
5 | export default class Details extends Component {
6 | static propTypes = {
7 | agentVersion: PropTypes.string,
8 | protocolVersion: PropTypes.string,
9 | host: PropTypes.string,
10 | port: PropTypes.number
11 | };
12 |
13 | static defaultProps = {
14 | peer: {}
15 | };
16 |
17 | render () {
18 | const styles = {
19 | wrapper: {
20 | display: 'flex',
21 | flexDirection: 'column',
22 | justifyContent: 'center',
23 | textAlign: 'center'
24 | },
25 | entry: {
26 | color: 'rgba(0, 0, 0, 0.5)',
27 | padding: '2px 0',
28 | fontSize: '13px',
29 | fontWeight: '500'
30 | },
31 | title: {
32 | fontWeight: 'bold',
33 | fontSize: '20px'
34 | }
35 | }
36 |
37 | return (
38 |
39 |
40 | Details
41 |
42 |
43 | Agent: {this.props.agentVersion}
44 |
45 |
46 | Protocol: {this.props.protocolVersion}
47 |
48 |
49 | Gateway: {`${this.props.host}:${this.props.port}`}
50 |
51 |
52 | )
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/popup/js/components/view/icon-button.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react'
2 | import Radium from 'radium'
3 |
4 | @Radium
5 | export default class IconButton extends Component {
6 | static propTypes = {
7 | name: PropTypes.string,
8 | icon: PropTypes.string,
9 | onClick: PropTypes.func,
10 | style: PropTypes.object,
11 | iconStyle: PropTypes.object
12 | };
13 |
14 | static defaultProps = {
15 | name: null,
16 | icon: '',
17 | onClick () {},
18 | style: {},
19 | iconStyle: {}
20 | };
21 |
22 | render () {
23 | const styles = {
24 | button: {
25 | opacity: 0.8,
26 | background: 'none',
27 | border: 'none',
28 | flex: '1',
29 | padding: '0 10px',
30 | textAlign: 'center',
31 | fontSize: '12px',
32 | textTransform: 'uppercase',
33 | transition: 'color 0.3s ease-in-out',
34 | ':focus': {
35 | outline: 'none'
36 | },
37 | ':hover': {
38 | opacity: 1,
39 | cursor: 'pointer'
40 | },
41 | ...this.props.style
42 | }
43 | }
44 |
45 | return (
46 |
47 |
51 | {this.props.name}
52 |
53 | )
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/popup/js/components/view/simple-stat.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react'
2 | import Radium from 'radium'
3 |
4 | @Radium
5 | export default class SimpleStat extends Component {
6 | static propTypes = {
7 | name: PropTypes.string.isRequired,
8 | value: PropTypes.number.isRequired,
9 | color: PropTypes.string
10 | };
11 |
12 | static defaultProps = {
13 | color: '#50d2c2'
14 | };
15 |
16 | render () {
17 | const styles = {
18 | wrapper: {
19 | display: 'flex',
20 | flexDirection: 'column',
21 | justifyContent: 'center'
22 | },
23 | ring: {
24 | width: '16px',
25 | height: '16px',
26 | borderRadius: '8px',
27 | border: `3px solid ${this.props.color}`,
28 | margin: '0 auto'
29 | },
30 | label: {
31 | textTransform: 'uppercase',
32 | color: 'rgba(0, 0, 0, 0.5)',
33 | padding: '10px 0 5px',
34 | fontSize: '13px',
35 | fontWeight: '500'
36 | },
37 | value: {
38 | textAlign: 'center',
39 | fontWeight: 'bold',
40 | fontSize: '20px'
41 | }
42 | }
43 |
44 | return (
45 |
46 |
47 |
48 | {this.props.name}
49 |
50 |
51 | {this.props.value}
52 |
53 |
54 | )
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/popup/js/screens/menu/start.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react'
2 | import Radium from 'radium'
3 |
4 | import Header from '../../components/view/header'
5 |
6 | import 'normalize.css'
7 | import 'css-box-sizing-border-box/index.css'
8 | import '../../../styles/common.less'
9 | import '../../../styles/fonts.less'
10 |
11 | @Radium
12 | export default class StartScreen extends Component {
13 | static propTypes = {
14 | onReportProblemClick: PropTypes.func
15 | };
16 |
17 | static defaultProps = {
18 | onReportProblemClick () {}
19 | };
20 |
21 | render () {
22 | const styles = {
23 | wrapper: {
24 | display: 'flex',
25 | width: '100%',
26 | height: '100%',
27 | backgroundColor: '#19b5fe',
28 | color: '#FFFFFF',
29 | flexDirection: 'column',
30 | alignItems: 'center'
31 | },
32 | content: {
33 | display: 'flex',
34 | flex: '1',
35 | margin: '40px 0',
36 | flexDirection: 'column'
37 | },
38 | text: {
39 | padding: '40px 0',
40 | textAlign: 'center'
41 | },
42 | link: {
43 | cursor: 'pointer',
44 | textAlign: 'center',
45 | textDecoration: 'underline'
46 | }
47 | }
48 |
49 | return (
50 |
51 |
52 |
53 |
59 |
60 |
Oh snap, it looks like your node is not running yet.
61 |
Please start it by running ipfs daemon on your terminal.
62 |
63 | Also, please make sure you have CORS enabled, by running
64 | {`ipfs config --json API.HTTPHeaders '{"Access-Control-Allow-Origin": ["*"]}'`}
65 |
66 |
67 |
Report a problem
68 |
69 |
70 | )
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/chrome/app/background/interceptor.js:
--------------------------------------------------------------------------------
1 | /* global chrome */
2 | import url from 'url'
3 | import { url as isIPFSUrl } from 'is-ipfs'
4 |
5 | const settingsKeys = ['redirecting', 'host', 'port']
6 | let settings = {}
7 |
8 | function interceptor (details) {
9 | var parsedUrl = url.parse(details.url)
10 | if (isIPFSUrl(details.url) && parsedUrl.host.indexOf(settings.host) === -1) {
11 | const node = `${settings.host}:${settings.port}`
12 |
13 | parsedUrl.protocol = 'http:'
14 | parsedUrl.host = node
15 | parsedUrl.hostname = node
16 | const localUrl = url.format(parsedUrl)
17 | console.log('redirected', details.url, 'to', node)
18 | return { redirectUrl: localUrl }
19 | }
20 | }
21 |
22 | function startInterceptor () {
23 | console.log('starting interceptor')
24 | chrome.webRequest.onBeforeRequest.addListener(interceptor,
25 | { urls: ['*://*/ipfs/*', '*://*/ipns/*'] },
26 | ['blocking']
27 | )
28 | }
29 |
30 | function stopInterceptor () {
31 | console.log('stopping interceptor')
32 | chrome.webRequest.onBeforeRequest.removeListener(interceptor)
33 | }
34 |
35 | chrome.storage.onChanged.addListener(function (changes, namespace) {
36 | Object.keys(changes).forEach(key => {
37 | var storageChange = changes[key]
38 |
39 | if (settingsKeys.indexOf(key) !== -1) {
40 | settings[key] = storageChange.newValue
41 | }
42 |
43 | if (key === 'redirecting') {
44 | if (storageChange.newValue === true) {
45 | startInterceptor()
46 | } else {
47 | stopInterceptor()
48 | }
49 | } else if (key === 'running') {
50 | if (storageChange.newValue === true) {
51 | chrome.storage.sync.get('redirecting', function (result) {
52 | if (result.redirecting === true) {
53 | startInterceptor()
54 | }
55 | })
56 | } else {
57 | stopInterceptor()
58 | }
59 | }
60 | })
61 | })
62 |
63 | chrome.storage.sync.get(settingsKeys, (result) => {
64 | settings = result
65 |
66 | const { redirecting } = settings
67 | if (redirecting === true) {
68 | chrome.storage.local.get('running', ({ running }) => {
69 | if (running === true) {
70 | startInterceptor()
71 | }
72 | })
73 | }
74 | })
75 |
--------------------------------------------------------------------------------
/webpack/prod.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import webpack from 'webpack'
3 |
4 | export default {
5 | entry: {
6 | popup: [ path.join(__dirname, '../chrome/app/popup/index') ],
7 | background: [ path.join(__dirname, '../chrome/app/background/index') ],
8 | options: [ path.join(__dirname, '../chrome/app/options/index') ]
9 | },
10 | output: {
11 | path: path.join(__dirname, '../build'),
12 | filename: '[name].bundle.js',
13 | chunkFilename: '[id].chunk.js'
14 | },
15 | plugins: [
16 | new webpack.DefinePlugin({
17 | 'process.env': {
18 | NODE_ENV: '"production"'
19 | }
20 | }),
21 | new webpack.IgnorePlugin(/[^/]+\/[\S]+.dev$/),
22 | new webpack.optimize.DedupePlugin()
23 | // new webpack.optimize.UglifyJsPlugin({
24 | // comments: false,
25 | // compressor: {
26 | // warnings: false
27 | // }
28 | // })
29 | ],
30 | resolve: {
31 | modulesDirectories: [
32 | 'node_modules'
33 | ],
34 | alias: {
35 | http: 'stream-http',
36 | https: 'https-browserify'
37 | },
38 | extensions: ['', '.js', '.jsx', '.json']
39 | },
40 | module: {
41 | loaders: [
42 | {
43 | test: /\.(js|jsx)$/,
44 | loader: 'babel',
45 | exclude: /node_modules/
46 | },
47 | {
48 | test: /\.js$/,
49 | include: /node_modules\/(hoek|qs|wreck|boom)/,
50 | loader: 'babel'
51 | },
52 | {
53 | test: /\.json$/,
54 | loader: 'json-loader'
55 | },
56 | {
57 | test: /\.css$/,
58 | loaders: ['style', 'css']
59 | },
60 | {
61 | test: /\.less$/,
62 | loaders: ['style', 'css', 'less']
63 | },
64 | {
65 | test: /\.woff(2)?(\?v=[0-9].[0-9].[0-9])?$/,
66 | loader: 'file-loader?name=[name].[ext]'
67 | },
68 | {
69 | test: /\.(ttf|eot|svg)(\?v=[0-9].[0-9].[0-9])?$/,
70 | loader: 'file-loader?name=[name].[ext]'
71 | },
72 | {
73 | test: /\.(png|gif|jpg|jpeg)$/,
74 | loader: 'file-loader?name=[name].[ext]'
75 | }
76 | ]
77 | },
78 | externals: {
79 | net: '{}',
80 | fs: '{}',
81 | tls: '{}',
82 | console: '{}',
83 | 'require-dir': '{}'
84 | },
85 | timeout: 60000
86 | }
87 |
--------------------------------------------------------------------------------
/chrome/app/background/api.js:
--------------------------------------------------------------------------------
1 | /* global chrome */
2 | import ipfsAPI from 'ipfs-api'
3 | import { get } from 'lodash'
4 |
5 | const settingsKeys = ['host', 'apiPort', 'apiInterval']
6 | let settings = {}
7 |
8 | let ipfs
9 | let updateInterval
10 |
11 | function connectToAPI () {
12 | ipfs = ipfsAPI(settings.host, settings.apiPort)
13 | console.log('changed api to', settings.host, settings.apiPort)
14 |
15 | ipfs.id((err, peer) => {
16 | if (err) return
17 |
18 | chrome.storage.local.set({
19 | id: peer.id,
20 | agentVersion: peer.agentVersion,
21 | protocolVersion: peer.protocolVersion
22 | })
23 | })
24 | }
25 |
26 | function updatePeersCount () {
27 | ipfs.swarm.peers((err, res) => {
28 | if (err) {
29 | chrome.storage.local.set({
30 | peersCount: 0,
31 | running: false
32 | })
33 | return
34 | }
35 |
36 | chrome.storage.local.set({
37 | peersCount: res.length,
38 | running: true
39 | })
40 | })
41 | }
42 |
43 | chrome.storage.onChanged.addListener(function (changes, namespace) {
44 | let needsRestart = false
45 | Object.keys(changes).forEach(key => {
46 | var storageChange = changes[key]
47 |
48 | if (settingsKeys.indexOf(key) !== -1) {
49 | settings[key] = storageChange.newValue
50 | }
51 |
52 | needsRestart = (key === 'host' || key === 'apiPort')
53 |
54 | if (key === 'apiInterval' && updateInterval) {
55 | clearInterval(updateInterval)
56 | updateInterval = setInterval(updatePeersCount, settings.apiInterval)
57 | console.log('changed update interval to', settings.apiInterval)
58 | }
59 | })
60 |
61 | if (needsRestart) {
62 | connectToAPI()
63 | }
64 | })
65 |
66 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
67 | if (request.method) {
68 | const method = get(ipfs, request.method)
69 | const args = request.args || []
70 |
71 | args.push(function () {
72 | console.log(request.method, 'result', arguments)
73 | sendResponse(arguments)
74 | })
75 |
76 | console.log('executing', request.method)
77 | method.apply(null, args)
78 |
79 | return true
80 | }
81 | })
82 |
83 | chrome.storage.sync.get(settingsKeys, (result) => {
84 | settings = result
85 |
86 | connectToAPI()
87 |
88 | updatePeersCount()
89 | updateInterval = setInterval(updatePeersCount, settings.apiInterval)
90 | })
91 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ipfs-chrome-station",
3 | "version": "0.1.2",
4 | "description": "Chrome extension that let's you access IPFS urls seamlessly from your local IPFS node, and take a look at its stats.",
5 | "scripts": {
6 | "lint": "standard --verbose | snazzy",
7 | "dev": "gulp",
8 | "build": "gulp build",
9 | "compress": "zip -r build.zip build",
10 | "clean": "rimraf build/ dev/ *.zip *.crx"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/xicombd/ipfs-chrome-station"
15 | },
16 | "homepage": "https://github.com/xicombd/ipfs-chrome-station",
17 | "author": "Francisco Dias ",
18 | "license": "MIT",
19 | "devDependencies": {
20 | "babel-core": "^6.3.15",
21 | "babel-eslint": "^7.1.1",
22 | "babel-loader": "^6.2.0",
23 | "babel-plugin-add-module-exports": "^0.1.1",
24 | "babel-plugin-transform-decorators-legacy": "^1.2.0",
25 | "babel-plugin-transform-runtime": "^6.4.3",
26 | "babel-polyfill": "^6.3.14",
27 | "babel-preset-es2015": "^6.3.13",
28 | "babel-preset-react": "^6.3.13",
29 | "babel-preset-react-hmre": "^1.0.0",
30 | "babel-preset-stage-0": "^6.3.13",
31 | "css-loader": "^0.27.3",
32 | "file-loader": "^0.8.5",
33 | "gulp": "^3.9.0",
34 | "gulp-pug": "^3.3.0",
35 | "gulp-rename": "^1.2.2",
36 | "gulp-util": "^3.0.5",
37 | "https-browserify": "0.0.1",
38 | "jsdom": "^6.5.1",
39 | "json-loader": "^0.5.4",
40 | "less": "^2.7.2",
41 | "less-loader": "^3.0.0",
42 | "merge-stream": "^1.0.0",
43 | "pre-commit": "^1.1.2",
44 | "raw-loader": "^0.5.1",
45 | "rimraf": "^2.4.3",
46 | "snazzy": "^2.0.1",
47 | "standard": "^9.0.2",
48 | "style-loader": "^0.12.3",
49 | "webpack": "^1.12.9",
50 | "webpack-dev-server": "^1.10.1"
51 | },
52 | "dependencies": {
53 | "belle": "^2.0.3",
54 | "css-box-sizing-border-box": "^0.1.0",
55 | "ipfs-api": "^12.1.7",
56 | "ipfs-logo": "github:ipfs/logo",
57 | "is-ipfs": "0.0.4",
58 | "lodash": "^4.2.0",
59 | "normalize.css": "^3.0.3",
60 | "radium": "^0.15.3",
61 | "react": "^0.14.2",
62 | "react-addons-css-transition-group": "^0.14.2",
63 | "react-dom": "^0.14.2",
64 | "request": "^2.65.0",
65 | "stream-http": "^2.1.0"
66 | },
67 | "standard": {
68 | "parser": "babel-eslint"
69 | },
70 | "pre-commit": [
71 | "lint"
72 | ]
73 | }
74 |
--------------------------------------------------------------------------------
/webpack/replace/JsonpMainTemplate.runtime.js:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License http://www.opensource.org/licenses/mit-license.php
3 | Author Tobias Koppers @sokra
4 | */
5 | /* global hotAddUpdateChunk parentHotUpdateCallback document XMLHttpRequest hotCurrentHash $require$ $hotMainFilename$ __webpack_require__ */
6 | module.exports = function () {
7 | function webpackHotUpdateCallback (chunkId, moreModules) { // eslint-disable-line no-unused-vars
8 | hotAddUpdateChunk(chunkId, moreModules)
9 | if (parentHotUpdateCallback) parentHotUpdateCallback(chunkId, moreModules)
10 | }
11 |
12 | var context = this
13 | function evalCode (code, context) {
14 | return (function () { return eval(code) }.call(context)) // eslint-disable-line no-eval
15 | }
16 |
17 | context.hotDownloadUpdateChunk = function (chunkId) { // eslint-disable-line no-unused-vars
18 | var src = __webpack_require__.p + '' + chunkId + '.' + hotCurrentHash + '.hot-update.js'
19 | var request = new XMLHttpRequest()
20 |
21 | request.onload = function () {
22 | evalCode(this.responseText, context)
23 | }
24 | request.open('get', src, true)
25 | request.send()
26 | }
27 |
28 | function hotDownloadManifest (callback) { // eslint-disable-line no-unused-vars
29 | if (typeof XMLHttpRequest === 'undefined') {
30 | return callback(new Error('No browser support'))
31 | }
32 | try {
33 | var request = new XMLHttpRequest()
34 | var requestPath = $require$.p + $hotMainFilename$
35 | request.open('GET', requestPath, true)
36 | request.timeout = 10000
37 | request.send(null)
38 | } catch (err) {
39 | return callback(err)
40 | }
41 | request.onreadystatechange = function () {
42 | if (request.readyState !== 4) return
43 | if (request.status === 0) {
44 | // timeout
45 | callback(new Error('Manifest request to ' + requestPath + ' timed out.'))
46 | } else if (request.status === 404) {
47 | // no update available
48 | callback()
49 | } else if (request.status !== 200 && request.status !== 304) {
50 | // other failure
51 | callback(new Error('Manifest request to ' + requestPath + ' failed.'))
52 | } else {
53 | // success
54 | try {
55 | var update = JSON.parse(request.responseText)
56 | } catch (e) {
57 | callback(e)
58 | return
59 | }
60 | callback(null, update)
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ipfs-chrome-station
2 | ===
3 |
4 | Chrome extension that let's you access [IPFS](https://ipfs.io) urls seamlessly from your local [IPFS node](https://ipfs.io/docs/install/), and take a look at its stats.
5 |
6 | [](https://github.com/feross/standard)
7 | [](https://chrome.google.com/webstore/detail/ipfs-station/kckhgoigikkadogfdiojcblegfhdnjei)
8 |
9 | Based on [react-chrome-extension-boilerplate](https://github.com/jhen0409/react-chrome-extension-boilerplate), [ipfs-station](https://github.com/ipfs/station/) and [ipfs-firefox-addon](https://github.com/lidel/ipfs-firefox-addon) *(if you're using Firefox, make sure to check it out)*.
10 |
11 | ### Demo
12 |
13 | 
14 |
15 | ### Features
16 |
17 | - Icon with badge that shows if the node is running, and how many peers are connected to it
18 | - Clicking on the icon opens popup menu with useful operations:
19 | - See stats of the IPFS node
20 | - Toggle redirection to the IPFS node
21 | - Open IPFS node WebUI
22 | - Open extension options (more about this bellow)
23 | - Additionally, on pages loaded from IPFS:
24 | - Copy canonical IPFS address
25 | - Copy shareable URL to resource at a default public gateway (https://ipfs.io)
26 | - Pin/unpin IPFS Resource
27 | - When redirection is on, requests to `https?://*/(ipfs|ipns)/$RESOURCE` are replaced with `http://localhost:8080/(ipfs|ipns)/$RESOURCE`
28 | - Options menu that let's you customize several parameters:
29 | - IPFS node host
30 | - IPFS node port
31 | - IPFS node API port
32 | - API stats polling interval
33 | - Toggle redirection
34 |
35 | ### Installation
36 |
37 | ```bash
38 | # git clone ...
39 |
40 | npm install
41 | ```
42 |
43 | ### Development
44 |
45 | * Run script
46 | ```bash
47 | # build files to './dev'
48 | # start webpack dev server
49 | npm run dev
50 | ```
51 | * Go to [chrome://extensions/](chrome://extensions/) and check `Developer Mode` box
52 | * Click `Load unpacked extension...` and select the `dev` folder
53 |
54 | ### Build
55 |
56 | ```bash
57 | # build files to './build'
58 | npm run build
59 | ```
60 |
61 | ### Compress ZIP file
62 |
63 | ```bash
64 | # compress build folder to build.zip
65 | npm run compress
66 | ```
67 |
68 | ### LICENSE
69 |
70 | [MIT](LICENSE)
71 |
--------------------------------------------------------------------------------
/webpack/dev.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import webpack from 'webpack'
3 |
4 | const port = 3000
5 | const entry = [
6 | `webpack-dev-server/client?http://localhost:${port}`,
7 | 'webpack/hot/only-dev-server'
8 | ]
9 |
10 | export default {
11 | devtool: 'source-map',
12 | entry: {
13 | popup: [ path.join(__dirname, '../chrome/app/popup/index'), ...entry ],
14 | background: [ path.join(__dirname, '../chrome/app/background/index'), ...entry ],
15 | options: [ path.join(__dirname, '../chrome/app/options/index'), ...entry ]
16 | },
17 | output: {
18 | path: path.join(__dirname, '../dev/js'),
19 | filename: '[name].bundle.js',
20 | chunkFilename: '[id].chunk.js',
21 | publicPath: `http://localhost:${port}/js/`
22 | },
23 | plugins: [
24 | new webpack.HotModuleReplacementPlugin(),
25 | new webpack.NoErrorsPlugin(),
26 | new webpack.IgnorePlugin(/[^/]+\/[\S]+.prod$/),
27 | new webpack.DefinePlugin({
28 | 'process.env': {
29 | DEVTOOLS: !!process.env.DEVTOOLS || true,
30 | DEVTOOLS_EXT: !!process.env.DEVTOOLS_EXT
31 | }
32 | })
33 | ],
34 | resolve: {
35 | modulesDirectories: [
36 | 'node_modules'
37 | ],
38 | alias: {
39 | http: 'stream-http',
40 | https: 'https-browserify'
41 | },
42 | extensions: ['', '.js', '.jsx', '.json']
43 | },
44 | module: {
45 | loaders: [
46 | {
47 | test: /\.(js|jsx)$/,
48 | exclude: /node_modules/,
49 | loader: 'babel',
50 | query: {
51 | presets: ['react-hmre'],
52 | plugins: ['transform-runtime']
53 | }
54 | },
55 | {
56 | test: /\.js$/,
57 | include: /node_modules\/(hoek|qs|wreck|boom)/,
58 | loader: 'babel',
59 | query: {
60 | presets: ['es2015'],
61 | plugins: ['transform-runtime']
62 | }
63 | },
64 | {
65 | test: /\.json$/,
66 | loader: 'json'
67 | },
68 | {
69 | test: /\.css$/,
70 | loaders: ['style', 'css']
71 | },
72 | {
73 | test: /\.less$/,
74 | loaders: ['style', 'css', 'less']
75 | },
76 | {
77 | test: /\.woff(2)?(\?v=[0-9].[0-9].[0-9])?$/,
78 | loader: 'file-loader?name=[name].[ext]'
79 | },
80 | {
81 | test: /\.(ttf|eot|svg)(\?v=[0-9].[0-9].[0-9])?$/,
82 | loader: 'file-loader?name=[name].[ext]'
83 | },
84 | {
85 | test: /\.(png|gif|jpg|jpeg)$/,
86 | loader: 'file-loader?name=[name].[ext]'
87 | }
88 | ]
89 | },
90 | externals: {
91 | net: '{}',
92 | fs: '{}',
93 | tls: '{}',
94 | console: '{}',
95 | 'require-dir': '{}'
96 | },
97 | timeout: 60000
98 | }
99 |
--------------------------------------------------------------------------------
/app/options/components/settings.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'
2 | import { Toggle, TextInput, Button } from 'belle'
3 |
4 | export default class Settings extends Component {
5 | state = {};
6 |
7 | static propTypes = {
8 | settings: PropTypes.object,
9 | onSubmit: PropTypes.func
10 | };
11 |
12 | constructor (props) {
13 | super(props)
14 |
15 | this.state = props.settings
16 | }
17 |
18 | componentWillReceiveProps (nextProps) {
19 | const {
20 | redirecting = this.state.redirecting,
21 | host = this.state.host,
22 | port = this.state.port,
23 | apiPort = this.state.apiPort,
24 | apiInterval = this.state.apiInterval
25 | } = nextProps.settings
26 |
27 | this.setState({ redirecting, host, port, apiPort, apiInterval })
28 | }
29 |
30 | handleChange (field, isNumber, { value }) {
31 | const nextState = {
32 | [field]: isNumber ? parseInt(value, 10) : value
33 | }
34 |
35 | console.log('change', nextState)
36 |
37 | this.setState(nextState)
38 | }
39 |
40 | handleSubmit () {
41 | this.props.onSubmit(this.state)
42 | }
43 |
44 | render () {
45 | const {
46 | redirecting,
47 | host,
48 | port,
49 | apiPort,
50 | apiInterval
51 | } = this.state
52 |
53 | const style = {
54 | toggle: {
55 | base: {
56 | transform: 'scale(0.7)',
57 | verticalAlign: 'middle',
58 | marginLeft: '-8px'
59 | },
60 | label: {
61 | fontSize: '14px',
62 | verticalAlign: 'sub'
63 | }
64 | },
65 | node: {
66 | paddingLeft: '10px'
67 | },
68 | button: {
69 | marginTop: '20px'
70 | }
71 | }
72 |
73 | return (
74 |
116 |
117 | )
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/app/popup/styles/loader.less:
--------------------------------------------------------------------------------
1 | // Loading animation taken from
2 | // http://codepen.io/thiesbrake/pen/bvLcI
3 |
4 | // 1. Variables
5 |
6 | @floatTime: 4s;
7 | @inital: 1;
8 | @downstate: 0.6;
9 | @originOne: 22.5px;
10 | @originTwo: 22.5px;
11 | @strokeDash: 142;
12 |
13 | // 2. Animation-Setup
14 |
15 | .spinner_svg{
16 | position: relative;
17 | z-index: 400;
18 | top: -50%;
19 | left: -50%;
20 | }
21 |
22 | #Group{
23 | stroke-dasharray: @strokeDash;
24 | stroke-dashoffset: @strokeDash;
25 | animation: dash 8s cubic-bezier(.02,.61,.85,.98) infinite;
26 | }
27 |
28 | #Oval-1{
29 | fill: none;
30 | transform-origin: @originOne @originTwo;
31 | animation: pulsate 8s infinite;
32 | }
33 |
34 | #Oval-2, #Oval-3, #Oval-4{
35 | fill: none;
36 | transform-origin: @originOne @originTwo;
37 | opacity: 0;
38 | }
39 |
40 | #Oval-2{
41 | animation: float 8s ease 3s infinite;
42 | }
43 |
44 | #Oval-3{
45 | animation: float2 8s ease 4s infinite;
46 | }
47 |
48 | #Oval-4{
49 | animation: float3 8s ease 5s infinite;
50 | }
51 |
52 | .pulsateInital(@inital){
53 | transform: scale(@inital) ;
54 | animation-timing-function: cubic-bezier(0,.96,.56,.97);
55 | }
56 |
57 | .pulsateDownstate(){
58 | transform: scale(@downstate);
59 | animation-timing-function:ease-in;
60 | }
61 |
62 | // 2. Animations =================
63 |
64 | @keyframes dash {
65 | 25% {stroke-dashoffset: 0; opacity: inherit;}
66 | 75% {opacity: 1;stroke-dashoffset: 0;}
67 | 99% {opacity: 0;stroke-dashoffset: 0;}
68 | 100%{stroke-dashoffset: @strokeDash;}
69 | }
70 |
71 | @keyframes pulsate {
72 | 0% {opacity:1}
73 | 25% {.pulsateInital(1);}
74 | 28% {.pulsateDownstate();}
75 | 37.5% {.pulsateInital(0.95);}
76 | 40.5% {.pulsateDownstate();}
77 | 50% {.pulsateInital(0.9);}
78 | 53% {.pulsateDownstate();}
79 | 62.5% {.pulsateInital(0.85);}
80 | 65.5% {.pulsateDownstate();}
81 | 75% {.pulsateInital(0.85);opacity:1;fill:none;}
82 | 100% {.pulsateDownstate();opacity:0;fill:white;}
83 | }
84 |
85 | @keyframes float {
86 | 0% {opacity: 0}
87 | 0.1% {transform: scale(@inital);opacity: 0.6;}
88 | 40% {transform: scale(2);opacity: 0;}
89 | 100% {transform: scale(@inital);opacity: 0;}
90 | }
91 |
92 | @keyframes float2 {
93 | 0% {opacity: 0}
94 | 0.1% {transform: scale(@inital);opacity: 0.6;}
95 | 30% {transform: scale(2);opacity: 0;}
96 | 100% {transform: scale(@inital);opacity: 0;}
97 | }
98 |
99 | @keyframes float3 {
100 | 0% {opacity: 0}
101 | 0.1% {transform: scale(@inital);opacity: 0.6;}
102 | 30% {transform: scale(2);opacity: 0;}
103 | 100% {transform: scale(@inital);opacity: 0;}
104 | }
105 |
106 | // Presentation =================
107 |
108 | body {
109 | background: #19b5fe;
110 | }
111 |
112 | .spinner{
113 | position: absolute;
114 | z-index: 200;
115 | top: 50%;
116 | left: 50%;
117 | width: 100px;
118 | height: 100px;
119 | background: #19b5fe;
120 |
121 | &:after{
122 | content: "";
123 | position: absolute;
124 | z-index: 300;
125 | width: 300px;
126 | height: 200px;
127 | top: 0;
128 | left: 0;
129 | opacity: 0.3;
130 | background-size: 100%;
131 | }
132 | }
--------------------------------------------------------------------------------
/gulpfile.babel.js:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill'
2 | import fs from 'fs'
3 | import gulp from 'gulp'
4 | import merge from 'merge-stream'
5 | import gutil from 'gulp-util'
6 | import pug from 'gulp-pug'
7 | import rename from 'gulp-rename'
8 | import webpack from 'webpack'
9 | import WebpackDevServer from 'webpack-dev-server'
10 | import prodConfig from './webpack/prod.config'
11 | import devConfig from './webpack/dev.config'
12 |
13 | const port = 3000
14 |
15 | /*
16 | * common tasks
17 | */
18 | gulp.task('replace-webpack-code', () => {
19 | const replaceTasks = [ {
20 | from: './webpack/replace/JsonpMainTemplate.runtime.js',
21 | to: './node_modules/webpack/lib/JsonpMainTemplate.runtime.js'
22 | }, {
23 | from: './webpack/replace/log-apply-result.js',
24 | to: './node_modules/webpack/hot/log-apply-result.js'
25 | } ]
26 | replaceTasks.forEach(task => fs.writeFileSync(task.to, fs.readFileSync(task.from)))
27 | })
28 |
29 | /*
30 | * dev tasks
31 | */
32 |
33 | gulp.task('webpack-dev-server', () => {
34 | let myConfig = Object.create(devConfig)
35 | new WebpackDevServer(webpack(myConfig, (err, stats) => {
36 | if (err) throw new gutil.PluginError('webpack', err)
37 | gutil.log('Please load the unpacked extension with `./dev` folder.\n (see https://developer.chrome.com/extensions/getstarted#unpacked)')
38 | }), {
39 | publicPath: myConfig.output.publicPath,
40 | stats: {colors: true},
41 | noInfo: true,
42 | hot: true,
43 | historyApiFallback: true
44 | // https: true
45 | }).listen(port, 'localhost', (err) => {
46 | if (err) throw new gutil.PluginError('webpack-dev-server', err)
47 | gutil.log('[webpack-dev-server]', `listening at port ${port}`)
48 | })
49 | })
50 |
51 | gulp.task('views:dev', () => {
52 | return gulp.src('./chrome/views/*.pug')
53 | .pipe(pug({
54 | locals: {
55 | env: 'dev',
56 | devToolsExt: !!process.env.DEVTOOLS_EXT || true
57 | }
58 | }))
59 | .pipe(gulp.dest('./dev'))
60 | })
61 |
62 | gulp.task('copy:dev', () => {
63 | const manifest = gulp.src('./chrome/manifest.dev.json')
64 | .pipe(rename('manifest.json'))
65 | .pipe(gulp.dest('./dev'))
66 | const assets = gulp.src('./chrome/assets/**/*').pipe(gulp.dest('./dev'))
67 | return merge(manifest, assets)
68 | })
69 |
70 | /*
71 | * build tasks
72 | */
73 |
74 | gulp.task('webpack:build', (callback) => {
75 | let myConfig = Object.create(prodConfig)
76 | webpack(myConfig, (err, stats) => {
77 | if (err) {
78 | throw new gutil.PluginError('webpack:build', err)
79 | }
80 | gutil.log('[webpack:build]', stats.toString({ colors: true }))
81 | callback()
82 | })
83 | })
84 |
85 | gulp.task('views:build', () => {
86 | return gulp.src('./chrome/views/*.pug')
87 | .pipe(pug({
88 | locals: { env: 'prod' }
89 | }))
90 | .pipe(gulp.dest('./build'))
91 | })
92 |
93 | gulp.task('copy:build', () => {
94 | const manifest = gulp.src('./chrome/manifest.prod.json')
95 | .pipe(rename('manifest.json'))
96 | .pipe(gulp.dest('./build'))
97 | const assets = gulp.src('./chrome/assets/**/*').pipe(gulp.dest('./build'))
98 | return merge(manifest, assets)
99 | })
100 |
101 | gulp.task('default', [ 'replace-webpack-code', 'webpack-dev-server', 'views:dev', 'copy:dev' ])
102 | gulp.task('build', [ 'replace-webpack-code', 'webpack:build', 'views:build', 'copy:build' ])
103 |
--------------------------------------------------------------------------------
/app/popup/js/screens/menu/profile.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react'
2 | import Radium from 'radium'
3 |
4 | import SimpleStat from '../../components/view/simple-stat'
5 | import IconButton from '../../components/view/icon-button'
6 | import Header from '../../components/view/header'
7 | import Icon from '../../components/view/icon'
8 | import Details from '../../components/view/details'
9 |
10 | import 'normalize.css'
11 | import 'css-box-sizing-border-box/index.css'
12 | import '../../../styles/common.less'
13 | import '../../../styles/fonts.less'
14 | import '../../../styles/file-drop.less'
15 |
16 | @Radium
17 | export default class ProfileScreen extends Component {
18 | static propTypes = {
19 | peers: PropTypes.number,
20 | location: PropTypes.string,
21 | redirecting: PropTypes.bool,
22 | agentVersion: PropTypes.string,
23 | protocolVersion: PropTypes.string,
24 | host: PropTypes.string,
25 | port: PropTypes.number,
26 | isIpfsPage: PropTypes.bool,
27 | pageUrl: PropTypes.string,
28 | pinned: PropTypes.bool,
29 | onRedirectClick: PropTypes.func,
30 | onWebUIClick: PropTypes.func,
31 | onOptionsClick: PropTypes.func,
32 | onCopyToClipboard: PropTypes.func,
33 | onPinClick: PropTypes.func
34 | };
35 |
36 | static defaultProps = {
37 | peers: 0,
38 | location: '',
39 | onRedirectClick () {},
40 | onWebUIClick () {},
41 | onOptionsClick () {},
42 | onCopyToClipboard () {},
43 | onPinClick () {}
44 | };
45 |
46 | render () {
47 | const styles = {
48 | wrapper: {
49 | display: 'flex',
50 | width: '100%',
51 | height: '100%',
52 | backgroundImage: `url(${require('../../../img/stars.png')})`,
53 | backgroundSize: 'cover',
54 | backgroundPosition: '0 10%',
55 | color: '#FFFFFF',
56 | flexDirection: 'column',
57 | alignItems: 'center'
58 | },
59 | header: {
60 | height: this.props.location ? '40px' : '60px'
61 | },
62 | location: {
63 | display: 'flex',
64 | flex: '1',
65 | color: '#FFFFFF',
66 | width: '100%',
67 | minHeight: '30px',
68 | alignItems: 'center',
69 | justifyContent: 'center',
70 | flexDirection: 'column',
71 | padding: '10px 0'
72 | },
73 | stats: {
74 | padding: '20px',
75 | display: 'flex',
76 | flex: '1',
77 | backgroundColor: '#FFFFFF',
78 | color: '#000000',
79 | width: '100%',
80 | height: '30%',
81 | justifyContent: 'space-around'
82 | },
83 | actions: {
84 | display: 'flex',
85 | height: '70px',
86 | justifyContent: 'space-around',
87 | backgroundColor: '#19b5fe',
88 | width: '100%'
89 | },
90 | pageActions: {
91 | wrapper: {
92 | width: '100%',
93 | backgroundImage: `url(${require('../../../img/stars.png')})`,
94 | backgroundSize: 'cover',
95 | backgroundPosition: '0 90%'
96 | },
97 | header: {
98 | textAlign: 'center',
99 | fontSize: '16px',
100 | padding: '10px'
101 | },
102 | buttons: {
103 | display: 'flex',
104 | height: '70px',
105 | justifyContent: 'space-around',
106 | width: '100%'
107 | }
108 | }
109 | }
110 |
111 | const pageActions = this.props.isIpfsPage ? (
112 |
113 |
114 | Current address actions
115 |
116 |
117 |
122 |
127 |
132 |
133 |
134 | ) : null
135 |
136 | const location = this.props.location ? (
137 |
138 |
139 |
140 | {this.props.location}
141 |
142 |
143 | ) : null
144 |
145 | return (
146 |
147 |
148 | {location}
149 |
150 |
156 |
161 |
162 |
163 |
168 |
173 |
178 |
179 | {pageActions}
180 |
181 | )
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/app/popup/js/screens/menu.js:
--------------------------------------------------------------------------------
1 | /* global chrome */
2 | import React, {Component} from 'react'
3 | import CSSTransitionGroup from 'react-addons-css-transition-group'
4 | import { url as isIPFSUrl, urlPattern } from 'is-ipfs'
5 |
6 | import StartScreen from './menu/start'
7 | import ProfileScreen from './menu/profile'
8 | import Loader from '../components/view/loader'
9 |
10 | import '../../styles/animations.less'
11 |
12 | const INITIALIZING = 'initializing'
13 | const RUNNING = 'running'
14 | const STOPPED = 'stopped'
15 |
16 | function copyToClipboard (str) {
17 | document.oncopy = (event) => {
18 | event.clipboardData.setData('Text', str)
19 | event.preventDefault()
20 | }
21 |
22 | document.execCommand('Copy')
23 | document.oncopy = undefined
24 | }
25 |
26 | function parseUrl (url) {
27 | const matches = url.match(urlPattern)
28 | const hash = matches[1] === 'ipfs' ? matches[4] : null
29 | const path = matches[3]
30 | const address = `/${matches[1]}/${path}`
31 | const publicUrl = `https://ipfs.io${address}`
32 |
33 | return {
34 | hash,
35 | path,
36 | address,
37 | publicUrl
38 | }
39 | }
40 |
41 | export default class Menu extends Component {
42 | state = {
43 | status: INITIALIZING,
44 | connected: false,
45 | version: null,
46 | stats: {}
47 | };
48 |
49 | constructor (props) {
50 | super(props)
51 |
52 | this.handleStorageChange = this.handleStorageChange.bind(this)
53 | this.handleRedirectClick = this.handleRedirectClick.bind(this)
54 | this.handleWebUIClick = this.handleWebUIClick.bind(this)
55 | this.handleOptionsClick = this.handleOptionsClick.bind(this)
56 | this.handleCopyToClipboard = this.handleCopyToClipboard.bind(this)
57 | this.handlePinClick = this.handlePinClick.bind(this)
58 | this.handleReportProblemClick = this.handleReportProblemClick.bind(this)
59 |
60 | chrome.storage.onChanged.addListener(this.handleStorageChange)
61 |
62 | chrome.storage.local.get(null, (result) => {
63 | this.setState({
64 | ...result,
65 | status: result.running ? RUNNING : STOPPED
66 | })
67 | })
68 |
69 | chrome.storage.sync.get(null, (result) => {
70 | this.setState(result)
71 | })
72 |
73 | chrome.tabs.query({ currentWindow: true, active: true }, (tabs) => {
74 | if (!isIPFSUrl(tabs[0].url)) {
75 | return
76 | }
77 |
78 | const pageUrl = tabs[0].url
79 |
80 | const { hash } = parseUrl(pageUrl)
81 |
82 | this.setState({
83 | isIpfsPage: true,
84 | pageUrl,
85 | pageHash: hash
86 | })
87 |
88 | chrome.runtime.sendMessage({ method: 'pin.list' }, (response) => {
89 | const err = response[0]
90 | const res = response[1]
91 | if (err) {
92 | console.error('error on pin.list', err)
93 | return
94 | }
95 |
96 | this.setState({
97 | pinned: !!res.Keys[hash]
98 | })
99 | })
100 | })
101 | }
102 |
103 | handleStorageChange (changes, namespace) {
104 | const nextState = {}
105 | Object.keys(changes).forEach(key => {
106 | const storageChange = changes[key]
107 |
108 | nextState[key] = storageChange.newValue
109 |
110 | if (key === 'running') {
111 | nextState.status = storageChange.newValue ? RUNNING : STOPPED
112 | }
113 | })
114 |
115 | console.log('nextState', nextState)
116 |
117 | this.setState(nextState)
118 | }
119 |
120 | handleRedirectClick () {
121 | const redirecting = !this.state.redirecting
122 |
123 | chrome.storage.sync.set({
124 | redirecting
125 | })
126 | }
127 |
128 | handleWebUIClick () {
129 | chrome.tabs.create({ url: `http://${this.state.host}:${this.state.apiPort}/webui` })
130 | }
131 |
132 | handleOptionsClick () {
133 | chrome.runtime.openOptionsPage()
134 | }
135 |
136 | handleCopyToClipboard (type) {
137 | const { address, publicUrl } = parseUrl(this.state.pageUrl)
138 |
139 | switch (type) {
140 | case 'public-address':
141 | return copyToClipboard(publicUrl)
142 | case 'address':
143 | return copyToClipboard(address)
144 | }
145 | }
146 |
147 | handlePinClick () {
148 | const method = this.state.pinned ? 'pin.remove' : 'pin.add'
149 |
150 | const args = [this.state.pageHash]
151 |
152 | chrome.runtime.sendMessage({ method, args }, (response) => {
153 | const err = response[0]
154 | if (err) {
155 | console.error('error on', method, err)
156 | return
157 | }
158 |
159 | this.setState({
160 | pinned: !this.state.pinned
161 | })
162 | })
163 | }
164 |
165 | handleReportProblemClick () {
166 | chrome.tabs.create({url: 'https://github.com/xicombd/ipfs-chrome-station/issues'})
167 | }
168 |
169 | getScreen () {
170 | switch (this.state.status) {
171 | case RUNNING:
172 | return (
173 |
191 | )
192 | case INITIALIZING:
193 | return
194 | default:
195 | return
196 | }
197 | }
198 |
199 | render () {
200 | return (
201 |
206 | {this.getScreen()}
207 |
208 | )
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/app/popup/styles/fonts.less:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 |
3 | @font-face {
4 | font-family: 'Maven Pro';
5 | src: url('../fonts/maven_pro_bold-webfont.ttf') format('truetype');
6 | font-weight: bold;
7 | font-style: normal;
8 | }
9 |
10 | @font-face {
11 | font-family: 'Maven Pro';
12 | src: url('../fonts/maven_pro_medium-webfont.ttf') format('truetype');
13 | font-weight: 500;
14 | font-style: normal;
15 |
16 | }
17 |
18 | @font-face {
19 | font-family: 'Maven Pro';
20 | src: url('../fonts/maven_pro_regular-webfont.ttf') format('truetype');
21 | font-weight: normal;
22 | font-style: normal;
23 | }
24 |
25 | @font-face {
26 | font-family: "dripicons";
27 | src: url("../fonts/dripicons.ttf") format("truetype");
28 | font-weight: normal;
29 | font-style: normal;
30 |
31 | }
32 |
33 | [data-icon]:before {
34 | font-family: "dripicons";
35 | content: attr(data-icon);
36 | font-style: normal;
37 | font-weight: normal;
38 | font-variant: normal;
39 | text-transform: none !important;
40 | speak: none;
41 | display: inline-block;
42 | text-decoration: none;
43 | width: 1em;
44 | line-height: 1em;
45 | -webkit-font-smoothing: antialiased;
46 | }
47 |
48 | [class^="icon-"]:before,
49 | [class*=" icon-"]:before {
50 | font-family: "dripicons";
51 | font-style: normal;
52 | font-weight: normal;
53 | font-variant: normal;
54 | text-transform: none !important;
55 | speak: none;
56 | display: inline-block;
57 | text-decoration: none;
58 | width: 1em;
59 | line-height: 1em;
60 | -webkit-font-smoothing: antialiased;
61 | }
62 |
63 |
64 | .icon-align-center:before {
65 | content: "\e000";
66 | }
67 |
68 | .icon-align-justify:before {
69 | content: "\e001";
70 | }
71 |
72 | .icon-align-left:before {
73 | content: "\e002";
74 | }
75 |
76 | .icon-align-right:before {
77 | content: "\e003";
78 | }
79 |
80 | .icon-arrow-down:before {
81 | content: "\e004";
82 | }
83 |
84 | .icon-arrow-left:before {
85 | content: "\e005";
86 | }
87 |
88 | .icon-arrow-thin-down:before {
89 | content: "\e006";
90 | }
91 |
92 | .icon-arrow-right:before {
93 | content: "\e007";
94 | }
95 |
96 | .icon-arrow-thin-left:before {
97 | content: "\e008";
98 | }
99 |
100 | .icon-arrow-thin-up:before {
101 | content: "\e009";
102 | }
103 |
104 | .icon-arrow-up:before {
105 | content: "\e010";
106 | }
107 |
108 | .icon-attachment:before {
109 | content: "\e011";
110 | }
111 |
112 | .icon-arrow-thin-right:before {
113 | content: "\e012";
114 | }
115 |
116 | .icon-code:before {
117 | content: "\e013";
118 | }
119 |
120 | .icon-cloud:before {
121 | content: "\e014";
122 | }
123 |
124 | .icon-chevron-right:before {
125 | content: "\e015";
126 | }
127 |
128 | .icon-chevron-up:before {
129 | content: "\e016";
130 | }
131 |
132 | .icon-chevron-down:before {
133 | content: "\e017";
134 | }
135 |
136 | .icon-chevron-left:before {
137 | content: "\e018";
138 | }
139 |
140 | .icon-camera:before {
141 | content: "\e019";
142 | }
143 |
144 | .icon-checkmark:before {
145 | content: "\e020";
146 | }
147 |
148 | .icon-calendar:before {
149 | content: "\e021";
150 | }
151 |
152 | .icon-clockwise:before {
153 | content: "\e022";
154 | }
155 |
156 | .icon-conversation:before {
157 | content: "\e023";
158 | }
159 |
160 | .icon-direction:before {
161 | content: "\e024";
162 | }
163 |
164 | .icon-cross:before {
165 | content: "\e025";
166 | }
167 |
168 | .icon-graph-line:before {
169 | content: "\e026";
170 | }
171 |
172 | .icon-gear:before {
173 | content: "\e027";
174 | }
175 |
176 | .icon-graph-bar:before {
177 | content: "\e028";
178 | }
179 |
180 | .icon-export:before {
181 | content: "\e029";
182 | }
183 |
184 | .icon-feed:before {
185 | content: "\e030";
186 | }
187 |
188 | .icon-folder:before {
189 | content: "\e031";
190 | }
191 |
192 | .icon-forward:before {
193 | content: "\e032";
194 | }
195 |
196 | .icon-folder-open:before {
197 | content: "\e033";
198 | }
199 |
200 | .icon-download:before {
201 | content: "\e034";
202 | }
203 |
204 | .icon-document-new:before {
205 | content: "\e035";
206 | }
207 |
208 | .icon-document-edit:before {
209 | content: "\e036";
210 | }
211 |
212 | .icon-document:before {
213 | content: "\e037";
214 | }
215 |
216 | .icon-gaming:before {
217 | content: "\e038";
218 | }
219 |
220 | .icon-graph-pie:before {
221 | content: "\e039";
222 | }
223 |
224 | .icon-heart:before {
225 | content: "\e040";
226 | }
227 |
228 | .icon-headset:before {
229 | content: "\e041";
230 | }
231 |
232 | .icon-help:before {
233 | content: "\e042";
234 | }
235 |
236 | .icon-information:before {
237 | content: "\e043";
238 | }
239 |
240 | .icon-loading:before {
241 | content: "\e044";
242 | }
243 |
244 | .icon-lock:before {
245 | content: "\e045";
246 | }
247 |
248 | .icon-location:before {
249 | content: "\e046";
250 | }
251 |
252 | .icon-lock-open:before {
253 | content: "\e047";
254 | }
255 |
256 | .icon-mail:before {
257 | content: "\e048";
258 | }
259 |
260 | .icon-map:before {
261 | content: "\e049";
262 | }
263 |
264 | .icon-media-loop:before {
265 | content: "\e050";
266 | }
267 |
268 | .icon-mobile-portrait:before {
269 | content: "\e051";
270 | }
271 |
272 | .icon-mobile-landscape:before {
273 | content: "\e052";
274 | }
275 |
276 | .icon-microphone:before {
277 | content: "\e053";
278 | }
279 |
280 | .icon-minus:before {
281 | content: "\e054";
282 | }
283 |
284 | .icon-message:before {
285 | content: "\e055";
286 | }
287 |
288 | .icon-menu:before {
289 | content: "\e056";
290 | }
291 |
292 | .icon-media-stop:before {
293 | content: "\e057";
294 | }
295 |
296 | .icon-media-shuffle:before {
297 | content: "\e058";
298 | }
299 |
300 | .icon-media-previous:before {
301 | content: "\e059";
302 | }
303 |
304 | .icon-media-play:before {
305 | content: "\e060";
306 | }
307 |
308 | .icon-media-next:before {
309 | content: "\e061";
310 | }
311 |
312 | .icon-media-pause:before {
313 | content: "\e062";
314 | }
315 |
316 | .icon-monitor:before {
317 | content: "\e063";
318 | }
319 |
320 | .icon-move:before {
321 | content: "\e064";
322 | }
323 |
324 | .icon-plus:before {
325 | content: "\e065";
326 | }
327 |
328 | .icon-phone:before {
329 | content: "\e066";
330 | }
331 |
332 | .icon-preview:before {
333 | content: "\e067";
334 | }
335 |
336 | .icon-print:before {
337 | content: "\e068";
338 | }
339 |
340 | .icon-media-record:before {
341 | content: "\e069";
342 | }
343 |
344 | .icon-music:before {
345 | content: "\e070";
346 | }
347 |
348 | .icon-home:before {
349 | content: "\e071";
350 | }
351 |
352 | .icon-question:before {
353 | content: "\e072";
354 | }
355 |
356 | .icon-reply:before {
357 | content: "\e073";
358 | }
359 |
360 | .icon-reply-all:before {
361 | content: "\e074";
362 | }
363 |
364 | .icon-return:before {
365 | content: "\e075";
366 | }
367 |
368 | .icon-retweet:before {
369 | content: "\e076";
370 | }
371 |
372 | .icon-search:before {
373 | content: "\e077";
374 | }
375 |
376 | .icon-view-thumb:before {
377 | content: "\e078";
378 | }
379 |
380 | .icon-view-list-large:before {
381 | content: "\e079";
382 | }
383 |
384 | .icon-view-list:before {
385 | content: "\e080";
386 | }
387 |
388 | .icon-upload:before {
389 | content: "\e081";
390 | }
391 |
392 | .icon-user-group:before {
393 | content: "\e082";
394 | }
395 |
396 | .icon-trash:before {
397 | content: "\e083";
398 | }
399 |
400 | .icon-user:before {
401 | content: "\e084";
402 | }
403 |
404 | .icon-thumbs-up:before {
405 | content: "\e085";
406 | }
407 |
408 | .icon-thumbs-down:before {
409 | content: "\e086";
410 | }
411 |
412 | .icon-tablet-portrait:before {
413 | content: "\e087";
414 | }
415 |
416 | .icon-tablet-landscape:before {
417 | content: "\e088";
418 | }
419 |
420 | .icon-tag:before {
421 | content: "\e089";
422 | }
423 |
424 | .icon-star:before {
425 | content: "\e090";
426 | }
427 |
428 | .icon-volume-full:before {
429 | content: "\e091";
430 | }
431 |
432 | .icon-volume-off:before {
433 | content: "\e092";
434 | }
435 |
436 | .icon-warning:before {
437 | content: "\e093";
438 | }
439 |
440 | .icon-window:before {
441 | content: "\e094";
442 | }
443 |
--------------------------------------------------------------------------------