diff --git a/api-client.js b/api-client.js index 0246c48..6cac1bc 100644 --- a/api-client.js +++ b/api-client.js @@ -7,19 +7,19 @@ class ApiClient { this.config = new ElectronStore(); this.store = new JsonApiDataStore(); } - + token() { return this.config.get('token'); } - + email() { return this.config.get('email'); } - + logIn(email, password, callback) { - var me = this; + const me = this; this.config.set('email', email); - var req = https.request({ + const req = https.request({ method: 'POST', host: 'api.op1.fun', path: '/v1/api_token', @@ -28,10 +28,12 @@ class ApiClient { 'Accept': 'application/json' } }, function(res) { - var body = ''; - res.on('data', function(d) { body += d; }); + let body = ''; + res.on('data', function(d) { + body += d; + }); res.on('end', function() { - var res = JSON.parse(body); + const res = JSON.parse(body); if (res.api_token) { me.config.set('token', res.api_token); } @@ -39,42 +41,42 @@ class ApiClient { callback(res); }); }); - req.write(JSON.stringify({ email: email, password: password })); + req.write(JSON.stringify({email: email, password: password})); req.end(); } - + enableAppFeatureFlag() { - this._post('feature_flags', { flags: { app: true } }, function() {}); + this._post('feature_flags', {flags: {app: true}}, function() {}); } - + logOut(callback) { this.config.set('email', null); this.config.set('token', null); callback(); } - + isLoggedIn() { return (this.email() && this.token()); } - + getPack(path, id, callback) { - var me = this; + const me = this; this._get(path, function() { - var pack = me.store.find("packs", id); + const pack = me.store.find('packs', id); callback(pack); - }) + }); } - + getPatch(path, id, callback) { - var me = this; + const me = this; this._get(path, function() { - var patch = me.store.find("patches", id); + const patch = me.store.find('patches', id); callback(patch); - }) + }); } - + _get(path, callback) { - var me = this; + const me = this; return https.get({ host: 'api.op1.fun', path: '/v1/' + path, @@ -83,18 +85,19 @@ class ApiClient { 'X-User-Token': this.token() } }, function(res) { - var body = ''; - res.on('data', function(d) { body += d; }); + let body = ''; + res.on('data', function(d) { + body += d; + }); res.on('end', function() { me.store.sync(JSON.parse(body)); callback(); }); }); } - + _post(path, data, callback) { - var me = this; - var req = https.request({ + const req = https.request({ method: 'POST', host: 'api.op1.fun', path: '/v1/' + path, @@ -105,9 +108,13 @@ class ApiClient { 'Accept': 'application/json' } }, function(res) { - var body = ''; - res.on('data', function(d) { body += d; }); - res.on('end', function() { callback(body); }); + let body = ''; + res.on('data', function(d) { + body += d; + }); + res.on('end', function() { + callback(body); + }); }); console.log(data); console.log(JSON.stringify(data)); diff --git a/front-end.js b/front-end.js index 4485070..65ab7f3 100644 --- a/front-end.js +++ b/front-end.js @@ -1,46 +1,64 @@ -const { ipcRenderer, webFrame } = require('electron'); +const {ipcRenderer, webFrame} = require('electron'); const ApiClient = require('./api-client'); const groupBy = require('./lib/group-by'); const api = new ApiClient(); const version = require('./package.json').version; -var app; - // disable zoom webFrame.setVisualZoomLevelLimits(1, 1); -var categoryFilter = function (patches, category) { - var filtered = patches.filter((p) => { return p.category === category }); - return filtered.sort(function (a, b) { +const categoryFilter = function(patches, category) { + const filtered = patches.filter((p) => { + return p.category === category; + }); + return filtered.sort(function(a, b) { // "/000" makes root level patches sort to the top - var _a = (a.packDir || "/000") + a.name.toLowerCase(); - var _b = (b.packDir || "/000") + b.name.toLowerCase(); - if (_a < _b) { return -1 } else if (_a > _b) { return 1 } + const _a = (a.packDir || '/000') + a.name.toLowerCase(); + const _b = (b.packDir || '/000') + b.name.toLowerCase(); + if (_a < _b) { + return -1; + } else if (_a > _b) { + return 1; + } return 0; }); -} +}; -app = new Vue({ +const app = new Vue({ el: '#app', - data: { - patches: [], - downloading: false, - currentView: api.isLoggedIn() ? 'browser' : 'login', - currentListId: 'synth', - loginError: '', - isLoggedIn: api.isLoggedIn(), - connected: false + data: function() { + return { + patches: [], + downloading: false, + currentView: api.isLoggedIn() ? 'browser' : 'login', + currentListId: 'synth', + loginError: '', + isLoggedIn: api.isLoggedIn(), + connected: false + }; }, methods: { - goToView: function (e) { this.currentView = e }, - showList: function (e) { this.currentListId = e }, - setLoginError: function (error) { this.loginError = error; }, - setLoggedInFalse: function () { this.isLoggedIn = false; }, - setLoggedInTrue: function () { this.isLoggedIn = true; }, - mountOP1: function () { ipcRenderer.send('mount-op1'); }, - showPopupMenu: function () { - ipcRenderer.send('show-popup-menu', { view: this.currentView }); + goToView: function(e) { + this.currentView = e; + }, + showList: function(e) { + this.currentListId = e; + }, + setLoginError: function(error) { + this.loginError = error; + }, + setLoggedInFalse: function() { + this.isLoggedIn = false; + }, + setLoggedInTrue: function() { + this.isLoggedIn = true; + }, + mountOP1: function() { + ipcRenderer.send('mount-op1'); }, + showPopupMenu: function() { + ipcRenderer.send('show-popup-menu', {view: this.currentView}); + } }, components: { @@ -49,16 +67,16 @@ app = new Vue({ // login: { template: '#login', - data: function () { + data: function() { return { email: api.email(), version: version, password: '' - } + }; }, props: ['loginError', 'isLoggedIn'], methods: { - logIn: function (e) { + logIn: function(e) { api.logIn(this.email, this.password, (res) => { if (res.error) { this.$emit('error', res.error); @@ -69,7 +87,7 @@ app = new Vue({ } }); }, - logOut: function () { + logOut: function() { api.logOut(() => { this.$emit('log-out'); }); @@ -84,38 +102,38 @@ app = new Vue({ template: '#browser', props: ['patches', 'downloading', 'currentListId', 'connected'], computed: { - filteredPatches: function () { + filteredPatches: function() { return categoryFilter(this.patches, this.currentListId); - }, + } }, components: { sideNav: { template: '#side-nav', props: ['patches', 'currentListId'], - data: function () { + data: function() { return { - limits: { drum: 42, synth: 100, sampler: 42 }, - } + limits: {drum: 42, synth: 100, sampler: 42} + }; }, computed: { - navItems: function () { - var sub = (cat) => { - return categoryFilter(this.patches, cat).length + " of " + this.limits[cat]; - } + navItems: function() { + const sub = (cat) => { + return categoryFilter(this.patches, cat).length + ' of ' + this.limits[cat]; + }; return [ - { id: 'synth', title: 'Synth', subtitle: sub('synth') }, - { id: 'drum', title: 'Drum', subtitle: sub('drum') }, - { id: 'sampler', title: 'Sampler', subtitle: sub('sampler') }, + {id: 'synth', title: 'Synth', subtitle: sub('synth')}, + {id: 'drum', title: 'Drum', subtitle: sub('drum')}, + {id: 'sampler', title: 'Sampler', subtitle: sub('sampler')} // { id: 'backups', title: 'Backups', subtitle: 'ok' }, - ] + ]; } } }, contentArea: { - template: "", + template: '', props: ['currentListId', 'patches', 'id'], computed: { - currentComponent: function () { + currentComponent: function() { return (this.currentListId === 'backups') ? 'backups' : 'patch-list'; } }, @@ -125,15 +143,17 @@ app = new Vue({ template: '#patch-list', props: ['patches', 'id'], computed: { - packs: function () { return groupBy(this.patches, 'packName') } + packs: function() { + return groupBy(this.patches, 'packName'); + } }, methods: { - showInFinder: function (e) { + showInFinder: function(e) { if (e) { - ipcRenderer.send('show-in-finder', e.target.getAttribute("href")); + ipcRenderer.send('show-in-finder', e.target.getAttribute('href')); } - }, - }, + } + } }, backups: { template: '#backups' @@ -172,9 +192,9 @@ ipcRenderer.on('show-login', (event, data) => { ipcRenderer.on('start-download', (event, message) => { if (message['pack']) { - app.downloading = "Downloading Pack: " + message.pack.name; + app.downloading = 'Downloading Pack: ' + message.pack.name; } else if (message['patch']) { - app.downloading = "Downloading Patch: " + message.patch.name; + app.downloading = 'Downloading Patch: ' + message.patch.name; } }); diff --git a/index.js b/index.js index 3f2047e..e0c9956 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ -const { menubar } = require('menubar'); -const { autoUpdater, dialog, ipcMain, shell, Menu } = require('electron'); -const { download } = require('electron-dl'); +const {menubar} = require('menubar'); +const {autoUpdater, dialog, ipcMain, shell, Menu} = require('electron'); +const {download} = require('electron-dl'); const chokidar = require('chokidar'); const drivelist = require('drivelist'); const Url = require('url'); @@ -13,10 +13,12 @@ const isDev = require('electron-is-dev'); if (!isDev) { const version = require('./package.json').version; - const platform = os.platform() + "_" + os.arch(); + const platform = os.platform() + '_' + os.arch(); autoUpdater.setFeedURL('https://nuts.op1.fun/update/' + platform + '/' + version); autoUpdater.checkForUpdates(); - setInterval(() => { autoUpdater.checkForUpdates() }, 300000); + setInterval(() => { + autoUpdater.checkForUpdates(); + }, 300000); autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => { const dialogOpts = { @@ -25,27 +27,24 @@ if (!isDev) { title: 'Application Update', message: releaseName, detail: 'A new version of op1.fun.app has been downloaded. Restart the application to apply the update.' - } + }; dialog.showMessageBox(dialogOpts, (response) => { if (response === 0) autoUpdater.quitAndInstall(); }); }); - autoUpdater.on('error', message => { + autoUpdater.on('error', (message) => { console.error('There was a problem updating the application'); console.error(message); }); } -var email, - token, - watcher, - urlToOpen, - mountpoint, - patches = [], - mounted = false, - settingUpWatcher = false; +let watcher; +let mountpoint; +let patches = []; +let mounted = false; +let settingUpWatcher = false; // mountpoint = "/Users/jordan/Documents/OP-1/fakeop1"; @@ -62,18 +61,18 @@ const mb = menubar({ }); mb.on('ready', function ready() { - ensureConnected().then(function (message) { + ensureConnected().then(function(message) { mb.showWindow(); - }, function (reason) { + }, function(reason) { mb.showWindow(); }); }); -mb.app.on('open-url', function (e, urlStr) { +mb.app.on('open-url', function(e, urlStr) { e.preventDefault(); if (ensureLoggedIn()) { - ensureConnected().then(function () { - var { id, path, type } = parseUrl(urlStr); + ensureConnected().then(function() { + const {id, path, type} = parseUrl(urlStr); if (type === 'packs' && id) { api.getPack(path, id, loadPack); } else if (type === 'patches' && id) { @@ -89,7 +88,7 @@ mb.app.on('open-url', function (e, urlStr) { mb.on('after-show', function show() { if (ensureLoggedIn()) { - ensureConnected().catch(function (reason) { + ensureConnected().catch(function(reason) { // noop }); }; @@ -99,18 +98,18 @@ ipcMain.on('show-popup-menu', (event, args) => { let menu; if (args.view === 'login') { menu = Menu.buildFromTemplate([ - { role: 'quit' } + {role: 'quit'} ]); } else { menu = Menu.buildFromTemplate([ - { label: 'Account Settings', click: () => { + {label: 'Account Settings', click: () => { mb.window.webContents.send('go-to-view', 'login'); }}, - { type: 'separator' }, - { role: 'quit' } + {type: 'separator'}, + {role: 'quit'} ]); } - + menu.popup(mb.window); }); @@ -119,7 +118,7 @@ ipcMain.on('show-in-finder', (event, arg) => { }); ipcMain.on('mount-op1', (event, arg) => { - ensureConnected().catch(function (reason) { + ensureConnected().catch(function(reason) { // noop }); }); @@ -130,7 +129,7 @@ ipcMain.on('show-config-menu', (event, arg) => { function loadPatch(patch, packDir) { mb.showWindow(); - var dir = [mountpoint]; + const dir = [mountpoint]; if (patch['patch-type'] === 'drum') { dir.push('drum'); } else { @@ -139,35 +138,37 @@ function loadPatch(patch, packDir) { if (packDir) { dir.push(packDir); } else { - mb.window.webContents.send('start-download', { patch: patch }); + mb.window.webContents.send('start-download', {patch: patch}); } - result = download(mb.window, patch.links.file, { directory: dir.join("/") }); + result = download(mb.window, patch.links.file, {directory: dir.join('/')}); if (!packDir) { - result = result.then(function () { - mb.window.webContents.send('finish-download', { patch: patch }); + result = result.then(function() { + mb.window.webContents.send('finish-download', {patch: patch}); }); } return result; } function loadPack(pack) { - mb.window.webContents.send('start-download', { pack: pack }); - var result = Promise.resolve(); - pack.patches.forEach(function (patch) { + mb.window.webContents.send('start-download', {pack: pack}); + let result = Promise.resolve(); + pack.patches.forEach(function(patch) { result = result.then(() => loadPatch(patch, pack.id)); }); - result = result.then(function () { - mb.window.webContents.send('finish-download', { pack: pack }); - }) + result = result.then(function() { + mb.window.webContents.send('finish-download', {pack: pack}); + }); return result; } function parseUrl(urlStr) { - var parsed = Url.parse(urlStr); - var path = parsed.pathname - while (path.charAt(0) === "/") { path = path.slice(1); } - var parts = path.split("/"); - return { type: parts[2], id: parts[3], path: path }; + const parsed = Url.parse(urlStr); + let path = parsed.pathname; + while (path.charAt(0) === '/') { + path = path.slice(1); + } + const parts = path.split('/'); + return {type: parts[2], id: parts[3], path: path}; } function ensureLoggedIn() { @@ -186,10 +187,10 @@ function ensureConnected() { return Promise.resolve(true); } else if (!settingUpWatcher) { settingUpWatcher = true; - return watchOP1().then(function () { + return watchOP1().then(function() { mb.window.webContents.send('op1-connected', true); settingUpWatcher = false; - }, function (error) { + }, function(error) { mb.window.webContents.send('op1-connected', false); settingUpWatcher = false; return Promise.reject(); @@ -229,7 +230,7 @@ async function watchOP1() { watcher = chokidar.watch(mountpoint, { ignored: /(^|[\/\\])\../, - awaitWriteFinish: true, + awaitWriteFinish: true }).on('all', (event, path) => { if (mountpoint) { const relPath = path.slice(mountpoint.length); diff --git a/op1-patch.js b/op1-patch.js index 87e07a5..85ff1dc 100644 --- a/op1-patch.js +++ b/op1-patch.js @@ -1,11 +1,13 @@ const fs = require('fs'); -const wordAlign = function(v) { return v + v % 2; } +const wordAlign = function(v) { + return v + v % 2; +}; class OP1Patch { constructor(options) { this.path = options.path; this.relPath = options.relPath; - this._pathParts = this.relPath.split("/"); + this._pathParts = this.relPath.split('/'); this.name = this._pathParts[this._pathParts.length-1]; this.metadata = null; this.category = null; @@ -15,29 +17,29 @@ class OP1Patch { this._setPackName(); this._setPackDir(); } - + _parse() { - var bytes = fs.readFileSync(this.path); + const bytes = fs.readFileSync(this.path); if (String.fromCharCode.apply(null, bytes.subarray(0, 4)) !== 'FORM') { - throw(new Error("FORM header not found")); + throw (new Error('FORM header not found')); } - var dv = new DataView(bytes.buffer, 0, bytes.byteLength); - var length = dv.getUint32(4, false); + const dv = new DataView(bytes.buffer, 0, bytes.byteLength); + const length = dv.getUint32(4, false); if (bytes.byteLength < 8+length) { - console.log("invalid length for", this.path); + console.log('invalid length for', this.path); // throw(new Error("invalid data length")); } - var header = String.fromCharCode.apply(null, bytes.subarray(8, 12)); + const header = String.fromCharCode.apply(null, bytes.subarray(8, 12)); if (header !== 'AIFC' && header !== 'AIFF') { - throw(new Error("AIFC/AIFF header not found")); + throw (new Error('AIFC/AIFF header not found')); } - for (var pos = 12; pos < length; pos += 8 + wordAlign(dv.getUint32(pos + 4, false))) { - var chunkName = String.fromCharCode.apply(null, bytes.subarray(pos, pos + 4)); + for (let pos = 12; pos < length; pos += 8 + wordAlign(dv.getUint32(pos + 4, false))) { + const chunkName = String.fromCharCode.apply(null, bytes.subarray(pos, pos + 4)); if (chunkName === 'APPL') { - var signature = String.fromCharCode.apply(null, bytes.subarray(pos+8, pos+12)); - var appl = String.fromCharCode.apply(null, bytes.subarray( - pos + 12, - pos + 8 + dv.getUint32(pos + 4, false) + const signature = String.fromCharCode.apply(null, bytes.subarray(pos+8, pos+12)); + let appl = String.fromCharCode.apply(null, bytes.subarray( + pos + 12, + pos + 8 + dv.getUint32(pos + 4, false) )); if (signature === 'op-1') { appl = appl.replace(/\0/g, ''); // json is NUL padded @@ -48,26 +50,26 @@ class OP1Patch { } } } - + _setCategory() { - if (this.metadata.type === "drum") { - this.category = "drum"; - } else if (this.metadata.type === "sampler") { - this.category = "sampler"; + if (this.metadata.type === 'drum') { + this.category = 'drum'; + } else if (this.metadata.type === 'sampler') { + this.category = 'sampler'; } else if (this.metadata.type) { - this.category = "synth"; + this.category = 'synth'; } } - + _setPackName() { if (this._pathParts.length > 3) { - this.packName = this._pathParts.slice(2,this._pathParts.length-1).join("/"); + this.packName = this._pathParts.slice(2, this._pathParts.length-1).join('/'); } } - + _setPackDir() { if (this._pathParts.length > 3) { - this.packDir = this._pathParts.slice(0,this._pathParts.length-1).join("/"); + this.packDir = this._pathParts.slice(0, this._pathParts.length-1).join('/'); } } }