Skip to content

Commit 9b068b1

Browse files
committed
feat(ui): suggestion and progress PluginAPI + add vue-router/vuex suggestions
1 parent 9426f38 commit 9b068b1

24 files changed

+559
-5
lines changed

packages/@vue/cli-ui/locales/en.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@
114114
"translate": "Help translate",
115115
"dark-mode": "Toggle dark mode"
116116
},
117+
"suggestion-bar": {
118+
"suggestion": "Suggestion",
119+
"modal": {
120+
"cancel": "Cancel",
121+
"continue": "Continue"
122+
}
123+
},
117124
"terminal-view": {
118125
"buttons": {
119126
"clear": "Clear console",
@@ -340,6 +347,19 @@
340347
"back": "Go back"
341348
}
342349
},
350+
"cli-service": {
351+
"suggestions": {
352+
"vue-router-add": {
353+
"label": "Add vue-router",
354+
"message": "Add support for multiple pages into the app."
355+
},
356+
"vuex-add": {
357+
"label": "Add vuex",
358+
"message": "Centralized State Management solution for large-scale apps."
359+
},
360+
"progress": "Installing {arg0}..."
361+
}
362+
},
343363
"vue-webpack": {
344364
"dashboard": {
345365
"title": "Dashboard",
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<template>
2+
<ApolloQuery
3+
:query="require('../graphql/suggestions.gql')"
4+
class="suggestion-bar"
5+
>
6+
<ApolloSubscribeToMore
7+
:document="require('../graphql/suggestionAdded.gql')"
8+
:updateQuery="(previousResult, { subscriptionData }) => ({
9+
suggestions: [
10+
...previousResult.suggestions,
11+
subscriptionData.data.suggestionAdded
12+
]
13+
})"
14+
/>
15+
16+
<ApolloSubscribeToMore
17+
:document="require('../graphql/suggestionUpdated.gql')"
18+
/>
19+
20+
<ApolloSubscribeToMore
21+
:document="require('../graphql/suggestionRemoved.gql')"
22+
:updateQuery="(previousResult, { subscriptionData }) => ({
23+
suggestions: previousResult.suggestions.filter(
24+
s => s.id !== subscriptionData.data.suggestionRemoved.id
25+
)
26+
})"
27+
/>
28+
29+
<template slot-scope="{ result: { data } }" v-if="data">
30+
<VueButton
31+
v-for="suggestion of data.suggestions"
32+
:key="suggestion.id"
33+
:label="$t(suggestion.label)"
34+
:loading="suggestion.busy"
35+
class="suggestion round"
36+
v-tooltip="$t('components.suggestion-bar.suggestion')"
37+
@click="select(suggestion)"
38+
/>
39+
</template>
40+
41+
<VueModal
42+
v-if="showDetails"
43+
:title="$t(currentSuggestion.label)"
44+
class="medium"
45+
@close="showDetails = false"
46+
>
47+
<div class="default-body">
48+
<div
49+
v-if="currentSuggestion.message"
50+
class="info message"
51+
v-html="$t(currentSuggestion.message)"
52+
/>
53+
<div
54+
v-if="currentSuggestion.link"
55+
class="info links"
56+
>
57+
<a :href="currentSuggestion.link" target="_blank">
58+
{{ $t('components.list-item-info.more-info') }}
59+
</a>
60+
</div>
61+
</div>
62+
63+
<div slot="footer" class="actions">
64+
<VueButton
65+
class="big"
66+
:label="$t('components.suggestion-bar.modal.cancel')"
67+
icon="close"
68+
@click="showDetails = false"
69+
/>
70+
<VueButton
71+
class="primary big"
72+
:label="$t('components.suggestion-bar.modal.continue')"
73+
icon="done"
74+
@click="activate(currentSuggestion)"
75+
/>
76+
</div>
77+
</VueModal>
78+
</ApolloQuery>
79+
</template>
80+
81+
<script>
82+
import SUGGESTION_ACTIVATE from '../graphql/suggestionActivate.gql'
83+
84+
export default {
85+
data () {
86+
return {
87+
currentSuggestion: null,
88+
showDetails: false
89+
}
90+
},
91+
92+
methods: {
93+
select (suggestion) {
94+
if (suggestion.message || suggestion.link) {
95+
this.currentSuggestion = suggestion
96+
this.showDetails = true
97+
} else {
98+
this.activate(suggestion)
99+
}
100+
},
101+
102+
async activate (suggestion) {
103+
this.showDetails = false
104+
await this.$apollo.mutate({
105+
mutation: SUGGESTION_ACTIVATE,
106+
variables: {
107+
input: {
108+
id: suggestion.id
109+
}
110+
}
111+
})
112+
}
113+
}
114+
}
115+
</script>
116+
117+
<style lang="stylus" scoped>
118+
@import "~@/style/imports"
119+
120+
.suggestion
121+
&:not(:first-child)
122+
margin-left $padding-item
123+
124+
.info
125+
&:not(:last-child)
126+
margin-bottom $padding-item
127+
</style>

