├── .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 | ![Screenshot](./screenshot.png) 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 | 15 | {this.props.children} 16 | 17 | 24 | 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 | --------------------------------------------------------------------------------