From e35ee2500df50914d1a58a1d48a29767e50d78c5 Mon Sep 17 00:00:00 2001 From: Guillaume Chau Date: Sun, 10 Jun 2018 15:03:39 +0200 Subject: [PATCH] feat(ui): PluginApi: notify --- docs/dev-guide/ui-plugin-dev.md | 17 ++++++++++ packages/@vue/cli-service/ui.js | 32 +++++++++++++++++++ .../cli-ui/src/graphql-api/api/PluginApi.js | 20 ++++++++++++ .../@vue/cli-ui/src/graphql-api/api/notify.js | 11 +++++++ .../src/graphql-api/connectors/plugins.js | 22 ++++++------- .../src/graphql-api/connectors/projects.js | 6 ++-- .../src/graphql-api/connectors/tasks.js | 11 +++---- .../src/graphql-api/utils/notification.js | 15 +++++++++ 8 files changed, 114 insertions(+), 20 deletions(-) create mode 100644 packages/@vue/cli-ui/src/graphql-api/api/notify.js create mode 100644 packages/@vue/cli-ui/src/graphql-api/utils/notification.js diff --git a/docs/dev-guide/ui-plugin-dev.md b/docs/dev-guide/ui-plugin-dev.md index e2f52bd93..e1d829912 100644 --- a/docs/dev-guide/ui-plugin-dev.md +++ b/docs/dev-guide/ui-plugin-dev.md @@ -966,6 +966,23 @@ api.db.get('posts') const { storageGet, storageSet } = api.namespace('my-plugin.') ``` +### Notification + +You can display notifications using the user OS notification system: + +```js +api.notify({ + title: 'Some title', + message: 'Some message', + icon: 'path-to-icon.png' +}) +``` + +There are some builtin icons: + +- `'done'` +- `'error'` + ### Localization You can put locale files compatible with [vue-i18n](https://github.com/kazupon/vue-i18n) in a `locales` folder at the root of your plugin. They will be automatically loaded into the client when the project is opened. You can then use `$t` to translate strings in your components and other vue-i18n helpers. Also, the strings used in the UI API (like `describeTask`) will go through vue-i18n as well to you can localize them. diff --git a/packages/@vue/cli-service/ui.js b/packages/@vue/cli-service/ui.js index b447f57f9..48d8ec243 100644 --- a/packages/@vue/cli-service/ui.js +++ b/packages/@vue/cli-service/ui.js @@ -1,6 +1,9 @@ module.exports = api => { const { setSharedData, removeSharedData } = api.namespace('webpack-dashboard-') + let firstRun = true + let hadFailed = false + function resetSharedData (key) { setSharedData(`${key}-status`, null) setSharedData(`${key}-progress`, 0) @@ -15,6 +18,33 @@ module.exports = api => { const type = message.webpackDashboardData.type for (const data of message.webpackDashboardData.value) { setSharedData(`${type}-${data.type}`, data.value) + + if (type === 'serve' && data.type === 'status') { + if (data.value === 'Failed') { + api.notify({ + title: 'Build failed', + message: 'The build has errors.', + icon: 'error' + }) + hadFailed = true + } else if (data.value === 'Success') { + if (hadFailed) { + api.notify({ + title: 'Build fixed', + message: 'The build succeeded.', + icon: 'done' + }) + hadFailed = false + } else if (firstRun) { + api.notify({ + title: 'App ready', + message: 'The build succeeded.', + icon: 'done' + }) + firstRun = false + } + } + } } } } @@ -107,6 +137,8 @@ module.exports = api => { // Data resetSharedData('serve') removeSharedData('serve-url') + firstRun = true + hadFailed = false }, onRun: () => { api.ipcOn(onWebpackMessage) diff --git a/packages/@vue/cli-ui/src/graphql-api/api/PluginApi.js b/packages/@vue/cli-ui/src/graphql-api/api/PluginApi.js index d1360b93a..eb5e26710 100644 --- a/packages/@vue/cli-ui/src/graphql-api/api/PluginApi.js +++ b/packages/@vue/cli-ui/src/graphql-api/api/PluginApi.js @@ -4,11 +4,13 @@ const sharedData = require('../connectors/shared-data') const views = require('../connectors/views') // Utils const ipc = require('../utils/ipc') +const { notify } = require('../utils/notification') // Validators const { validateConfiguration } = require('./configuration') const { validateDescribeTask, validateAddTask } = require('./task') const { validateClientAddon } = require('./client-addon') const { validateView, validateBadge } = require('./view') +const { validateNotify } = require('./notify') class PluginApi { constructor (context) { @@ -264,6 +266,24 @@ class PluginApi { return this.context.db } + /** + * Display a notification in the user OS + * @param {object} options Notification options + */ + notify (options) { + try { + validateNotify(options) + notify(options) + } catch (e) { + logs.add({ + type: 'error', + tag: 'PluginApi', + message: `(${this.pluginId || 'unknown plugin'}) 'notify' options are invalid\n${e.message}` + }, this.context) + console.error(new Error(`Invalid options: ${e.message}`)) + } + } + /* Namespaced */ /** diff --git a/packages/@vue/cli-ui/src/graphql-api/api/notify.js b/packages/@vue/cli-ui/src/graphql-api/api/notify.js new file mode 100644 index 000000000..2f5556802 --- /dev/null +++ b/packages/@vue/cli-ui/src/graphql-api/api/notify.js @@ -0,0 +1,11 @@ +const { createSchema, validateSync } = require('@vue/cli-shared-utils') + +const schema = createSchema(joi => ({ + title: joi.string().required(), + message: joi.string().required(), + icon: joi.string() +})) + +exports.validateNotify = (options) => { + validateSync(options, schema) +} diff --git a/packages/@vue/cli-ui/src/graphql-api/connectors/plugins.js b/packages/@vue/cli-ui/src/graphql-api/connectors/plugins.js index de431e7cf..35b359bfa 100644 --- a/packages/@vue/cli-ui/src/graphql-api/connectors/plugins.js +++ b/packages/@vue/cli-ui/src/graphql-api/connectors/plugins.js @@ -16,7 +16,6 @@ const { updatePackage } = require('@vue/cli/lib/util/installDeps') const invoke = require('@vue/cli/lib/invoke') -const notifier = require('node-notifier') // Subs const channels = require('../channels') // Connectors @@ -36,6 +35,7 @@ const { getCommand } = require('../utils/command') const { resolveModuleRoot } = require('../utils/resolve-path') const ipc = require('../utils/ipc') const { log } = require('../utils/logger') +const { notify } = require('../utils/notification') const PROGRESS_ID = 'plugin-installation' const CLI_SERVICE = '@vue/cli-service' @@ -264,10 +264,10 @@ function install (id, context) { await initPrompts(id, context) installationStep = 'config' - notifier.notify({ + notify({ title: `Plugin installed`, message: `Plugin ${id} installed, next step is configuration`, - icon: path.resolve(__dirname, '../../assets/done.png') + icon: 'done' }) return getInstallation(context) @@ -297,10 +297,10 @@ function uninstall (id, context) { currentPluginId = null installationStep = null - notifier.notify({ + notify({ title: `Plugin uninstalled`, message: `Plugin ${id} uninstalled`, - icon: path.resolve(__dirname, '../../assets/done.png') + icon: 'done' }) return getInstallation(context) @@ -329,10 +329,10 @@ function runInvoke (id, context) { runPluginApi(id, context) installationStep = 'diff' - notifier.notify({ + notify({ title: `Plugin invoke sucess`, message: `Plugin ${id} invoked successfully`, - icon: path.resolve(__dirname, '../../assets/done.png') + icon: 'done' }) return getInstallation(context) @@ -377,10 +377,10 @@ function update (id, context, notify = true) { }, context) if (notify) { - notifier.notify({ + notify({ title: `Plugin updated`, message: `Plugin ${id} was successfully updated`, - icon: path.resolve(__dirname, '../../assets/done.png') + icon: 'done' }) } @@ -399,10 +399,10 @@ async function updateAll (context) { } } - notifier.notify({ + notify({ title: `Plugins updated`, message: `${updatedPlugins.length} plugin(s) were successfully updated`, - icon: path.resolve(__dirname, '../../assets/done.png') + icon: 'done' }) return updatedPlugins diff --git a/packages/@vue/cli-ui/src/graphql-api/connectors/projects.js b/packages/@vue/cli-ui/src/graphql-api/connectors/projects.js index 381ea3a17..e1abc46d0 100644 --- a/packages/@vue/cli-ui/src/graphql-api/connectors/projects.js +++ b/packages/@vue/cli-ui/src/graphql-api/connectors/projects.js @@ -7,7 +7,6 @@ const { getFeatures } = require('@vue/cli/lib/util/features') const { defaults } = require('@vue/cli/lib/options') const { toShortPluginId } = require('@vue/cli-shared-utils') const { progress: installProgress } = require('@vue/cli/lib/util/installDeps') -const notifier = require('node-notifier') // Connectors const progress = require('./progress') const cwd = require('./cwd') @@ -19,6 +18,7 @@ const locales = require('./locales') const getContext = require('../context') // Utils const { log } = require('../utils/logger') +const { notify } = require('../utils/notification') const PROGRESS_ID = 'project-create' @@ -309,10 +309,10 @@ async function create (input, context) { await creator.create({ git: true }, preset) removeCreator() - notifier.notify({ + notify({ title: `Project created`, message: `Project ${cwd.get()} created`, - icon: path.resolve(__dirname, '../../assets/done.png') + icon: 'done' }) return importProject({ diff --git a/packages/@vue/cli-ui/src/graphql-api/connectors/tasks.js b/packages/@vue/cli-ui/src/graphql-api/connectors/tasks.js index 5dcd69b09..80a9c328f 100644 --- a/packages/@vue/cli-ui/src/graphql-api/connectors/tasks.js +++ b/packages/@vue/cli-ui/src/graphql-api/connectors/tasks.js @@ -1,7 +1,5 @@ -const path = require('path') const execa = require('execa') const terminate = require('terminate') -const notifier = require('node-notifier') // Subs const channels = require('../channels') // Connectors @@ -14,6 +12,7 @@ const views = require('./views') // Utils const { getCommand } = require('../utils/command') const { log } = require('../utils/logger') +const { notify } = require('../utils/notification') const MAX_LOGS = 2000 const VIEW_ID = 'vue-project-tasks' @@ -329,10 +328,10 @@ async function run (id, context) { message: `Task ${task.id} ended with error code ${code}`, type: 'error' }, context) - notifier.notify({ + notify({ title: `Task error`, message: `Task ${task.id} ended with error code ${code}`, - icon: path.resolve(__dirname, '../../assets/error.png') + icon: 'error' }) } else { updateOne({ @@ -343,10 +342,10 @@ async function run (id, context) { message: `Task ${task.id} completed`, type: 'done' }, context) - notifier.notify({ + notify({ title: `Task completed`, message: `Task ${task.id} completed`, - icon: path.resolve(__dirname, '../../assets/done.png') + icon: 'done' }) } } diff --git a/packages/@vue/cli-ui/src/graphql-api/utils/notification.js b/packages/@vue/cli-ui/src/graphql-api/utils/notification.js new file mode 100644 index 000000000..e217ee623 --- /dev/null +++ b/packages/@vue/cli-ui/src/graphql-api/utils/notification.js @@ -0,0 +1,15 @@ +const path = require('path') +const notifier = require('node-notifier') + +const builtinIcons = { + 'done': path.resolve(__dirname, '../../assets/done.png'), + 'error': path.resolve(__dirname, '../../assets/error.png') +} + +exports.notify = ({ title, message, icon }) => { + notifier.notify({ + title, + message, + icon: builtinIcons[icon] || icon + }) +}