Skip to content

Commit

Permalink
Merge pull request #278 from charjac/feat.httpApi
Browse files Browse the repository at this point in the history
feat: add a simple express rest api to remotly control nuclear
  • Loading branch information
nukeop authored Mar 15, 2019
2 parents 1232433 + 79100ca commit 45edc75
Show file tree
Hide file tree
Showing 27 changed files with 1,052 additions and 68 deletions.
10 changes: 5 additions & 5 deletions app/actions/playlists.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function addPlaylist(tracks, name) {

if (tracks.length === 0) {
dispatch({
type: null
type: null
});
return;
}
Expand All @@ -35,13 +35,13 @@ export function loadPlaylists() {

if (playlists) {
dispatch({
type: LOAD_PLAYLISTS,
payload: playlists
type: LOAD_PLAYLISTS,
payload: playlists
});
} else {
dispatch({
type: LOAD_PLAYLISTS,
payload: []
type: LOAD_PLAYLISTS,
payload: []
});
}
};
Expand Down
13 changes: 9 additions & 4 deletions app/components/Settings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,15 +172,20 @@ class Settings extends React.Component {
</div>);
}
renderNumberOption (option) {
if (typeof option.min === 'number' && typeof option.max === 'number') {
if (typeof option.unit === 'string') {
return this.renderSliderOption(option);
} else {
const value = this.getOptionValue(option);

return (<Input
type={typeof option.min === 'number' && typeof option.max === 'number' ? 'number' : 'text' }
min={option.min}
max={option.max}
fluid
value={this.getOptionValue(option) || option.default}
error={!this.validateNumberInput(this.getOptionValue(option))}
value={ value || option.default}
error={!this.validateNumberInput(value)}
onChange={
e => this.validateNumberInput(this.getOptionValue(option)) && this.props.actions.setNumberOption(option.name, _.parseInt(e.target.value))
e => !!e.target.value && this.validateNumberInput(value) && this.props.actions.setNumberOption(option.name, _.parseInt(e.target.value))
}
/>);
}
Expand Down
16 changes: 16 additions & 0 deletions app/constants/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,21 @@ module.exports = [
type: settingType.STRING,
prettyName: 'MPD HTTP stream address',
default: 'localhost:8888'
},
{
name: 'api.enabled',
category: 'HTTP API',
type: settingType.BOOLEAN,
prettyName: 'Enable the api',
default: true
},
{
name: 'api.port',
category: 'HTTP API',
type: settingType.NUMBER,
prettyName: 'Port used by the api',
default: 8080,
min: 1024,
max: 49151
}
];
38 changes: 31 additions & 7 deletions app/containers/IpcContainer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,29 @@ import { bindActionCreators } from 'redux';
import { ipcRenderer } from 'electron';
import * as PlayerActions from '../../actions/player';
import * as QueueActions from '../../actions/queue';
import * as SettingsActions from '../../actions/settings';
import * as PlaylistActions from '../../actions/playlists';

import { onNext, onPrevious, onPause, onPlayPause, onStop, onPlay, onSongChange} from '../../mpris';
import {
onNext,
onPrevious,
onPause,
onPlayPause,
onStop,
onPlay,
onSongChange,
onSettings,
onVolume,
onSeek,
sendPlayingStatus,
sendQueueItems,
onMute,
onEmptyQueue,
onCreatePlaylist,
onRefreshPlaylists
} from '../../mpris';

class IpcContainer extends React.Component {
constructor(props) {
super(props);
}

componentDidMount() {
ipcRenderer.send('started');
ipcRenderer.on('next', event => onNext(event, this.props.actions));
Expand All @@ -21,11 +36,20 @@ class IpcContainer extends React.Component {
ipcRenderer.on('playpause', event => onPlayPause(event, this.props.actions, this.props.player));
ipcRenderer.on('stop', event => onStop(event, this.props.actions));
ipcRenderer.on('play', event => onPlay(event, this.props.actions));
ipcRenderer.on('settings', (event, data) => onSettings(event, data, this.props.actions));
ipcRenderer.on('mute', event => onMute(event, this.props.actions, this.props.player));
ipcRenderer.on('volume', (event, data) => onVolume(event, data, this.props.actions));
ipcRenderer.on('seek', (event, data) => onSeek(event, data, this.props.actions));
ipcRenderer.on('playing-status', event => sendPlayingStatus(event, this.props.player, this.props.queue));
ipcRenderer.on('empty-queue', event => onEmptyQueue(event, this.props.actions));
ipcRenderer.on('queue', event => sendQueueItems(event, this.props.queue.queueItems));
ipcRenderer.on('create-playlist', (event, name) => onCreatePlaylist(event, { name, tracks: this.props.queue.queueItems }, this.props.actions));
ipcRenderer.on('refresh-playlists', (event) => onRefreshPlaylists(event, this.props.actions));
}

componentWillReceiveProps(nextProps){
if (this.props !== nextProps) {
let currentSong = nextProps.queue.queueItems[nextProps.queue.currentSong];
const currentSong = nextProps.queue.queueItems[nextProps.queue.currentSong];
onSongChange(currentSong);
}
}
Expand All @@ -44,7 +68,7 @@ function mapStateToProps(state) {

function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Object.assign({}, PlayerActions, QueueActions), dispatch)
actions: bindActionCreators(Object.assign({}, PlayerActions, QueueActions, SettingsActions, PlaylistActions), dispatch)
};
}

Expand Down
68 changes: 68 additions & 0 deletions app/mpris.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,52 @@ export function onPlay(event, actions) {
actions.startPlayback();
}

export function onSettings(event, data, actions) {
const key = Object.keys(data).pop();
const value = Object.values(data).pop();

switch (typeof value) {
case 'boolean':
actions.setBooleanOption(key, value);
break;
case 'number':
actions.setNumberOption(key, value);
break;
case 'string':
default:
actions.setStringOption(key, value);
break;
}
}

export function onMute(event, actions, playerState) {
if (playerState.muted) {
actions.unMute();
} else {
actions.mute();
}
}

export function onVolume(event, data, actions) {
actions.updateVolume(data);
}

export function onSeek(event, data, actions) {
actions.updateSeek(data);
}

export function onEmptyQueue(event, actions) {
actions.clearQueue();
}

export function onCreatePlaylist(event, { tracks, name }, actions) {
actions.addPlaylist(tracks, name);
}

export function onRefreshPlaylists(event, actions) {
actions.loadPlaylists();
}

export function onSongChange(song) {
ipcRenderer.send('songChange', song);
}
Expand All @@ -47,3 +93,25 @@ export function sendMinimize() {
export function sendMaximize() {
ipcRenderer.send('maximize');
}

export function restartApi() {
ipcRenderer.send('restart-api');
}

export function stopApi() {
ipcRenderer.send('stop-api');
}

export function sendPlayingStatus(event, playerState, queueState) {
try {
const { artist, name, thumbnail } = queueState.queueItems[queueState.currentSong];

ipcRenderer.send('playing-status', { ...playerState, artist, name, thumbnail });
} catch (err) {
ipcRenderer.send('playing-status', playerState);
}
}

export function sendQueueItems(event, queueItems) {
ipcRenderer.send('queue', queueItems);
}
22 changes: 19 additions & 3 deletions app/persistence/store.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
let { app } = require('electron').remote;
import _ from 'lodash';
import electronStore from 'electron-store';

import options from '../constants/settings';
import { restartApi, stopApi } from '../mpris';

const store = new electronStore();

Expand All @@ -22,18 +23,33 @@ function initStore () {
initStore();

function getOption (key) {
let settings = store.get('settings') || {};
const settings = store.get('settings') || {};
let value = settings[key];

if (typeof value === 'undefined') {
value = _.find(options, { name: key }).default;
}

return value;
}

function isValidPort(value) {
return typeof value === 'number' && value > 1024 && value < 49151;
}

function setOption (key, value) {
let settings = store.get('settings') || {};
const settings = store.get('settings') || {};

store.set('settings', Object.assign({}, settings, { [`${key}`]: value }));

if (
(key === 'api.port' && isValidPort(value) && getOption('api.enabled')) ||
(key === 'api.enabled' && value)
) {
restartApi();
} else if (key === 'api.enabled' && !value) {
stopApi();
}
}

export { store, getOption, setOption };
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,14 @@
"dependencies": {
"billboard-top-100": "^2.0.8",
"bluebird": "^3.5.3",
"body-parser": "^1.18.3",
"cheerio": "^1.0.0-rc.2",
"cors": "^2.8.5",
"electron-platform": "^1.2.0",
"electron-store": "^2.0.0",
"electron-timber": "^0.5.1",
"express": "^4.16.4",
"express-json-validator-middleware": "^1.2.3",
"fast-levenshtein": "^2.0.6",
"font-awesome": "^4.7.0",
"get-artist-title": "^1.1.1",
Expand All @@ -69,6 +73,7 @@
"semantic-ui-react": "^0.82.1",
"simple-get-lyrics": "0.0.4",
"styled-components": "^3.2.6",
"swagger-spec-express": "^2.0.7",
"uuid": "^3.2.1",
"webpack-cli": "^3.0.8",
"youtube-playlist": "^1.0.2",
Expand Down
6 changes: 6 additions & 0 deletions server/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "../.eslintrc.js",
"env": {
"node": true
}
}
6 changes: 6 additions & 0 deletions server/http/api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from './window';
export * from './settings';
export * from './player';
export * from './swagger';
export * from './playlist';
export * from './queue';
Loading

0 comments on commit 45edc75

Please sign in to comment.