Skip to content

Commit

Permalink
Fix external links, share + some cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
uffou committed Jan 18, 2019
1 parent 7a73c66 commit f23cd85
Show file tree
Hide file tree
Showing 16 changed files with 105 additions and 283 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ MixCloud Play
=====
MixCloud Play is the missing desktop experience for MixCloud.com with support for media controls and showing current track in menu bar. Enjoy listening your favorite music for hours...

## [Download for Mac](https://github.com/uffou/MixCloud-Play/releases/download/v0.9/MixCloud-Play.app.zip)
## [Download for Mac](https://github.com/uffou/MixCloud-Play/releases/download/v0.9.1/MixCloud-Play.app.zip)

![screenshot](https://raw.githubusercontent.com/uffou/MixCloud-Play/master/Screenshot.png)

Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "MixCloud-Play",
"version": "0.0.1",
"version": "0.9.1",
"description": "The desktop client MixCloud",
"author": {
"name": "Codemotion Ltd."
Expand All @@ -17,7 +17,7 @@
"build"
],
"appId": "com.codemotionapps.mixcloud",
"copyright": "Copyright © 2018 Codemotion Ltd.",
"copyright": "Copyright © 2019 Codemotion Ltd.",
"productName": "MixCloud Play",
"mac": {
"icon": "MixCloud.icns",
Expand Down Expand Up @@ -48,6 +48,7 @@
"webpack-cli": "^3.1.0"
},
"dependencies": {
"@codemotion/electron-google-analytics": "^0.0.1",
"electron-context-menu": "0.10.0",
"electron-store": "2.0.0"
}
Expand Down
10 changes: 0 additions & 10 deletions src/@enum/SoundValues.js

This file was deleted.

8 changes: 0 additions & 8 deletions src/@util/configStoreDefaults.js

This file was deleted.

3 changes: 2 additions & 1 deletion src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
<div id="dragger"></div>
<webview
id="webview"
allowpopups
disablewebsecurity
preload="./preload.js"
src="https://www.mixcloud.com"
webpreferences="experimentalFeatures"
webpreferences="nativeWindowOpen=true;experimentalFeatures"
></webview>
<script src="renderer.js"></script>
</body>
Expand Down
26 changes: 16 additions & 10 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@ const {
Menu,
Notification,
Tray,
shell,
} = require(`electron`);
const contextMenu = require('electron-context-menu');
const ConfigStore = require(`electron-store`);

const SoundValues = require(`./@enum/SoundValues`);

const configStoreDefaults = require(`./@util/configStoreDefaults`);

const menuTemplate = require(`./menu`);

let preferencesWindow;
Expand Down Expand Up @@ -65,6 +62,15 @@ function closeHandler(event){
mainWindow.hide();
}

app.on('web-contents-created', (event, contents) => {
if (contents.getType() === 'webview') {
contents.on('new-window', (event, url) => {
shell.openExternal(url)
event.preventDefault()
})
}
})

app.on('ready', () => {
createTray();
Menu.setApplicationMenu(menu);
Expand Down Expand Up @@ -136,7 +142,7 @@ app.on('before-quit', () => {
mainWindow && mainWindow.removeListener('close', closeHandler);
});

const configStore = new ConfigStore({defaults: configStoreDefaults});
const configStore = new ConfigStore();
ipcMain.on('notification', (_event, notificationIndex, subtitle) => {
if(mainWindow.isFocused()) return;

Expand All @@ -145,7 +151,7 @@ ipcMain.on('notification', (_event, notificationIndex, subtitle) => {
const notification = new Notification({
title: 'MixCloud Play',
subtitle,
silent: configStore.get('soundValue') !== SoundValues.OPERATING_SYSTEM
silent: true
});
notification.on('click', () => {
mainWindow.webContents.send('notificationClicked', notificationIndex);
Expand All @@ -164,7 +170,7 @@ ipcMain.on('handlePause', (_,track) => {
const notification = new Notification({
title: 'Mix Paused',
subtitle: track,
silent: configStore.get('soundValue') !== SoundValues.OPERATING_SYSTEM
silent: true
});
notification.on('click', () => {
mainWindow.webContents.send('notificationClicked', notificationIndex);
Expand All @@ -188,7 +194,7 @@ ipcMain.on('handlePlay', (_,track) => {
const notification = new Notification({
title: 'Playing...',
subtitle: track,
silent: configStore.get('soundValue') !== SoundValues.OPERATING_SYSTEM
silent: true
});
notification.on('click', () => {
mainWindow.webContents.send('notificationClicked', notificationIndex);
Expand All @@ -200,6 +206,6 @@ ipcMain.on('handlePlay', (_,track) => {
}, 7000);
});

ipcMain.on('updatedSound', () => {
mainWindow.webContents.send('updatedSound');
ipcMain.on('updatedPreferences', () => {
mainWindow.webContents.send('updatedPreferences');
})
8 changes: 0 additions & 8 deletions src/preferences/Deferred.js

This file was deleted.

153 changes: 4 additions & 149 deletions src/preferences/Main.js
Original file line number Diff line number Diff line change
@@ -1,168 +1,23 @@
const fs = window.require('fs');
const path = window.require('path');

const { remote } = window.require(`electron`);

const PropTypes = require(`prop-types`);
const { observer } = require(`mobx-react`);
const React = require(`react`);

const SoundValues = require(`../@enum/SoundValues`);

const noop = require(`../@util/noop`);

const Deferred = require(`./Deferred`);

const { dialog, getCurrentWindow, app } = remote;

function unsupportedFileTypeErrorDialog(){
dialog.showMessageBox(getCurrentWindow(), {
type: "error",
message: "The selected file cannot be used.",
title: "Unsupported file type"
})
}

const openFileDialogOptions = {
properties: ["openFile"]
}

const longAudioFileDialogOptions = {
type: "question",
buttons: ['Cancel', 'OK'],
title: "Long audio file",
message: "This audio file is longer than 5 seconds. Are you sure you want to use this for a notification sound?"
};
const settingsStore = require(`./SettingsStore`);

@observer class Main extends React.Component {
static propTypes = {
settings: PropTypes.shape({
soundValue: PropTypes.oneOf(Object.values(SoundValues)).isRequired,
soundFilename: PropTypes.string.isRequired,
lastSoundFileId: PropTypes.number.isRequired,
setSound: PropTypes.func.isRequired,
setLastSoundFileId: PropTypes.func.isRequired
}).isRequired
}

fileInputRef = React.createRef();

handleSoundChange = ::this.handleSoundChange;
handleSoundChange({target}){
const { settings } = this.props;
const { value } = target;
if(value !== SoundValues.CUSTOM){
settings.setSound(value);

return;
}

dialog.showOpenDialog(getCurrentWindow(), openFileDialogOptions, ([file] = []) => {
if(!file) return;

const audio = new Audio(file);
const loadedDataDefferer = new Deferred();
const longAudioAnswerDeferrer = new Deferred();

Promise.all([
loadedDataDefferer.promise,
longAudioAnswerDeferrer.promise
]).then(() => {
const { base: filename, ext } = path.parse(file);
const appDataPath = app.getPath('userData');
const nextFileId = settings.lastSoundFileId + 1;
settings.setLastSoundFileId(nextFileId);
const destinationFolder = path.join(appDataPath, `soundFiles`);
const copyFile = () => {
const destinationFilename = `${nextFileId}${ext}`;
fs.copyFile(file, path.join(destinationFolder, destinationFilename), (error) => {
if(error){
dialog.showMessageBox(getCurrentWindow(), {
type: "error",
message: "The selected file cannot be used.",
title: "Unsupported file type"
})

return;
}

fs.readdir(destinationFolder, (error, files) => {
if(error) return;

for(const file of files){
if(file === destinationFilename) continue;
fs.unlink(path.join(destinationFolder, file), noop);
}
})

settings.setSound(SoundValues.FILE, filename, ext)
})
}
fs.exists(destinationFolder, (exists) => {
if(exists){
copyFile();
}else{
fs.mkdir(destinationFolder, copyFile)
}
})
}).catch(noop)

const handleLoadedData = () => {
if(audio.error){
unsupportedFileTypeErrorDialog();
loadedDataDefferer.reject();
return;
}

loadedDataDefferer.resolve();
}

audio.addEventListener('loadedmetadata', () => {
// TODO: audio.canPlayType()
if(audio.error){
unsupportedFileTypeErrorDialog();

audio.removeEventListener('loadeddata', handleLoadedData);
loadedDataDefferer.reject();
return;
}

if(audio.duration > 5){
dialog.showMessageBox(getCurrentWindow(), longAudioFileDialogOptions, (response) => {
if(response === 1){
longAudioAnswerDeferrer.resolve();
}else{
longAudioAnswerDeferrer.reject();
}
})
}else{
longAudioAnswerDeferrer.resolve();
}
})

audio.addEventListener('loadeddata', handleLoadedData);

audio.addEventListener('error', () => {
unsupportedFileTypeErrorDialog();
loadedDataDefferer.reject();
longAudioAnswerDeferrer.reject();
})
});
}

render(){
const { soundValue, soundFilename } = this.props.settings;
return <table>
<tbody>
<tr>
<td>Notification sound:</td>
<td>
<select value={soundValue} onChange={this.handleSoundChange}>
<option value={SoundValues.HELP_SCOUT}>Default Help Scout sound</option>
<option value={SoundValues.OPERATING_SYSTEM}>Default OS sound</option>
{soundFilename && <option value={SoundValues.FILE}>{soundFilename}</option>}
<option value={SoundValues.CUSTOM}>Custom...</option>
<option value={SoundValues.NO_SOUND}>No sound</option>
<select value={soundValue} onChange={settingsStore.onChangeShowTitle}>
<option value={'show'}>Show song title in menu bar</option>
<option value={'hide'}>Don't show song title in menu bar</option>
</select>
</td>
</tr>
Expand Down
34 changes: 0 additions & 34 deletions src/preferences/Settings.js

This file was deleted.

15 changes: 15 additions & 0 deletions src/preferences/SettingsStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { ipcRenderer } = window.require(`electron`);

const { observable, action } = require('mobx');

class SettingsStore {
@observable showTitle;

@action onChangeShowTitle({target}){
this.showTitle = target.value;
ipcRenderer.send('updatedPreferences',this)
}
}

const settingsStore = new SettingsStore()
module.exports = settingsStore
9 changes: 1 addition & 8 deletions src/preferences/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,11 @@ const React = require('react');
const ReactDOM = require('react-dom');

const Main = require('./Main');
const Settings = require(`./Settings`);

const configStore = new ConfigStore({
defaults: require(`../@util/configStoreDefaults`)
});

const settings = new Settings(configStore)

ipcRenderer.on('reload', () => {
location.reload();
})

const root = document.getElementById('root');

ReactDOM.render(<Main settings={settings} />, root);
ReactDOM.render(<Main />, root);
Loading

0 comments on commit f23cd85

Please sign in to comment.