-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Add Wakatime integration #2923
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Wakatime integration #2923
Changes from all commits
6dc8826
052c70b
57705cf
39a98e7
a0f5a06
8ec7d19
2ea0514
98d4fa0
2ac38e9
938b075
fd3e243
6fe6794
1aaba74
c02ab03
76da76a
8ede1a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import config from 'browser/main/lib/ConfigManager' | ||
| const exec = require('child_process').exec | ||
| const path = require('path') | ||
| let lastHeartbeat = 0 | ||
|
|
||
| function sendWakatimeHeartBeat( | ||
| storagePath, | ||
| noteKey, | ||
| storageName, | ||
| { isWrite, hasFileChanges, isFileChange } | ||
| ) { | ||
| if ( | ||
| config.get().wakatime.isActive && | ||
| !!config.get().wakatime.key && | ||
| (new Date().getTime() - lastHeartbeat > 120000 || isFileChange) | ||
| ) { | ||
| const notePath = path.join(storagePath, 'notes', noteKey + '.cson') | ||
|
|
||
| if (!isWrite && !hasFileChanges && !isFileChange) { | ||
| return | ||
| } | ||
|
|
||
| lastHeartbeat = new Date() | ||
| const wakatimeKey = config.get().wakatime.key | ||
| if (wakatimeKey) { | ||
| exec( | ||
| `wakatime --file ${notePath} --project '${storageName}' --key ${wakatimeKey} --plugin Boostnote-wakatime`, | ||
| (error, stdOut, stdErr) => { | ||
| if (error) { | ||
| console.log(error) | ||
| lastHeartbeat = 0 | ||
| } else { | ||
| console.log( | ||
| 'wakatime', | ||
| 'isWrite', | ||
| isWrite, | ||
| 'hasChanges', | ||
| hasFileChanges, | ||
| 'isFileChange', | ||
| isFileChange | ||
| ) | ||
| } | ||
| } | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| export { sendWakatimeHeartBeat } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,207 @@ | ||
| import PropTypes from 'prop-types' | ||
| import React from 'react' | ||
| import CSSModules from 'browser/lib/CSSModules' | ||
| import styles from './ConfigTab.styl' | ||
| import ConfigManager from 'browser/main/lib/ConfigManager' | ||
| import { store } from 'browser/main/store' | ||
| import _ from 'lodash' | ||
| import i18n from 'browser/lib/i18n' | ||
| import { sync as commandExists } from 'command-exists' | ||
| const electron = require('electron') | ||
| const ipc = electron.ipcRenderer | ||
| const { remote } = electron | ||
| const { dialog } = remote | ||
| class PluginsTab extends React.Component { | ||
| constructor(props) { | ||
| super(props) | ||
|
|
||
| this.state = { | ||
| config: props.config | ||
| } | ||
| } | ||
|
|
||
| componentDidMount() { | ||
| this.handleSettingDone = () => { | ||
| this.setState({ | ||
| pluginsAlert: { | ||
| type: 'success', | ||
| message: i18n.__('Successfully applied!') | ||
| } | ||
| }) | ||
| } | ||
| this.handleSettingError = err => { | ||
| this.setState({ | ||
| pluginsAlert: { | ||
| type: 'error', | ||
| message: | ||
| err.message != null ? err.message : i18n.__('An error occurred!') | ||
| } | ||
| }) | ||
| } | ||
| this.oldWakatimeConfig = this.state.config.wakatime | ||
| ipc.addListener('APP_SETTING_DONE', this.handleSettingDone) | ||
| ipc.addListener('APP_SETTING_ERROR', this.handleSettingError) | ||
| } | ||
|
|
||
| componentWillUnmount() { | ||
| ipc.removeListener('APP_SETTING_DONE', this.handleSettingDone) | ||
| ipc.removeListener('APP_SETTING_ERROR', this.handleSettingError) | ||
| } | ||
|
|
||
| checkWakatimePluginRequirement() { | ||
| const { wakatime } = this.state.config | ||
| if (wakatime.isActive && !commandExists('wakatime')) { | ||
| this.setState({ | ||
| wakatimePluginAlert: { | ||
| type: i18n.__('Warning'), | ||
| message: i18n.__('Missing wakatime cli') | ||
| } | ||
| }) | ||
|
|
||
| const alertConfig = { | ||
| type: 'warning', | ||
| message: i18n.__('Missing Wakatime CLI'), | ||
| detail: i18n.__( | ||
| `Please install Wakatime CLI to use Wakatime tracker feature.` | ||
| ), | ||
| buttons: [i18n.__('OK')] | ||
| } | ||
| dialog.showMessageBox(remote.getCurrentWindow(), alertConfig) | ||
| } else { | ||
| this.setState({ | ||
| wakatimePluginAlert: null | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| handleSaveButtonClick(e) { | ||
| const newConfig = { | ||
| wakatime: { | ||
| isActive: this.state.config.wakatime.isActive, | ||
| key: this.state.config.wakatime.key | ||
| } | ||
| } | ||
|
|
||
| ConfigManager.set(newConfig) | ||
|
|
||
| store.dispatch({ | ||
| type: 'SET_CONFIG', | ||
| config: newConfig | ||
| }) | ||
| this.clearMessage() | ||
| this.props.haveToSave() | ||
| this.checkWakatimePluginRequirement() | ||
| } | ||
|
|
||
| handleIsWakatimePluginActiveChange(e) { | ||
| const { config } = this.state | ||
| config.wakatime.isActive = !config.wakatime.isActive | ||
| this.setState({ | ||
| config | ||
| }) | ||
| if (_.isEqual(this.oldWakatimeConfig.isActive, config.wakatime.isActive)) { | ||
| this.props.haveToSave() | ||
| } else { | ||
| this.props.haveToSave({ | ||
| tab: 'Plugins', | ||
| type: 'warning', | ||
| message: i18n.__('Unsaved Changes!') | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| handleWakatimeKeyChange(e) { | ||
| const { config } = this.state | ||
| config.wakatime = { | ||
| isActive: true, | ||
| key: this.refs.wakatimeKey.value | ||
| } | ||
| this.setState({ | ||
| config | ||
| }) | ||
| if (_.isEqual(this.oldWakatimeConfig.key, config.wakatime.key)) { | ||
| this.props.haveToSave() | ||
| } else { | ||
| this.props.haveToSave({ | ||
| tab: 'Plugins', | ||
| type: 'warning', | ||
| message: i18n.__('Unsaved Changes!') | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| clearMessage() { | ||
| _.debounce(() => { | ||
| this.setState({ | ||
| pluginsAlert: null | ||
| }) | ||
| }, 2000)() | ||
| } | ||
|
|
||
| render() { | ||
| const pluginsAlert = this.state.pluginsAlert | ||
| const pluginsAlertElement = | ||
| pluginsAlert != null ? ( | ||
| <p className={`alert ${pluginsAlert.type}`}>{pluginsAlert.message}</p> | ||
| ) : null | ||
|
|
||
| const wakatimeAlert = this.state.wakatimePluginAlert | ||
| const wakatimePluginAlertElement = | ||
| wakatimeAlert != null ? ( | ||
| <p className={`alert ${wakatimeAlert.type}`}>{wakatimeAlert.message}</p> | ||
| ) : null | ||
|
|
||
| const { config } = this.state | ||
|
|
||
| return ( | ||
| <div styleName='root'> | ||
| <div styleName='group'> | ||
| <div styleName='group-header'>{i18n.__('Plugins')}</div> | ||
| <div styleName='group-header2'>{i18n.__('Wakatime')}</div> | ||
| <div styleName='group-checkBoxSection'> | ||
| <label> | ||
| <input | ||
| onChange={e => this.handleIsWakatimePluginActiveChange(e)} | ||
| checked={config.wakatime.isActive} | ||
| ref='wakatimeIsActive' | ||
| type='checkbox' | ||
| /> | ||
| | ||
| {i18n.__('Enable Wakatime')} | ||
| </label> | ||
| </div> | ||
| <div styleName='group-section'> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think Wakatime isn't the only plugin that we support so it's better to create a section for Wakatime in this plugin setting page. Just a simple section with title Wakatime and bellow is a check box saying enable wakatime? and below it is the text box for wakatime key. If we plan to only have 1 plugin then this plugin page should change the name to Wakatime which I think will be worst cus harder to change when we want to support more plugins
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done 👍 |
||
| <div styleName='group-section-label'>{i18n.__('Wakatime key')}</div> | ||
| <div styleName='group-section-control'> | ||
| <input | ||
| styleName='group-section-control-input' | ||
| onChange={e => this.handleWakatimeKeyChange(e)} | ||
| disabled={!config.wakatime.isActive} | ||
| ref='wakatimeKey' | ||
| value={config.wakatime.key} | ||
| type='text' | ||
| /> | ||
| {wakatimePluginAlertElement} | ||
| </div> | ||
| </div> | ||
| <div styleName='group-control'> | ||
| <button | ||
| styleName='group-control-rightButton' | ||
| onClick={e => this.handleSaveButtonClick(e)} | ||
| > | ||
| {i18n.__('Save')} | ||
| </button> | ||
| {pluginsAlertElement} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| PluginsTab.propTypes = { | ||
| dispatch: PropTypes.func, | ||
| haveToSave: PropTypes.func | ||
| } | ||
|
|
||
| export default CSSModules(PluginsTab, styles) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The last 3 variable is all booleans. I suggest we change to object type for more clarification. For example, if we change this function to:
We can keep the code inside the same but when we call this function, we will call it like this: