Mailspring/internal_packages/plugins/lib/packages-store.jsx
Ben Gotow a48ddd51f8 refactor(menus): Major prune of keymaps/commands, real conditional menus
Summary:
Keymaps & menus CSON => JSON, remove AtomKeymaps, CommandRegistry use of CSS selectors, use Mousetrap instead

Important Notes:

- The `application:` prefix is reserved for commands which are handled in the application process. Don't use it for other things. You will not receive the events in the window.

- Maintaining dynamic menus seems to come with quite an overhead, because Electron updates the entire menu every time. In the future, we'll need https://github.com/electron/electron/issues/528 to really make things nice. I will be tracking this upstream.

- The format for keyboard shortcuts has changed. `cmd-X` is now `command+shift+x`

Test Plan: Run tests

Reviewers: juan, evan

Reviewed By: evan

Differential Revision: https://phab.nylas.com/D2917
2016-04-24 12:16:25 -05:00

357 lines
11 KiB
JavaScript

import _ from 'underscore';
import Reflux from 'reflux';
import path from 'path';
import fs from 'fs-plus';
import PluginsActions from './plugins-actions';
import {APMWrapper} from 'nylas-exports';
import {ipcRenderer, shell, remote} from 'electron';
const dialog = remote.dialog;
const PackagesStore = Reflux.createStore({
init: function init() {
this._apm = new APMWrapper();
this._globalSearch = "";
this._installedSearch = "";
this._installing = {};
this._featured = {
themes: [],
packages: [],
};
this._newerVersions = [];
this._searchResults = null;
this._refreshFeatured();
this.listenTo(PluginsActions.refreshFeaturedPackages, this._refreshFeatured);
this.listenTo(PluginsActions.refreshInstalledPackages, this._refreshInstalled);
NylasEnv.commands.add(document.body,
'application:create-package',
() => this._onCreatePackage()
);
NylasEnv.commands.add(document.body,
'application:install-package',
() => this._onInstallPackage()
);
this.listenTo(PluginsActions.installNewPackage, this._onInstallPackage);
this.listenTo(PluginsActions.createPackage, this._onCreatePackage);
this.listenTo(PluginsActions.updatePackage, this._onUpdatePackage);
this.listenTo(PluginsActions.setGlobalSearchValue, this._onGlobalSearchChange);
this.listenTo(PluginsActions.setInstalledSearchValue, this._onInstalledSearchChange);
this.listenTo(PluginsActions.showPackage, (pkg) => {
const dir = NylasEnv.packages.resolvePackagePath(pkg.name);
if (dir) shell.showItemInFolder(dir);
});
this.listenTo(PluginsActions.installPackage, (pkg) => {
this._installing[pkg.name] = true;
this.trigger(this);
this._apm.install(pkg, (err) => {
if (err) {
delete(this._installing[pkg.name]);
this._displayMessage("Sorry, an error occurred", err.toString());
} else {
if (NylasEnv.packages.isPackageDisabled(pkg.name)) {
NylasEnv.packages.enablePackage(pkg.name);
}
}
this._onPackagesChanged();
});
});
this.listenTo(PluginsActions.uninstallPackage, (pkg) => {
if (NylasEnv.packages.isPackageLoaded(pkg.name)) {
NylasEnv.packages.disablePackage(pkg.name);
NylasEnv.packages.unloadPackage(pkg.name);
}
this._apm.uninstall(pkg, (err) => {
if (err) this._displayMessage("Sorry, an error occurred", err.toString())
this._onPackagesChanged();
})
});
this.listenTo(PluginsActions.enablePackage, (pkg) => {
if (NylasEnv.packages.isPackageDisabled(pkg.name)) {
NylasEnv.packages.enablePackage(pkg.name);
this._onPackagesChanged();
}
});
this.listenTo(PluginsActions.disablePackage, (pkg) => {
if (!NylasEnv.packages.isPackageDisabled(pkg.name)) {
NylasEnv.packages.disablePackage(pkg.name);
this._onPackagesChanged();
}
});
this._hasPrepared = false;
},
// Getters
installed: function installed() {
this._prepareIfFresh();
return this._addPackageStates(this._filter(this._installed, this._installedSearch));
},
installedSearchValue: function installedSearchValue() {
return this._installedSearch;
},
featured: function featured() {
this._prepareIfFresh();
return this._addPackageStates(this._featured);
},
searchResults: function searchResults() {
return this._addPackageStates(this._searchResults);
},
globalSearchValue: function globalSearchValue() {
return this._globalSearch;
},
// Action Handlers
_prepareIfFresh: function _prepareIfFresh() {
if (this._hasPrepared) return;
NylasEnv.packages.onDidActivatePackage(() => this._onPackagesChangedDebounced());
NylasEnv.packages.onDidDeactivatePackage(() => this._onPackagesChangedDebounced());
NylasEnv.packages.onDidLoadPackage(() => this._onPackagesChangedDebounced());
NylasEnv.packages.onDidUnloadPackage(() => this._onPackagesChangedDebounced());
this._onPackagesChanged();
this._hasPrepared = true;
},
_filter: function _filter(hash, search) {
const result = {}
const query = search.toLowerCase();
if (hash) {
Object.keys(hash).forEach((key) => {
result[key] = _.filter(hash[key], (p) =>
query.length === 0 || p.name.toLowerCase().indexOf(query) !== -1
);
});
}
return result;
},
_refreshFeatured: function _refreshFeatured() {
this._apm.getFeatured({themes: false})
.then((results) => {
this._featured.packages = results;
this.trigger();
})
.catch(() => {
// We may be offline
});
this._apm.getFeatured({themes: true})
.then((results) => {
this._featured.themes = results;
this.trigger();
})
.catch(() => {
// We may be offline
});
},
_refreshInstalled: function _refreshInstalled() {
this._onPackagesChanged();
},
_refreshSearch: function _refreshSearch() {
if (!this._globalSearch || this._globalSearch.length <= 0) return;
this._apm.search(this._globalSearch)
.then((results) => {
this._searchResults = {
packages: results.filter(({theme}) => !theme),
themes: results.filter(({theme}) => theme),
}
this.trigger();
})
.catch(() => {
// We may be offline
});
},
_refreshSearchThrottled: function _refreshSearchThrottled() {
_.debounce(this._refreshSearch, 400)
},
_onPackagesChanged: function _onPackagesChanged() {
this._apm.getInstalled()
.then((packages) => {
for (const category of ['dev', 'user']) {
packages[category].forEach((pkg) => {
pkg.category = category;
delete(this._installing[pkg.name]);
});
}
const available = NylasEnv.packages.getAvailablePackageMetadata();
const examples = available.filter(({isOptional}) => isOptional);
packages.example = examples.map((pkg) =>
_.extend({}, pkg, {installed: true, category: 'example'})
);
this._installed = packages;
this.trigger();
});
},
_onPackagesChangedDebounced: function _onPackagesChangedDebounced() {
_.debounce(this._onPackagesChanged, 200);
},
_onInstalledSearchChange: function _onInstalledSearchChange(val) {
this._installedSearch = val;
this.trigger();
},
_onUpdatePackage: function _onUpdatePackage(pkg) {
this._apm.update(pkg, pkg.newerVersion);
},
_onInstallPackage: function _onInstallPackage() {
NylasEnv.showOpenDialog({
title: "Choose a Plugin Directory",
properties: ['openDirectory'],
},
(filenames) => {
if (!filenames || filenames.length === 0) return;
NylasEnv.packages.installPackageFromPath(filenames[0], (err) => {
if (err) return;
const packageName = path.basename(filenames[0]);
const msg = `${packageName} has been installed and enabled. No need to
restart! If you don't see the plugin loaded, check the
console for errors.`
this._displayMessage("Plugin installed! 🎉", msg);
});
});
},
_onCreatePackage: function _onCreatePackage() {
if (!NylasEnv.inDevMode()) {
const btn = dialog.showMessageBox({
type: 'warning',
message: "Run with debug flags?",
detail: `To develop plugins, you should run N1 with debug flags.
This gives you better error messages, the debug version of
React, and more. You can disable it at any time from the
Developer menu.`,
buttons: ["OK", "Cancel"],
});
if (btn === 0) {
ipcRenderer.send('command', 'application:toggle-dev');
}
return;
}
const packagesDir = path.join(NylasEnv.getConfigDirPath(), 'dev', 'packages');
fs.makeTreeSync(packagesDir);
NylasEnv.showSaveDialog({
title: "Save New Package",
defaultPath: packagesDir,
properties: ['createDirectory'],
}, (packageDir) => {
if (!packageDir) return;
const packageName = path.basename(packageDir);
if (!packageDir.startsWith(packagesDir)) {
this._displayMessage('Invalid plugin location',
'Sorry, you must create plugins in the packages folder.');
}
if (NylasEnv.packages.resolvePackagePath(packageName)) {
this._displayMessage('Invalid plugin name',
'Sorry, you must give your plugin a unqiue name.');
}
if (packageName.indexOf(' ') !== -1) {
this._displayMessage('Invalid plugin name',
'Sorry, plugin names cannot contain spaces.');
}
fs.mkdir(packageDir, (err) => {
if (err) return this._displayMessage('Could not create plugin', err.toString());
const {resourcePath} = NylasEnv.getLoadSettings();
const packageTemplatePath = path.join(resourcePath, 'static', 'package-template');
const packageJSON = {
name: packageName,
main: "./lib/main",
version: '0.1.0',
repository: {
type: 'git',
url: '',
},
engines: {
nylas: `>=${NylasEnv.getVersion().split('-')[0]}`,
},
windowTypes: {
default: true,
composer: true,
},
description: "Enter a description of your package!",
dependencies: {},
license: "MIT",
};
fs.copySync(packageTemplatePath, packageDir);
fs.writeFileSync(path.join(packageDir, 'package.json'), JSON.stringify(packageJSON, null, 2));
shell.showItemInFolder(packageDir);
_.defer(() => {
NylasEnv.packages.enablePackage(packageDir);
NylasEnv.packages.activatePackage(packageName);
});
});
});
},
_onGlobalSearchChange: function _onGlobalSearchChange(val) {
// Clear previous search results data if this is a new
// search beginning from "".
if (this._globalSearch.length === 0 && val.length > 0) {
this._searchResults = null;
}
this._globalSearch = val;
this._refreshSearchThrottled();
this.trigger();
},
_addPackageStates: function _addPackageStates(pkgs) {
const installedNames = _.flatten(_.values(this._installed)).map((pkg) => pkg.name);
_.flatten(_.values(pkgs)).forEach((pkg) => {
pkg.enabled = !NylasEnv.packages.isPackageDisabled(pkg.name);
pkg.installed = installedNames.indexOf(pkg.name) !== -1;
pkg.installing = this._installing[pkg.name];
pkg.newerVersionAvailable = this._newerVersions[pkg.name];
pkg.newerVersion = this._newerVersions[pkg.name];
});
return pkgs;
},
_displayMessage: function _displayMessage(title, message) {
dialog.showMessageBox({
type: 'warning',
message: title,
detail: message,
buttons: ["OK"],
});
},
});
export default PackagesStore;