packages/@vue/cli-ui/src/components/TopBar.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,15 @@
2525
icon-left="home"
2626
/>
2727
</VueDropdown>
28+
2829
<portal-target name="top-title" class="title">Vue</portal-target>
30+
2931
<AppLoading/>
32+
3033
<div class="vue-ui-spacer"/>
34+
35+
<SuggestionBar/>
36+
3137
<portal-target name="top-actions" class="actions"/>
3238
</div>
3339
</template>

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

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
const logs = require('../connectors/logs')
33
const sharedData = require('../connectors/shared-data')
44
const views = require('../connectors/views')
5+
const suggestions = require('../connectors/suggestions')
6+
const folders = require('../connectors/folders')
7+
const cwd = require('../connectors/cwd')
8+
const progress = require('../connectors/progress')
59
// Utils
610
const ipc = require('../utils/ipc')
711
const { notify } = require('../utils/notification')
@@ -12,6 +16,8 @@ const { validateDescribeTask, validateAddTask } = require('./task')
1216
const { validateClientAddon } = require('./client-addon')
1317
const { validateView, validateBadge } = require('./view')
1418
const { validateNotify } = require('./notify')
19+
const { validateSuggestion } = require('./suggestion')
20+
const { validateProgress } = require('./progress')
1521

1622
class PluginApi {
1723
constructor ({ plugins }, context) {
@@ -353,9 +359,50 @@ class PluginApi {
353359
* @param {string} id Plugin id or short id
354360
*/
355361
hasPlugin (id) {
362+
if (['vue-router', 'vuex'].includes(id)) {
363+
const folder = cwd.get()
364+
const pkg = folders.readPackage(folder, this.context, true)
365+
return (pkg.dependencies[id] || pkg.devDependencies[id])
366+
}
356367
return this.plugins.some(p => matchesPluginId(id, p.id))
357368
}
358369

370+
/**
371+
* Display the progress screen.
372+
*
373+
* @param {object} options Progress options
374+
*/
375+
setProgress (options) {
376+
try {
377+
validateProgress(options)
378+
progress.set({
379+
...options,
380+
id: '__plugins__'
381+
}, this.context)
382+
} catch (e) {
383+
logs.add({
384+
type: 'error',
385+
tag: 'PluginApi',
386+
message: `(${this.pluginId || 'unknown plugin'}) 'setProgress' options are invalid\n${e.message}`
387+
}, this.context)
388+
console.error(new Error(`Invalid options: ${e.message}`))
389+
}
390+
}
391+
392+
/**
393+
* Remove the progress screen.
394+
*/
395+
removeProgress () {
396+
progress.remove('__plugins__', this.context)
397+
}
398+
399+
/**
400+
* Get current working directory.
401+
*/
402+
getCwd () {
403+
return cwd.get()
404+
}
405+
359406
/* Namespaced */
360407

361408
/**
@@ -454,6 +501,34 @@ class PluginApi {
454501
this.db.set(id, value).write()
455502
}
456503

504+
/**
505+
* Add a suggestion for the user.
506+
*
507+
* @param {object} options Suggestion
508+
*/
509+
addSuggestion (options) {
510+
try {
511+
validateSuggestion(options)
512+
suggestions.add(options, this.context)
513+
} catch (e) {
514+
logs.add({
515+
type: 'error',
516+
tag: 'PluginApi',
517+
message: `(${this.pluginId || 'unknown plugin'}) 'addSuggestion' options are invalid\n${e.message}`
518+
}, this.context)
519+
console.error(new Error(`Invalid options: ${e.message}`))
520+
}
521+
}
522+
523+
/**
524+
* Remove a suggestion
525+
*
526+
* @param {string} id Id of the suggestion
527+
*/
528+
removeSuggestion (id) {
529+
suggestions.remove(id, this.context)
530+
}
531+
457532
/**
458533
* Create a namespaced version of:
459534
* - getSharedData
@@ -474,7 +549,12 @@ class PluginApi {
474549
onAction: (id, cb) => this.onAction(namespace + id, cb),
475550
callAction: (id, params) => this.callAction(namespace + id, params),
476551
storageGet: (id) => this.storageGet(namespace + id),
477-
storageSet: (id, value) => this.storageSet(namespace + id, value)
552+
storageSet: (id, value) => this.storageSet(namespace + id, value),
553+
addSuggestion: (options) => {
554+
options.id = namespace + options.id
555+
return this.addSuggestion(options)
556+
},
557+
removeSuggestion: (id) => this.removeSuggestion(namespace + id)
478558
}
479559
}
480560
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const { createSchema, validateSync } = require('@vue/cli-shared-utils')
2+
3+
const schema = createSchema(joi => ({
4+
status: joi.string().required(),
5+
error: joi.string(),
6+
info: joi.string(),
7+
progress: joi.number(),
8+
args: joi.array()
9+
}))
10+
11+
exports.validateProgress = (options) => {
12+
validateSync(options, schema)
13+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const { createSchema, validateSync } = require('@vue/cli-shared-utils')
2+
3+
const schema = createSchema(joi => ({
4+
id: joi.string().required(),
5+
label: joi.string().required(),
6+
type: joi.string().required(),
7+
handler: joi.func().required(),
8+
importance: joi.string(),
9+
message: joi.string(),
10+
link: joi.string()
11+
}))
12+
13+
exports.validateSuggestion = (options) => {
14+
validateSync(options, schema)
15+
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,8 @@ module.exports = {
1212
SHARED_DATA_UPDATED: 'shared_data_updated',
1313
PLUGIN_ACTION_CALLED: 'plugin_action_called',
1414
PLUGIN_ACTION_RESOLVED: 'plugin_action_resolved',
15-
LOCALE_ADDED: 'locale_added'
15+
LOCALE_ADDED: 'locale_added',
16+
SUGGESTION_ADDED: 'suggestion_added',
17+
SUGGESTION_REMOVED: 'suggestion_removed',
18+
SUGGESTION_UPDATED: 'suggestion_updated'
1619
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const clientAddons = require('./client-addons')
2828
const views = require('./views')
2929
const locales = require('./locales')
3030
const sharedData = require('./shared-data')
31+
const suggestions = require('./suggestions')
3132
// Api
3233
const PluginApi = require('../api/PluginApi')
3334
// Utils
@@ -101,10 +102,13 @@ function resetPluginApi (context) {
101102
}
102103
sharedData.unWatchAll()
103104

105+
suggestions.clear(context)
106+
104107
pluginApi = new PluginApi({
105108
plugins
106109
}, context)
107110
// Run Plugin API
111+
runPluginApi(path.resolve(__dirname, '../../../'), context, 'ui-defaults')
108112
plugins.forEach(plugin => runPluginApi(plugin.id, context))
109113
runPluginApi(cwd.get(), context, 'vue-cli-ui')
110114
// Add client addons

0 commit comments

Comments
 (0)