Skip to content

Commit e35ee25

Browse files
committed
feat(ui): PluginApi: notify
1 parent dbef5e9 commit e35ee25

File tree

8 files changed

+114
-20
lines changed

8 files changed

+114
-20
lines changed

docs/dev-guide/ui-plugin-dev.md

+17
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,23 @@ api.db.get('posts')
966966
const { storageGet, storageSet } = api.namespace('my-plugin.')
967967
```
968968

969+
### Notification
970+
971+
You can display notifications using the user OS notification system:
972+
973+
```js
974+
api.notify({
975+
title: 'Some title',
976+
message: 'Some message',
977+
icon: 'path-to-icon.png'
978+
})
979+
```
980+
981+
There are some builtin icons:
982+
983+
- `'done'`
984+
- `'error'`
985+
969986
### Localization
970987

971988
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.

packages/@vue/cli-service/ui.js

+32
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
module.exports = api => {
22
const { setSharedData, removeSharedData } = api.namespace('webpack-dashboard-')
33

4+
let firstRun = true
5+
let hadFailed = false
6+
47
function resetSharedData (key) {
58
setSharedData(`${key}-status`, null)
69
setSharedData(`${key}-progress`, 0)
@@ -15,6 +18,33 @@ module.exports = api => {
1518
const type = message.webpackDashboardData.type
1619
for (const data of message.webpackDashboardData.value) {
1720
setSharedData(`${type}-${data.type}`, data.value)
21+
22+
if (type === 'serve' && data.type === 'status') {
23+
if (data.value === 'Failed') {
24+
api.notify({
25+
title: 'Build failed',
26+
message: 'The build has errors.',
27+
icon: 'error'
28+
})
29+
hadFailed = true
30+
} else if (data.value === 'Success') {
31+
if (hadFailed) {
32+
api.notify({
33+
title: 'Build fixed',
34+
message: 'The build succeeded.',
35+
icon: 'done'
36+
})
37+
hadFailed = false
38+
} else if (firstRun) {
39+
api.notify({
40+
title: 'App ready',
41+
message: 'The build succeeded.',
42+
icon: 'done'
43+
})
44+
firstRun = false
45+
}
46+
}
47+
}
1848
}
1949
}
2050
}
@@ -107,6 +137,8 @@ module.exports = api => {
107137
// Data
108138
resetSharedData('serve')
109139
removeSharedData('serve-url')
140+
firstRun = true
141+
hadFailed = false
110142
},
111143
onRun: () => {
112144
api.ipcOn(onWebpackMessage)

packages/@vue/cli-ui/src/graphql-api/api/PluginApi.js

+20
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ const sharedData = require('../connectors/shared-data')
44
const views = require('../connectors/views')
55
// Utils
66
const ipc = require('../utils/ipc')
7+
const { notify } = require('../utils/notification')
78
// Validators
89
const { validateConfiguration } = require('./configuration')
910
const { validateDescribeTask, validateAddTask } = require('./task')
1011
const { validateClientAddon } = require('./client-addon')
1112
const { validateView, validateBadge } = require('./view')
13+
const { validateNotify } = require('./notify')
1214

1315
class PluginApi {
1416
constructor (context) {
@@ -264,6 +266,24 @@ class PluginApi {
264266
return this.context.db
265267
}
266268

269+
/**
270+
* Display a notification in the user OS
271+
* @param {object} options Notification options
272+
*/
273+
notify (options) {
274+
try {
275+
validateNotify(options)
276+
notify(options)
277+
} catch (e) {
278+
logs.add({
279+
type: 'error',
280+
tag: 'PluginApi',
281+
message: `(${this.pluginId || 'unknown plugin'}) 'notify' options are invalid\n${e.message}`
282+
}, this.context)
283+
console.error(new Error(`Invalid options: ${e.message}`))
284+
}
285+
}
286+
267287
/* Namespaced */
268288

269289
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const { createSchema, validateSync } = require('@vue/cli-shared-utils')
2+
3+
const schema = createSchema(joi => ({
4+
title: joi.string().required(),
5+
message: joi.string().required(),
6+
icon: joi.string()
7+
}))
8+
9+
exports.validateNotify = (options) => {
10+
validateSync(options, schema)
11+
}

packages/@vue/cli-ui/src/graphql-api/connectors/plugins.js

+11-11
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ const {
1616
updatePackage
1717
} = require('@vue/cli/lib/util/installDeps')
1818
const invoke = require('@vue/cli/lib/invoke')
19-
const notifier = require('node-notifier')
2019
// Subs
2120
const channels = require('../channels')
2221
// Connectors
@@ -36,6 +35,7 @@ const { getCommand } = require('../utils/command')
3635
const { resolveModuleRoot } = require('../utils/resolve-path')
3736
const ipc = require('../utils/ipc')
3837
const { log } = require('../utils/logger')
38+
const { notify } = require('../utils/notification')
3939

4040
const PROGRESS_ID = 'plugin-installation'
4141
const CLI_SERVICE = '@vue/cli-service'
@@ -264,10 +264,10 @@ function install (id, context) {
264264
await initPrompts(id, context)
265265
installationStep = 'config'
266266

267-
notifier.notify({
267+
notify({
268268
title: `Plugin installed`,
269269
message: `Plugin ${id} installed, next step is configuration`,
270-
icon: path.resolve(__dirname, '../../assets/done.png')
270+
icon: 'done'
271271
})
272272

273273
return getInstallation(context)
@@ -297,10 +297,10 @@ function uninstall (id, context) {
297297
currentPluginId = null
298298
installationStep = null
299299

300-
notifier.notify({
300+
notify({
301301
title: `Plugin uninstalled`,
302302
message: `Plugin ${id} uninstalled`,
303-
icon: path.resolve(__dirname, '../../assets/done.png')
303+
icon: 'done'
304304
})
305305

306306
return getInstallation(context)
@@ -329,10 +329,10 @@ function runInvoke (id, context) {
329329
runPluginApi(id, context)
330330
installationStep = 'diff'
331331

332-
notifier.notify({
332+
notify({
333333
title: `Plugin invoke sucess`,
334334
message: `Plugin ${id} invoked successfully`,
335-
icon: path.resolve(__dirname, '../../assets/done.png')
335+
icon: 'done'
336336
})
337337

338338
return getInstallation(context)
@@ -377,10 +377,10 @@ function update (id, context, notify = true) {
377377
}, context)
378378

379379
if (notify) {
380-
notifier.notify({
380+
notify({
381381
title: `Plugin updated`,
382382
message: `Plugin ${id} was successfully updated`,
383-
icon: path.resolve(__dirname, '../../assets/done.png')
383+
icon: 'done'
384384
})
385385
}
386386

@@ -399,10 +399,10 @@ async function updateAll (context) {
399399
}
400400
}
401401

402-
notifier.notify({
402+
notify({
403403
title: `Plugins updated`,
404404
message: `${updatedPlugins.length} plugin(s) were successfully updated`,
405-
icon: path.resolve(__dirname, '../../assets/done.png')
405+
icon: 'done'
406406
})
407407

408408
return updatedPlugins

packages/@vue/cli-ui/src/graphql-api/connectors/projects.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const { getFeatures } = require('@vue/cli/lib/util/features')
77
const { defaults } = require('@vue/cli/lib/options')
88
const { toShortPluginId } = require('@vue/cli-shared-utils')
99
const { progress: installProgress } = require('@vue/cli/lib/util/installDeps')
10-
const notifier = require('node-notifier')
1110
// Connectors
1211
const progress = require('./progress')
1312
const cwd = require('./cwd')
@@ -19,6 +18,7 @@ const locales = require('./locales')
1918
const getContext = require('../context')
2019
// Utils
2120
const { log } = require('../utils/logger')
21+
const { notify } = require('../utils/notification')
2222

2323
const PROGRESS_ID = 'project-create'
2424

@@ -309,10 +309,10 @@ async function create (input, context) {
309309
await creator.create({ git: true }, preset)
310310
removeCreator()
311311

312-
notifier.notify({
312+
notify({
313313
title: `Project created`,
314314
message: `Project ${cwd.get()} created`,
315-
icon: path.resolve(__dirname, '../../assets/done.png')
315+
icon: 'done'
316316
})
317317

318318
return importProject({

packages/@vue/cli-ui/src/graphql-api/connectors/tasks.js

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
const path = require('path')
21
const execa = require('execa')
32
const terminate = require('terminate')
4-
const notifier = require('node-notifier')
53
// Subs
64
const channels = require('../channels')
75
// Connectors
@@ -14,6 +12,7 @@ const views = require('./views')
1412
// Utils
1513
const { getCommand } = require('../utils/command')
1614
const { log } = require('../utils/logger')
15+
const { notify } = require('../utils/notification')
1716

1817
const MAX_LOGS = 2000
1918
const VIEW_ID = 'vue-project-tasks'
@@ -329,10 +328,10 @@ async function run (id, context) {
329328
message: `Task ${task.id} ended with error code ${code}`,
330329
type: 'error'
331330
}, context)
332-
notifier.notify({
331+
notify({
333332
title: `Task error`,
334333
message: `Task ${task.id} ended with error code ${code}`,
335-
icon: path.resolve(__dirname, '../../assets/error.png')
334+
icon: 'error'
336335
})
337336
} else {
338337
updateOne({
@@ -343,10 +342,10 @@ async function run (id, context) {
343342
message: `Task ${task.id} completed`,
344343
type: 'done'
345344
}, context)
346-
notifier.notify({
345+
notify({
347346
title: `Task completed`,
348347
message: `Task ${task.id} completed`,
349-
icon: path.resolve(__dirname, '../../assets/done.png')
348+
icon: 'done'
350349
})
351350
}
352351
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const path = require('path')
2+
const notifier = require('node-notifier')
3+
4+
const builtinIcons = {
5+
'done': path.resolve(__dirname, '../../assets/done.png'),
6+
'error': path.resolve(__dirname, '../../assets/error.png')
7+
}
8+
9+
exports.notify = ({ title, message, icon }) => {
10+
notifier.notify({
11+
title,
12+
message,
13+
icon: builtinIcons[icon] || icon
14+
})
15+
}

0 commit comments

Comments
 (0)