├── .babelrc
├── .editorconfig
├── .github
└── ISSUE_TEMPLATE.md
├── .gitignore
├── .npmignore
├── README.md
├── jest.config.json
├── license.md
├── package-lock.json
├── package.json
├── screenshot.png
├── src
├── index.js
└── lib
│ ├── core
│ └── hyperline.js
│ ├── plugins
│ ├── battery.js
│ ├── battery
│ │ ├── battery-icon.js
│ │ ├── charging.js
│ │ ├── critical.js
│ │ └── draining.js
│ ├── cpu.js
│ ├── docker.js
│ ├── git-status.js
│ ├── hostname.js
│ ├── index.js
│ ├── ip.js
│ ├── memory.js
│ ├── network.js
│ ├── spotify.js
│ ├── time.js
│ └── uptime.js
│ └── utils
│ ├── colors.js
│ ├── config.js
│ ├── svg-icon.js
│ └── time.js
├── tests
├── __snapshots__
│ └── time.spec.js.snap
├── spotify.spec.js
└── time.spec.js
├── webpack.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react"],
3 | "plugins": [
4 | [
5 | "styled-jsx/babel",
6 | {
7 | "vendorPrefixes": false
8 | }
9 | ],
10 | "transform-object-rest-spread"
11 | ],
12 | "env": {
13 | "test": {
14 | "presets": ["es2015"]
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 |
11 | # Matches multiple files with brace expansion notation
12 | # Set default charset
13 | [*.{js}]
14 | charset = utf-8
15 |
16 | # 4 space indentation
17 | [*.js]
18 | indent_style = space
19 | indent_size = 2
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | dist
4 | .vscode
5 | .DS_STORE
6 | .idea
7 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # All
2 | *
3 |
4 | # But not
5 | !dist/**
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | HyperLine
2 | =========
3 |
4 | **HyperLine is a status line plugin for [Hyper.app](https://hyper.is/)**. It shows you useful system information such as free memory, uptime and CPU usage.
5 |
6 | 
7 |
8 | ## Install
9 |
10 | * **NOTE:** HyperLine is not currently supported when using Microsoft Windows. See [this issue](https://github.com/Hyperline/hyperline/issues/57) for additional information.
11 |
12 | To install, edit `~/.hyper.js` and add `"hyperline"` to `plugins`:
13 |
14 | ```
15 | plugins: [
16 | "hyperline",
17 | ],
18 | ```
19 |
20 | ## Styling the line
21 |
22 | We implemented the same mechanism for styling/creating css classes that Hyper uses.
23 | This will allow you to create custom HyperLine themes the same way you would create a Hyper css theme.
24 |
25 | ## Configuring plugins
26 | Add the names of plugins in the order in which you would like them to be displayed to your `~/.hyper.js`:
27 |
28 | ```
29 | config: {
30 | hyperline: {
31 | plugins: [
32 | "ip",
33 | "cpu",
34 | "spotify"
35 | ]
36 | },
37 | }
38 | ```
39 | You can see a list of all available plugins in [`src/lib/plugins/index.js`](https://github.com/Hyperline/hyperline/blob/master/src/lib/plugins/index.js)
40 |
41 | ## Contributing
42 |
43 | Feel free to contribute to HyperLine by [requesting a feature](https://github.com/hyperline/hyperline/issues/new), [submitting a bug](https://github.com/hyperline/hyperline/issues/new) or contributing code.
44 |
45 | To set up the project for development:
46 |
47 | 1. Clone this repository into `~/.hyper_plugins/local/`
48 | 2. Run `npm install` within the project directory
49 | 3. Run `npm run build` to build the plugin **OR** `npm run dev` to build the plugin and watch for file changes.
50 | 4. Add the name of the directory to `localPlugins` in `~/.hyper.js`.
51 | 5. Reload terminal window
52 |
53 | ## Authors
54 |
55 | - Nick Tikhonov [@nicktikhonov](https://github.com/nicktikhonov)
56 | - Tim Neutkens [@timneutkens](https://github.com/timneutkens)
57 | - Stefan Ivic [@stefanivic](https://github.com/stefanivic)
58 | - Henrik Dahlheim [@henrikdahl](https://github.com/henrikdahl)
59 |
60 | ## Contributors
61 |
62 | This project exists thanks to all the people who contribute.
63 |
64 |
--------------------------------------------------------------------------------
/jest.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "testRegex": "(/tests/.*|\\.(test|spec))\\.js$",
3 | "moduleFileExtensions": [ "js" ],
4 | "moduleDirectories": [ "node_modules" ]
5 | }
6 |
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Nick Tikhonov, Tim Neutkens
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hyperline",
3 | "version": "1.2.0",
4 | "description": "Handy status line for Hyper.app",
5 | "keywords": [
6 | "hyper.app",
7 | "hyper",
8 | "hyperterm"
9 | ],
10 | "author": "Nick Tikhonov",
11 | "contributors": [
12 | {
13 | "name": "Tim Neutkens"
14 | },
15 | {
16 | "name": "Stefan Ivic"
17 | },
18 | {
19 | "name": "Henrik Dahlheim"
20 | }
21 | ],
22 | "license": "MIT",
23 | "repository": "hyperline/hyperline",
24 | "main": "dist/hyperline.js",
25 | "files": [
26 | "dist/hyperline.js"
27 | ],
28 | "dependencies": {
29 | "color": "^0.11.3",
30 | "git-state": "^4.0.0",
31 | "json-loader": "^0.5.4",
32 | "left-pad": "^1.1.3",
33 | "moment": "^2.18.1",
34 | "opencollective": "^1.0.3",
35 | "prop-types": "^15.5.10",
36 | "public-ip": "^2.0.1",
37 | "spotify-node-applescript": "^1.1.0",
38 | "styled-jsx": "2.2.6",
39 | "systeminformation": "^3.4.1"
40 | },
41 | "devDependencies": {
42 | "babel-core": "^6.11.4",
43 | "babel-jest": "^20.0.3",
44 | "babel-loader": "^6.2.4",
45 | "babel-plugin-transform-object-rest-spread": "6.26.0",
46 | "babel-preset-es2015": "^6.9.0",
47 | "babel-preset-react": "^6.11.1",
48 | "cross-env": "^3.1.4",
49 | "eslint-config-prettier": "^2.3.0",
50 | "eslint-config-xo-react": "^0.13.0",
51 | "eslint-loader": "^1.5.0",
52 | "eslint-plugin-react": "^7.1.0",
53 | "jest": "^20.0.4",
54 | "lint-staged": "^4.0.0",
55 | "prettier": "^1.5.2",
56 | "prop-types": "^15.5.10",
57 | "webpack": "2.2.0-rc.1",
58 | "webpack-node-externals": "^1.3.3",
59 | "xo": "^0.18.2",
60 | "xo-loader": "^0.8.0"
61 | },
62 | "scripts": {
63 | "build": "cross-env NODE_ENV=production webpack",
64 | "dev": "webpack --watch",
65 | "tdev": "jest --watch",
66 | "test": "jest",
67 | "precommit": "lint-staged",
68 | "postinstall": "opencollective postinstall"
69 | },
70 | "xo": {
71 | "ignores": [
72 | "examples/**/*",
73 | "node_modules/**/*"
74 | ],
75 | "extends": "prettier",
76 | "rules": {
77 | "import/no-extraneous-dependencies": 0,
78 | "import/no-unresolved": 0,
79 | "no-unused-vars": 0,
80 | "import/order": 1,
81 | "no-warning-comments": 0,
82 | "prefer-promise-reject-errors": 0,
83 | "import/named": 1,
84 | "space-in-parens": 0,
85 | "object-curly-spacing": 0,
86 | "computed-property-spacing": 0,
87 | "space-infix-ops": 0,
88 | "one-var": 0,
89 | "no-console": 0,
90 | "no-useless-constructor": 1,
91 | "no-useless-escape": 1
92 | }
93 | },
94 | "lint-staged": {
95 | "*.js": [
96 | "npm run lint",
97 | "prettier --single-quote --no-semi --write",
98 | "jest"
99 | ]
100 | },
101 | "collective": {
102 | "type": "opencollective",
103 | "url": "https://opencollective.com/hyperline",
104 | "logo": "https://opencollective.com/opencollective/logo.txt"
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hyperline/hyperline/b4e275e6b399abff1540400d719f1169a77e3b30/screenshot.png
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import HyperLine from './lib/core/hyperline'
4 | import { getColorList } from './lib/utils/colors'
5 | import hyperlinePlugins from './lib/plugins'
6 |
7 | export function reduceUI(state, { type, config }) {
8 | switch (type) {
9 | case 'CONFIG_LOAD':
10 | case 'CONFIG_RELOAD': {
11 | return state.set('hyperline', config.hyperline)
12 | }
13 | default:
14 | break
15 | }
16 |
17 | return state
18 | }
19 |
20 | export function mapHyperState({ ui: { colors, fontFamily, hyperline } }, map) {
21 | let userPlugins = []
22 | if (hyperline !== undefined) {
23 | if (hyperline.plugins !== undefined) {
24 | userPlugins = hyperline.plugins
25 | }
26 | }
27 |
28 | return Object.assign({}, map, {
29 | colors: getColorList(colors),
30 | fontFamily,
31 | userPlugins
32 | })
33 | }
34 |
35 | function pluginsByName(plugins) {
36 | const dict = {}
37 | plugins.forEach((plugin) => {
38 | dict[plugin.displayName()] = plugin
39 | })
40 |
41 | return dict
42 | }
43 |
44 | function filterPluginsByConfig(plugins) {
45 | const config = window.config.getConfig().hyperline
46 | if (!config) return plugins
47 |
48 | const userPluginNames = config.plugins
49 | if (!userPluginNames) {
50 | return plugins
51 | }
52 |
53 | plugins = pluginsByName(plugins)
54 | const filtered = []
55 |
56 | userPluginNames.forEach((name) => {
57 | if (plugins.hasOwnProperty(name)) {
58 | filtered.push(plugins[name])
59 | }
60 | })
61 |
62 | return filtered
63 | }
64 |
65 | export function decorateHyperLine(HyperLine) {
66 | return class extends Component {
67 | static displayName() {
68 | return 'HyperLine'
69 | }
70 |
71 | static propTypes() {
72 | return {
73 | plugins: PropTypes.array.isRequired
74 | }
75 | }
76 |
77 | static get defaultProps() {
78 | return {
79 | plugins: []
80 | }
81 | }
82 |
83 | render() {
84 | const plugins = [...this.props.plugins, ...hyperlinePlugins]
85 |
86 | return
87 | }
88 | }
89 | }
90 |
91 | export function decorateHyper(Hyper) {
92 | return class extends Component {
93 | static displayName() {
94 | return 'Hyper'
95 | }
96 |
97 | static propTypes() {
98 | return {
99 | colors: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
100 | fontFamily: PropTypes.string,
101 | customChildren: PropTypes.element.isRequired
102 | }
103 | }
104 |
105 | render() {
106 | const customChildren = (
107 |
108 | {this.props.customChildren}
109 |
110 |
111 | )
112 |
113 | return
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/lib/core/hyperline.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Component from 'hyper/component'
4 | import decorate from 'hyper/decorate'
5 |
6 | class HyperLine extends Component {
7 | static propTypes() {
8 | return {
9 | plugins: PropTypes.array.isRequired
10 | }
11 | }
12 |
13 | render() {
14 | const { plugins, ...props } = this.props
15 |
16 | return (
17 |
18 | {plugins.map((Component, index) => (
19 |
20 |
21 |
22 | ))}
23 |
24 |
47 |
48 | )
49 | }
50 | }
51 |
52 | export default decorate(HyperLine, 'HyperLine')
53 |
--------------------------------------------------------------------------------
/src/lib/plugins/battery.js:
--------------------------------------------------------------------------------
1 | /* eslint no-undef: 0 */
2 | // Note: This is to stop XO from complaining about {navigator}
3 |
4 | import React from 'react'
5 | import Component from 'hyper/component'
6 | import leftPad from 'left-pad'
7 | import BatteryIcon from './battery/battery-icon'
8 |
9 | export default class Battery extends Component {
10 | static displayName() {
11 | return 'battery'
12 | }
13 |
14 | constructor(props) {
15 | super(props)
16 |
17 | this.state = {
18 | charging: false,
19 | percentage: '--'
20 | }
21 |
22 | this.batteryEvents = [ 'chargingchange', 'chargingtimechange', 'dischargingtimechange', 'levelchange' ]
23 | this.handleEvent = this.handleEvent.bind(this)
24 | }
25 |
26 | setBatteryStatus(battery) {
27 | this.setState({
28 | charging: battery.charging,
29 | percentage: Math.floor(battery.level * 100)
30 | })
31 | }
32 |
33 | handleEvent(event) {
34 | this.setBatteryStatus(event.target)
35 | }
36 |
37 | componentDidMount() {
38 | navigator.getBattery().then(battery => {
39 | this.setBatteryStatus(battery)
40 |
41 | this.batteryEvents.forEach(event => {
42 | battery.addEventListener(event, this.handleEvent, false)
43 | })
44 | })
45 | }
46 |
47 | componentWillUnmount() {
48 | navigator.getBattery().then(battery => {
49 | this.batteryEvents.forEach(event => {
50 | battery.removeEventListener(event, this.handleEvent)
51 | })
52 | })
53 | }
54 |
55 | render() {
56 | const { charging, percentage } = this.state
57 |
58 | return (
59 |
60 | {leftPad(percentage, 2, 0)}%
61 |
62 |
68 |
69 | )
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/lib/plugins/battery/battery-icon.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types';
3 | import Critical from './critical'
4 | import Charging from './charging'
5 | import Draining from './draining'
6 |
7 | function BatteryIcon({ charging, percentage }) {
8 | if (charging) {
9 | return
10 | }
11 |
12 | if (percentage <= 20) {
13 | return
14 | }
15 |
16 | return
17 | }
18 |
19 | BatteryIcon.propTypes = {
20 | charging: PropTypes.bool,
21 | percentage: PropTypes.number
22 | }
23 |
24 | export default BatteryIcon
25 |
--------------------------------------------------------------------------------
/src/lib/plugins/battery/charging.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Component from 'hyper/component'
3 | import SvgIcon from '../../utils/svg-icon'
4 |
5 | export default class Charging extends Component {
6 | render() {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/lib/plugins/battery/critical.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Component from 'hyper/component'
3 | import SvgIcon from '../../utils/svg-icon'
4 |
5 | export default class Critical extends Component {
6 | render() {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/lib/plugins/battery/draining.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Component from 'hyper/component'
4 | import SvgIcon from '../../utils/svg-icon'
5 |
6 | export default class Draining extends Component {
7 | static propTypes() {
8 | return {
9 | percentage: PropTypes.number
10 | }
11 | }
12 |
13 | calculateChargePoint(percent) {
14 | const base = 3.5,
15 | val = Math.round((100 - percent) / 4.5),
16 | point = base + (val / 2)
17 |
18 | return val > 0 ? `M5,3 L11,3 L11,${point} L5,${point} L5,3 Z` : ''
19 | }
20 |
21 | render() {
22 | const chargePoint = this.calculateChargePoint(this.props.percentage)
23 | return (
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
36 |
37 | )
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/lib/plugins/cpu.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Component from 'hyper/component'
3 | import { currentLoad as cpuLoad } from 'systeminformation'
4 | import leftPad from 'left-pad'
5 | import SvgIcon from '../utils/svg-icon'
6 |
7 | class PluginIcon extends Component {
8 | render() {
9 | return (
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
43 |
44 | )
45 | }
46 | }
47 |
48 | export default class Cpu extends Component {
49 | static displayName() {
50 | return 'cpu'
51 | }
52 |
53 | constructor(props) {
54 | super(props)
55 |
56 | this.state = {
57 | cpuLoad: 0
58 | }
59 | }
60 |
61 | getCpuLoad() {
62 | cpuLoad().then(({ currentload }) =>
63 | this.setState({
64 | cpuLoad: leftPad(currentload.toFixed(2), 2, 0)
65 | })
66 | )
67 | }
68 |
69 | componentDidMount() {
70 | this.getCpuLoad()
71 | this.interval = setInterval(() => this.getCpuLoad(), 2500)
72 | }
73 |
74 | componentWillUnmount() {
75 | clearInterval(this.interval)
76 | }
77 |
78 | render() {
79 | return (
80 |
81 |
{this.state.cpuLoad}
82 |
83 |
89 |
90 | )
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/lib/plugins/docker.js:
--------------------------------------------------------------------------------
1 | import { exec as ex } from 'child_process'
2 | import React from 'react'
3 | import Component from 'hyper/component'
4 | import SvgIcon from '../utils/svg-icon'
5 |
6 | class PluginIcon extends Component {
7 | render() {
8 | return (
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
28 |
29 | )
30 | }
31 | }
32 |
33 | export default class Docker extends Component {
34 | static displayName() {
35 | return 'docker'
36 | }
37 |
38 | constructor(props) {
39 | super(props)
40 |
41 | this.state = { version: 'Not running' }
42 | this.setVersion = this.setVersion.bind(this)
43 | }
44 |
45 | setVersion() {
46 | exec('/usr/local/bin/docker version -f {{.Server.Version}}')
47 | .then(version => {
48 | this.setState({ version })
49 | })
50 | .catch(() => {
51 | this.setState({ version: 'Not running' })
52 | })
53 | }
54 |
55 | componentDidMount() {
56 | this.setVersion()
57 | this.interval = setInterval(() => this.setVersion(), 15000)
58 | }
59 |
60 | componentWillUnmount() {
61 | clearInterval(this.interval)
62 | }
63 |
64 | render() {
65 | return (
66 |
67 |
{this.state.version}
68 |
69 |
76 |
77 | )
78 | }
79 | }
80 |
81 | function exec(command, options) {
82 | return new Promise((resolve, reject) => {
83 | ex(command, options, (err, stdout, stderr) => {
84 | if (err) {
85 | reject(`${err}\n${stderr}`)
86 | } else {
87 | resolve(stdout)
88 | }
89 | })
90 | })
91 | }
92 |
--------------------------------------------------------------------------------
/src/lib/plugins/git-status.js:
--------------------------------------------------------------------------------
1 | import exec from 'child_process'
2 | import shell from 'electron'
3 | import React from 'react'
4 | import Component from 'hyper/component'
5 |
6 |
7 | /*
8 | NOTICE
9 | ============
10 | This code is essentially a port from Henrik Dahlheim's "hyper-statusline"
11 | https://github.com/henrikdahl/hyper-statusline
12 |
13 | I'm simply the guy that ported it over to work in Hyperline
14 |
15 |
16 | Corbin (basedjux) Matschull
17 | */
18 |
19 | const dirtyColor = '#FFFBB3'
20 | const pushColor = '#B7E8FF'
21 |
22 | class PluginIcon extends Component {
23 | }
24 |
25 |
26 | let curBranch
27 | let curRemote
28 | let repoDirty
29 | let pushArrow
30 | let pullArrow
31 |
32 | export default class GitStatus extends Component {
33 | static displayName() {
34 | return "git-status"
35 | }
36 |
37 | constructor(props) {
38 | super(props)
39 |
40 | this.state = {
41 | branch: curBranch,
42 | remote: curRemote,
43 | dirty: repoDirty,
44 | push: pushArrow,
45 | pull: pullArrow
46 | }
47 |
48 | this.handleClick = this.handleClick.bind(this)
49 | }
50 |
51 | handleClick(e) {
52 | shell.openExternal(this.state.remote)
53 | }
54 |
55 | checkDirty(actionCwd) {
56 | exec(`git status --porcelain --ignore-submodules -unormal`, { cwd: actionCwd }, (err, branch) => {
57 | repoDirty = true
58 | })
59 | }
60 |
61 | setRemote(actionCwd) {
62 | exec(`git config --get remote.origin.url`, { cwd: actionCwd }, (err, remote) => {
63 | curRemote = remote.trim().replace(/^git@(.*?):/, 'https://$1/').replace(/[A-z0-9\-]+@/, '').replace(/\.git$/, '')
64 | })
65 | }
66 |
67 | checkArrows(actionCwd) {
68 | exec(`git rev-list --left-right --count HEAD...@'{u}' 2>/dev/null`, { cwd: actionCwd }, (err, arrows) => {
69 | arrows = arrows.split('\t');
70 | pushArrow = arrows[0] > 0 ? arrows[0] : '';
71 | pullArrow = arrows[1] > 0 ? arrows[1] : '';
72 | })
73 | }
74 |
75 | setBranch(actionCwd) {
76 | exec(`git symbolic-ref --short HEAD || git rev-parse --short HEAD`, { cwd: actionCwd }, (err,branch) => {
77 | curBranch = branch
78 |
79 | if (branch === '') {
80 | this.setRemote(actionCwd)
81 | this.checkDirty(actionCwd)
82 | this.checkArrows(actionCwd)
83 | }
84 | })
85 | }
86 |
87 | styles() {
88 | return {
89 | 'item_branch': {
90 | 'padding-left': '30px'
91 | },
92 | 'item_branch:before': {
93 | 'left': '14.5px',
94 | '-webkit-mask-image': 'url(\'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5IiBoZWlnaHQ9IjEyIiB2aWV3Qm94PSIwIDAgOSAxMiI+PHBhdGggZmlsbD0iIzAwMDAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNOSwzLjQyODU3NzQ2IEM5LDIuNDc3MTQ4ODggOC4xOTksMS43MTQyOTE3NCA3LjIsMS43MTQyOTE3NCBDNi4zODY5NDE5NCwxLjcxMjI0NTc4IDUuNjc0MTI3NDksMi4yMzEzMDI2NCA1LjQ2MzA1NjAyLDIuOTc5MDk4NzEgQzUuMjUxOTg0NTQsMy43MjY4OTQ3OCA1LjU5NTQ1MzE3LDQuNTE2Mzc3NDEgNi4zLDQuOTAyODYzMTcgTDYuMyw1LjE2MDAwNjAzIEM2LjI4Miw1LjYwNTcyMDMxIDYuMDkzLDYuMDAwMDA2MDMgNS43MzMsNi4zNDI4NjMxNyBDNS4zNzMsNi42ODU3MjAzMSA0Ljk1OSw2Ljg2NTcyMDMxIDQuNDkxLDYuODgyODYzMTcgQzMuNzQ0LDYuOTAwMDA2MDMgMy4xNTksNy4wMjAwMDYwMyAyLjY5MSw3LjI2ODU3NzQ2IEwyLjY5MSwzLjE4ODU3NzQ2IEMzLjM5NTU0NjgzLDIuODAyMDkxNyAzLjczOTAxNTQ2LDIuMDEyNjA5MDcgMy41Mjc5NDM5OCwxLjI2NDgxMjk5IEMzLjMxNjg3MjUxLDAuNTE3MDE2OTIzIDIuNjA0MDU4MDYsLTAuMDAyMDM5OTM0MTUgMS43OTEsNi4wMjY4NzM4NWUtMDYgQzAuNzkyLDYuMDI2ODczODVlLTA2IDkuOTkyMDA3MjJlLTE3LDAuNzYyODYzMTcgOS45OTIwMDcyMmUtMTcsMS43MTQyOTE3NCBDMC4wMDM4NTgyMzAyNiwyLjMyMzA1MzU2IDAuMzQ2NDE5ODM1LDIuODg0MjAyMDkgMC45LDMuMTg4NTc3NDYgTDAuOSw4LjgxMTQzNDYgQzAuMzY5LDkuMTExNDM0NiAwLDkuNjYwMDA2MDMgMCwxMC4yODU3MjAzIEMwLDExLjIzNzE0ODkgMC44MDEsMTIuMDAwMDA2IDEuOCwxMi4wMDAwMDYgQzIuNzk5LDEyLjAwMDAwNiAzLjYsMTEuMjM3MTQ4OSAzLjYsMTAuMjg1NzIwMyBDMy42LDkuODMxNDM0NiAzLjQyLDkuNDI4NTc3NDYgMy4xMjMsOS4xMjAwMDYwMyBDMy4yMDQsOS4wNjg1Nzc0NiAzLjU1NSw4Ljc2ODU3NzQ2IDMuNjU0LDguNzE3MTQ4ODggQzMuODc5LDguNjIyODYzMTcgNC4xNTgsOC41NzE0MzQ2IDQuNSw4LjU3MTQzNDYgQzUuNDQ1LDguNTI4NTc3NDYgNi4yNTUsOC4xODU3MjAzMSA2Ljk3NSw3LjUwMDAwNjAzIEM3LjY5NSw2LjgxNDI5MTc0IDguMDU1LDUuODAyODYzMTcgOC4xLDQuOTExNDM0NiBMOC4wODIsNC45MTE0MzQ2IEM4LjYzMSw0LjYwMjg2MzE3IDksNC4wNTQyOTE3NCA5LDMuNDI4NTc3NDYgTDksMy40Mjg1Nzc0NiBaIE0xLjgsMC42ODU3MjAzMTMgQzIuMzk0LDAuNjg1NzIwMzEzIDIuODgsMS4xNTcxNDg4OCAyLjg4LDEuNzE0MjkxNzQgQzIuODgsMi4yNzE0MzQ2IDIuMzg1LDIuNzQyODYzMTcgMS44LDIuNzQyODYzMTcgQzEuMjE1LDIuNzQyODYzMTcgMC43MiwyLjI3MTQzNDYgMC43MiwxLjcxNDI5MTc0IEMwLjcyLDEuMTU3MTQ4ODggMS4yMTUsMC42ODU3MjAzMTMgMS44LDAuNjg1NzIwMzEzIEwxLjgsMC42ODU3MjAzMTMgWiBNMS44LDExLjMyMjg2MzIgQzEuMjA2LDExLjMyMjg2MzIgMC43MiwxMC44NTE0MzQ2IDAuNzIsMTAuMjk0MjkxNyBDMC43Miw5LjczNzE0ODg4IDEuMjE1LDkuMjY1NzIwMzEgMS44LDkuMjY1NzIwMzEgQzIuMzg1LDkuMjY1NzIwMzEgMi44OCw5LjczNzE0ODg4IDIuODgsMTAuMjk0MjkxNyBDMi44OCwxMC44NTE0MzQ2IDIuMzg1LDExLjMyMjg2MzIgMS44LDExLjMyMjg2MzIgTDEuOCwxMS4zMjI4NjMyIFogTTcuMiw0LjQ2NTcyMDMxIEM2LjYwNiw0LjQ2NTcyMDMxIDYuMTIsMy45OTQyOTE3NCA2LjEyLDMuNDM3MTQ4ODggQzYuMTIsMi44ODAwMDYwMyA2LjYxNSwyLjQwODU3NzQ2IDcuMiwyLjQwODU3NzQ2IEM3Ljc4NSwyLjQwODU3NzQ2IDguMjgsMi44ODAwMDYwMyA4LjI4LDMuNDM3MTQ4ODggQzguMjgsMy45OTQyOTE3NCA3Ljc4NSw0LjQ2NTcyMDMxIDcuMiw0LjQ2NTcyMDMxIEw3LjIsNC40NjU3MjAzMSBaIi8+PC9zdmc+\')',
95 | '-webkit-mask-size': '9px 12px'
96 | },
97 | 'item_click:hover': {
98 | 'text-decoration': 'underline',
99 | 'cursor': 'pointer'
100 | },
101 | 'item_folder, .item_text': {
102 | 'line-height': '29px'
103 | },
104 | 'item_text': {
105 | 'height': '100%'
106 | },
107 | 'item_icon': {
108 | 'display': 'none',
109 | 'width': '12px',
110 | 'height' : '100%',
111 | 'margin-left': '9px',
112 | '-webkit-mask-size': '12px 12px',
113 | '-webkit-mask-repeat': 'no-repeat',
114 | '-webkit-mast-position': '0 center'
115 | },
116 | 'icon_active': {
117 | 'display': 'inline-block'
118 | },
119 | 'icon_dirty': {
120 | '-webkit-mask-image': 'url(\'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMiIgaGVpZ2h0PSIxMiIgdmlld0JveD0iMCAwIDEyIDEyIj48cGF0aCBmaWxsPSIjMDAwMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xMS4xNDI4NTcxLDAgTDAuODU3MTQyODU3LDAgQzAuMzg1NzE0Mjg2LDAgMCwwLjM4NTcxNDI4NiAwLDAuODU3MTQyODU3IEwwLDExLjE0Mjg1NzEgQzAsMTEuNjE0Mjg1NyAwLjM4NTcxNDI4NiwxMiAwLjg1NzE0Mjg1NywxMiBMMTEuMTQyODU3MSwxMiBDMTEuNjE0Mjg1NywxMiAxMiwxMS42MTQyODU3IDEyLDExLjE0Mjg1NzEgTDEyLDAuODU3MTQyODU3IEMxMiwwLjM4NTcxNDI4NiAxMS42MTQyODU3LDAgMTEuMTQyODU3MSwwIEwxMS4xNDI4NTcxLDAgWiBNMTEuMTQyODU3MSwxMS4xNDI4NTcxIEwwLjg1NzE0Mjg1NywxMS4xNDI4NTcxIEwwLjg1NzE0Mjg1NywwLjg1NzE0Mjg1NyBMMTEuMTQyODU3MSwwLjg1NzE0Mjg1NyBMMTEuMTQyODU3MSwxMS4xNDI4NTcxIEwxMS4xNDI4NTcxLDExLjE0Mjg1NzEgWiBNMy40Mjg1NzE0Myw2IEMzLjQyODU3MTQzLDQuNTc3MTQyODYgNC41NzcxNDI4NiwzLjQyODU3MTQzIDYsMy40Mjg1NzE0MyBDNy40MjI4NTcxNCwzLjQyODU3MTQzIDguNTcxNDI4NTcsNC41NzcxNDI4NiA4LjU3MTQyODU3LDYgQzguNTcxNDI4NTcsNy40MjI4NTcxNCA3LjQyMjg1NzE0LDguNTcxNDI4NTcgNiw4LjU3MTQyODU3IEM0LjU3NzE0Mjg2LDguNTcxNDI4NTcgMy40Mjg1NzE0Myw3LjQyMjg1NzE0IDMuNDI4NTcxNDMsNiBMMy40Mjg1NzE0Myw2IFoiLz48L3N2Zz4=\')',
121 | 'background-color': `${dirtyColor}`
122 | },
123 | 'icon_push': {
124 | '-webkit-mask-image': 'url(\'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMiIgaGVpZ2h0PSIxMiIgdmlld0JveD0iMCAwIDEyIDEyIj48cGF0aCBmaWxsPSIjMDAwMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik01LjE0Mjg1NzE0LDYuODU3MTQyODYgTDIuNTcxNDI4NTcsNi44NTcxNDI4NiBMMi41NzE0Mjg1Nyw1LjE0Mjg1NzE0IEw1LjE0Mjg1NzE0LDUuMTQyODU3MTQgTDUuMTQyODU3MTQsMi41NzE0Mjg1NyBMOS40Mjg1NzE0Myw2IEw1LjE0Mjg1NzE0LDkuNDI4NTcxNDMgTDUuMTQyODU3MTQsNi44NTcxNDI4NiBMNS4xNDI4NTcxNCw2Ljg1NzE0Mjg2IFogTTEyLDAuODU3MTQyODU3IEwxMiwxMS4xNDI4NTcxIEMxMiwxMS42MTQyODU3IDExLjYxNDI4NTcsMTIgMTEuMTQyODU3MSwxMiBMMC44NTcxNDI4NTcsMTIgQzAuMzg1NzE0Mjg2LDEyIDAsMTEuNjE0Mjg1NyAwLDExLjE0Mjg1NzEgTDAsMC44NTcxNDI4NTcgQzAsMC4zODU3MTQyODYgMC4zODU3MTQyODYsMCAwLjg1NzE0Mjg1NywwIEwxMS4xNDI4NTcxLDAgQzExLjYxNDI4NTcsMCAxMiwwLjM4NTcxNDI4NiAxMiwwLjg1NzE0Mjg1NyBMMTIsMC44NTcxNDI4NTcgWiBNMTEuMTQyODU3MSwwLjg1NzE0Mjg1NyBMMC44NTcxNDI4NTcsMC44NTcxNDI4NTcgTDAuODU3MTQyODU3LDExLjE0Mjg1NzEgTDExLjE0Mjg1NzEsMTEuMTQyODU3MSBMMTEuMTQyODU3MSwwLjg1NzE0Mjg1NyBMMTEuMTQyODU3MSwwLjg1NzE0Mjg1NyBaIiB0cmFuc2Zvcm09Im1hdHJpeCgwIC0xIC0xIDAgMTIgMTIpIi8+PC9zdmc+\')',
125 | 'background-color': `${pushColor}`
126 | },
127 | 'icon_pull': {
128 | 'transform': 'scaleY(-1)',
129 | '-webkit-mask-position': '0 8px'
130 | }
131 | }
132 | }
133 |
134 |
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/src/lib/plugins/hostname.js:
--------------------------------------------------------------------------------
1 | import os from 'os'
2 | import React from 'react'
3 | import Component from 'hyper/component'
4 | import SvgIcon from '../utils/svg-icon'
5 |
6 | class PluginIcon extends Component {
7 | render() {
8 |
9 | return (
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
25 |
26 | )
27 | }
28 | }
29 |
30 | export default class HostName extends Component {
31 | static displayName() {
32 | return 'hostname'
33 | }
34 |
35 | render() {
36 | const hostname = os.hostname()
37 | const username = process.env.USER
38 |
39 | return (
40 |
41 |
{username}@
42 | {hostname}
43 |
44 |
50 |
51 | )
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/lib/plugins/index.js:
--------------------------------------------------------------------------------
1 | import hostname from './hostname'
2 | import ip from './ip'
3 | import memory from './memory'
4 | // Import Uptime from './uptime'
5 | import cpu from './cpu'
6 | import network from './network'
7 | import battery from './battery'
8 | // Import Time from './time'
9 | // Import Docker from './docker'
10 | import spotify from './spotify'
11 |
12 | export default [hostname, ip, memory, battery, cpu, network, spotify]
13 |
--------------------------------------------------------------------------------
/src/lib/plugins/ip.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Component from 'hyper/component'
3 | import publicIp from 'public-ip'
4 | import SvgIcon from '../utils/svg-icon'
5 |
6 | function getIp() {
7 | return new Promise(resolve => {
8 | publicIp.v4().then(ip => resolve(ip)).catch(() => resolve('?.?.?.?'))
9 | })
10 | }
11 |
12 | class PluginIcon extends Component {
13 | render() {
14 | return (
15 |
16 |
17 |
21 |
25 |
26 |
27 |
32 |
33 | )
34 | }
35 | }
36 |
37 | export default class Ip extends Component {
38 | static displayName() {
39 | return 'ip'
40 | }
41 |
42 | constructor(props) {
43 | super(props)
44 |
45 | this.state = {
46 | ip: '?.?.?.?'
47 | }
48 |
49 | this.setIp = this.setIp.bind(this)
50 | }
51 |
52 | setIp() {
53 | getIp().then(ip => this.setState({ ip }))
54 | }
55 |
56 | componentDidMount() {
57 | // Every 5 seconds
58 | this.setIp()
59 | this.interval = setInterval(() => this.setIp(), 60000 * 5)
60 | }
61 |
62 | componentWillUnmount() {
63 | clearInterval(this.interval)
64 | }
65 |
66 | render() {
67 | return (
68 |
69 |
{this.state.ip}
70 |
71 |
77 |
78 | )
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/lib/plugins/memory.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Component from 'hyper/component'
3 | import { mem as memoryData } from 'systeminformation'
4 | import leftPad from 'left-pad'
5 | import SvgIcon from '../utils/svg-icon'
6 |
7 | class PluginIcon extends Component {
8 | render() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
37 |
38 |
39 | )
40 | }
41 | }
42 |
43 | export default class Memory extends Component {
44 | static displayName() {
45 | return 'memory'
46 | }
47 |
48 | constructor(props) {
49 | super(props)
50 |
51 | this.state = {
52 | activeMemory: 0,
53 | totalMemory: 0
54 | }
55 |
56 | this.getMemory = this.getMemory.bind(this)
57 | this.setMemory = this.setMemory.bind(this)
58 | }
59 |
60 | componentDidMount() {
61 | this.setMemory()
62 | this.interval = setInterval(() => this.setMemory(), 2500)
63 | }
64 |
65 | componentWillUnmount() {
66 | clearInterval(this.interval)
67 | }
68 |
69 | getMemory() {
70 | return memoryData().then(memory => {
71 | const totalMemory = this.getMb(memory.total)
72 | const activeMemory = this.getMb(memory.active)
73 | const totalWidth = totalMemory.toString().length
74 |
75 | return {
76 | activeMemory: leftPad(activeMemory, totalWidth, 0),
77 | totalMemory
78 | }
79 | })
80 | }
81 |
82 | setMemory() {
83 | return this.getMemory().then(data => this.setState(data))
84 | }
85 |
86 | getMb(bytes) {
87 | // 1024 * 1024 = 1048576
88 | return (bytes / 1048576).toFixed(0)
89 | }
90 |
91 | render() {
92 | return (
93 |
94 |
{this.state.activeMemory}MB / {this.state.totalMemory}MB
95 |
96 |
102 |
103 | )
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/lib/plugins/network.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Component from 'hyper/component'
3 | import { networkStats } from 'systeminformation'
4 | import SvgIcon from '../utils/svg-icon'
5 |
6 | class PluginIcon extends Component {
7 | render() {
8 | return (
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
29 |
30 | )
31 | }
32 | }
33 |
34 | export default class Network extends Component {
35 | static displayName() {
36 | return 'network'
37 | }
38 |
39 | constructor(props) {
40 | super(props)
41 | this.state = {
42 | download: 0,
43 | upload: 0
44 | }
45 | }
46 |
47 | componentDidMount() {
48 | this.getSpeed()
49 | this.interval = setInterval(() => this.getSpeed(), 1500)
50 | }
51 |
52 | componentWillUnmount() {
53 | clearInterval(this.interval)
54 | }
55 |
56 | calculate(data) {
57 | const rawData = data / 1024
58 | return (rawData > 0 ? rawData : 0).toFixed()
59 | }
60 |
61 | getSpeed() {
62 | networkStats().then(data =>
63 | this.setState({
64 | download: this.calculate(data.rx_sec),
65 | upload: this.calculate(data.tx_sec)
66 | })
67 | )
68 | }
69 |
70 | render() {
71 | const { download, upload } = this.state
72 | return (
73 |
74 |
{download}kB/s {upload}kB/s
75 |
76 |
82 |
83 | )
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/lib/plugins/spotify.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Component from 'hyper/component'
3 | import spotify from 'spotify-node-applescript'
4 | import SvgIcon from '../utils/svg-icon'
5 |
6 | class PluginIcon extends Component {
7 | render() {
8 | return (
9 |
10 |
11 |
12 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
31 |
32 | )
33 | }
34 | }
35 |
36 | export default class Spotify extends Component {
37 | static displayName() {
38 | return 'spotify'
39 | }
40 |
41 | constructor(props) {
42 | super(props)
43 |
44 | this.state = { version: 'Not running' }
45 | this.setStatus = this.setStatus.bind(this)
46 |
47 | this.handleSpotifyActivation = this.handleSpotifyActivation.bind(this)
48 | }
49 |
50 | setStatus() {
51 | spotify.isRunning((err, isRunning) => {
52 | if (!isRunning) {
53 | this.setState({ state: 'Not running' })
54 | return
55 | }
56 | if (err) {
57 | console.log(`Caught exception at setStatus(e): ${err}`)
58 | }
59 | spotify.getState((err, st) => {
60 | if (err) {
61 | console.log(`Caught exception at spotify.getState(e): ${err}`)
62 | }
63 |
64 | spotify.getTrack((err, track) => {
65 | if (err) {
66 | console.log(`Caught exception at spotify.getTrack(e): ${err}`)
67 | }
68 | this.setState({
69 | state: `${st.state === 'playing'
70 | ? '▶'
71 | : '❚❚'} ${track.artist} - ${track.name}`
72 | })
73 | })
74 | })
75 | })
76 | }
77 |
78 | /*
79 | TODO: Make this work on Linux and Win 32/64
80 | */
81 | handleSpotifyActivation(e) {
82 | e.preventDefault()
83 | console.log('HANDLE CLICKED FOR SPOTIFY')
84 | spotify.isRunning((err, isRunning) => {
85 | if (!isRunning) {
86 | spotify.openSpotify()
87 | }
88 |
89 | if (err) {
90 | console.log(`Caught exception at handleSpotifyActivation(e): ${err}`)
91 | }
92 | })
93 | }
94 |
95 | componentDidMount() {
96 | this.setStatus()
97 | this.interval = setInterval(() => this.setStatus(), 1000)
98 | }
99 |
100 | componentWillUnmount() {
101 | clearInterval(this.interval)
102 | }
103 |
104 | render() {
105 | return (
106 |
110 |
{this.state.state}
111 |
112 |
119 |
120 | )
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/lib/plugins/time.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Component from 'hyper/component'
3 | import moment from 'moment'
4 | import SvgIcon from '../utils/svg-icon'
5 |
6 | class PluginIcon extends Component {
7 | render() {
8 | return (
9 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
27 |
28 | )
29 | }
30 | }
31 |
32 | export default class Time extends Component {
33 | static displayName() {
34 | return 'time'
35 | }
36 |
37 | constructor(props) {
38 | super(props)
39 |
40 | this.state = {
41 | time: this.getCurrentTime()
42 | }
43 | }
44 |
45 | componentDidMount() {
46 | this.interval = setInterval(() => {
47 | this.setState({ time: this.getCurrentTime() })
48 | }, 100)
49 | }
50 |
51 | componentWillUnmount() {
52 | clearInterval(this.interval)
53 | }
54 |
55 | getCurrentTime() {
56 | // TODO: Allow for format overriding by the user
57 | return moment().format('LTS')
58 | }
59 |
60 | render() {
61 | return (
62 |
63 |
{this.state.time}
64 |
65 |
71 |
72 | )
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/lib/plugins/uptime.js:
--------------------------------------------------------------------------------
1 | import os from 'os'
2 | import React from 'react'
3 | import Component from 'hyper/component'
4 | import formatUptime from '../utils/time'
5 | import SvgIcon from '../utils/svg-icon'
6 |
7 | class PluginIcon extends Component {
8 | render() {
9 | return (
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
28 |
29 | )
30 | }
31 | }
32 |
33 | export default class Uptime extends Component {
34 | static displayName() {
35 | return 'uptime'
36 | }
37 |
38 | constructor(props) {
39 | super(props)
40 |
41 | this.state = {
42 | uptime: this.getUptime()
43 | }
44 | }
45 |
46 | componentDidMount() {
47 | const uptime = this.getUptime()
48 | // Recheck every 5 minutes
49 | setInterval(() => this.setState({ uptime }), 60000 * 5)
50 | }
51 |
52 | getUptime() {
53 | return formatUptime(os.uptime())
54 | }
55 |
56 | template(css) {
57 | return (
58 |
59 |
{this.state.uptime}
60 |
61 |
67 |
68 | )
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/lib/utils/colors.js:
--------------------------------------------------------------------------------
1 | // Taken from https://github.com/zeit/hyper/blob/master/lib/utils/colors.js
2 | // Effect of this script is the reverse of colors.js in hyper.app
3 | const colorList = [
4 | 'black',
5 | 'red',
6 | 'green',
7 | 'yellow',
8 | 'blue',
9 | 'magenta',
10 | 'cyan',
11 | 'white',
12 | 'lightBlack',
13 | 'lightRed',
14 | 'lightGreen',
15 | 'lightYellow',
16 | 'lightBlue',
17 | 'lightMagenta',
18 | 'lightCyan',
19 | 'lightWhite',
20 | 'colorCubes',
21 | 'grayscale'
22 | ];
23 |
24 | export function getColorList(colors) {
25 | // For forwards compatibility, return early if it's already an object
26 | if (!Array.isArray(colors)) {
27 | return colors;
28 | }
29 |
30 | // For backwards compatibility
31 | const colorsList = {}
32 | colors.forEach( ( color, index ) => {
33 | colorsList[colorList[index]] = color
34 | });
35 |
36 | return colorsList;
37 | }
38 |
39 | export function colorExists(name) {
40 | return colorList.indexOf(name) !== -1
41 | }
42 |
--------------------------------------------------------------------------------
/src/lib/utils/config.js:
--------------------------------------------------------------------------------
1 | // Import plugins from '../plugins'
2 | // import { colorExists } from './colors'
3 | // import notify from 'hyper/notify'
4 | //
5 | // function getPluginFromListByName(pluginList, name) {
6 | // return pluginList.find(each => each.name === name)
7 | // }
8 | //
9 | // function mergeColorConfigs(defaultColor, userColor = false) {
10 | // if (!userColor || !colorExists(userColor)) {
11 | // return defaultColor
12 | // }
13 | //
14 | // return userColor
15 | // }
16 | //
17 | // function mergePluginConfigs(defaultPlugins, userPlugins) {
18 | // if (!userPlugins) {
19 | // return defaultPlugins
20 | // }
21 | //
22 | // return userPlugins.reduce((newPlugins, plugin) => {
23 | // const newPlugin = Object.assign({}, plugin)
24 | // const { name, options = false } = plugin
25 | //
26 | // if (typeof plugin !== 'object' || Array.isArray(plugin)) {
27 | // notify('HyperLine', '\'plugins\' array members in \'.hyper.js\' must be objects.')
28 | // return newPlugins
29 | // }
30 | //
31 | // const { options: defaultOptions = false } = getPluginFromListByName(defaultPlugins, name)
32 | //
33 | // if (!defaultOptions) {
34 | // notify('HyperLine', `Plugin with name "${name}" does not exist.`)
35 | // return newPlugins
36 | // }
37 | //
38 | // if (options) {
39 | // newPlugin.options = defaultOptions
40 | // }
41 | //
42 | // const { validateOptions: validator = false } = plugins[name]
43 | // if (validator) {
44 | // const errors = validator(options)
45 | // if (errors.length > 0) {
46 | // errors.forEach(error => notify(`HyperLine '${name}' plugin`, error))
47 | // newPlugin.options = defaultOptions
48 | // }
49 | // }
50 | //
51 | // return [ ...newPlugins, plugin ]
52 | // }, [])
53 | // }
54 | //
55 | // export function getDefaultConfig(plugins) {
56 | // return {
57 | // color: 'black',
58 | // plugins: Object.keys(plugins).reduce((pluginsArray, pluginName) => {
59 | // const { defaultOptions } = plugins[pluginName]
60 | //
61 | // const plugin = {
62 | // name: pluginName,
63 | // options: defaultOptions
64 | // }
65 | //
66 | // return [ ...pluginsArray, plugin ]
67 | // }, [])
68 | // }
69 | // }
70 | //
71 | // export function mergeConfigs(defaultConfig, userConfig = false) {
72 | // if (!userConfig) {
73 | // return defaultConfig
74 | // }
75 | //
76 | // return {
77 | // color: mergeColorConfigs(defaultConfig.color, userConfig.color),
78 | // plugins: mergePluginConfigs(defaultConfig.plugins, userConfig.plugins)
79 | // }
80 | // }
81 |
--------------------------------------------------------------------------------
/src/lib/utils/svg-icon.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Component from 'hyper/component'
4 |
5 | export default class SvgIcon extends Component {
6 | static propTypes() {
7 | return {
8 | children: PropTypes.element.isRequired
9 | }
10 | }
11 |
12 | render() {
13 | return (
14 |
25 | )
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/lib/utils/time.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment'
2 |
3 | export default function formatUptime(uptime) {
4 | const uptimeInHours = Number((uptime / 3600).toFixed(0))
5 |
6 | if (uptimeInHours === 0) {
7 | return '0h'
8 | }
9 |
10 | const uptimeInMoment = moment.duration(uptimeInHours, 'hours')
11 | const days = uptimeInMoment.days()
12 | const hours = uptimeInMoment.hours()
13 | const daysFormatted = days ? days + 'd' : ''
14 | const hoursFormatted = hours ? hours + 'h' : ''
15 |
16 | return [daysFormatted, hoursFormatted].filter(Boolean).join(' ')
17 | }
18 |
--------------------------------------------------------------------------------
/tests/__snapshots__/time.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`time formatUptime 1 day 4 hours 1`] = `"1d 4h"`;
4 |
5 | exports[`time formatUptime 3 days 1`] = `"3d"`;
6 |
7 | exports[`time formatUptime 3 days 8 minutes 1`] = `"3d"`;
8 |
9 | exports[`time formatUptime 3 hours 48 minutes 1`] = `"4h"`;
10 |
11 | exports[`time formatUptime 3 seconds 1`] = `"0h"`;
12 |
13 | exports[`time formatUptime 4 hours 1`] = `"4h"`;
14 |
15 | exports[`time formatUptime 23 hours 1`] = `"23h"`;
16 |
17 | exports[`time formatUptime 23 hours 17 minutes 1`] = `"23h"`;
18 |
19 | exports[`time formatUptime 23 hours 49 minutes 1`] = `"1d"`;
20 |
21 | exports[`time formatUptime 23 minutes 1`] = `"0h"`;
22 |
23 | exports[`time formatUptime 29 minutes 1`] = `"0h"`;
24 |
25 | exports[`time formatUptime 30 minutes 1`] = `"1h"`;
26 |
27 | exports[`time formatUptime 31 minutes 1`] = `"1h"`;
28 |
29 | exports[`time formatUptime is a function 1`] = `[Function]`;
30 |
--------------------------------------------------------------------------------
/tests/spotify.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jest */
2 | import spotify from 'spotify-node-applescript'
3 |
4 | describe('spotify', () => {
5 | it('should open spotify', () => {
6 | expect(spotify.openSpotify()).toBeUndefined()
7 | })
8 | })
9 |
--------------------------------------------------------------------------------
/tests/time.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jest */
2 | import formatUptime from '../src/lib/utils/time'
3 |
4 | describe('time', () => {
5 | describe('formatUptime', () => {
6 | it('is a function', () => expect(formatUptime).toMatchSnapshot())
7 | it('3 seconds', () => expect(formatUptime(3)).toMatchSnapshot())
8 | it('23 minutes', () => expect(formatUptime(1380)).toMatchSnapshot())
9 | it('29 minutes', () => expect(formatUptime(1740)).toMatchSnapshot())
10 | it('30 minutes', () => expect(formatUptime(1800)).toMatchSnapshot())
11 | it('31 minutes', () => expect(formatUptime(1860)).toMatchSnapshot())
12 | it('3 hours 48 minutes', () =>
13 | expect(formatUptime(13680)).toMatchSnapshot())
14 | it('4 hours', () => expect(formatUptime(14400)).toMatchSnapshot())
15 | it('23 hours', () => expect(formatUptime(82800)).toMatchSnapshot())
16 | it('23 hours 17 minutes', () =>
17 | expect(formatUptime(83820)).toMatchSnapshot())
18 | it('23 hours 49 minutes', () =>
19 | expect(formatUptime(85740)).toMatchSnapshot())
20 | it('1 day 4 hours', () => expect(formatUptime(100800)).toMatchSnapshot())
21 | it('3 days', () => expect(formatUptime(259200)).toMatchSnapshot())
22 | it('3 days 8 minutes', () => expect(formatUptime(259680)).toMatchSnapshot())
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const nodeExternals = require('webpack-node-externals')
3 |
4 | module.exports = {
5 | target: 'node',
6 | entry: './src/index.js',
7 | output: {
8 | path: './dist',
9 | filename: 'hyperline.js',
10 | libraryTarget: 'commonjs'
11 | },
12 | plugins: [ new webpack.DefinePlugin({ 'global.GENTLY': false }) ],
13 | externals: [ nodeExternals(), 'hyper/component', 'hyper/notify', 'hyper/decorate', 'react' ],
14 | module: {
15 | rules: [
16 | {
17 | test: /\.json$/,
18 | loader: 'json-loader'
19 | },
20 | {
21 | test: /\.js$/,
22 | loader: 'babel-loader',
23 | exclude: /node_modules/
24 | }
25 | ]
26 | }
27 | }
28 |
--------------------------------------------------------------------------------