Skip to content

Commit

Permalink
feat(ui): suggestion and progress PluginAPI + add vue-router/vuex sug…
Browse files Browse the repository at this point in the history
…gestions
  • Loading branch information
Akryum committed Jun 11, 2018
1 parent 9426f38 commit 9b068b1
Show file tree
Hide file tree
Showing 24 changed files with 559 additions and 5 deletions.
20 changes: 20 additions & 0 deletions packages/@vue/cli-ui/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@
"translate": "Help translate",
"dark-mode": "Toggle dark mode"
},
"suggestion-bar": {
"suggestion": "Suggestion",
"modal": {
"cancel": "Cancel",
"continue": "Continue"
}
},
"terminal-view": {
"buttons": {
"clear": "Clear console",
Expand Down Expand Up @@ -340,6 +347,19 @@
"back": "Go back"
}
},
"cli-service": {
"suggestions": {
"vue-router-add": {
"label": "Add vue-router",
"message": "Add support for multiple pages into the app."
},
"vuex-add": {
"label": "Add vuex",
"message": "Centralized State Management solution for large-scale apps."
},
"progress": "Installing {arg0}..."
}
},
"vue-webpack": {
"dashboard": {
"title": "Dashboard",
Expand Down
127 changes: 127 additions & 0 deletions packages/@vue/cli-ui/src/components/SuggestionBar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<template>
<ApolloQuery
:query="require('../graphql/suggestions.gql')"
class="suggestion-bar"
>
<ApolloSubscribeToMore
:document="require('../graphql/suggestionAdded.gql')"
:updateQuery="(previousResult, { subscriptionData }) => ({
suggestions: [
...previousResult.suggestions,
subscriptionData.data.suggestionAdded
]
})"
/>

<ApolloSubscribeToMore
:document="require('../graphql/suggestionUpdated.gql')"
/>

<ApolloSubscribeToMore
:document="require('../graphql/suggestionRemoved.gql')"
:updateQuery="(previousResult, { subscriptionData }) => ({
suggestions: previousResult.suggestions.filter(
s => s.id !== subscriptionData.data.suggestionRemoved.id
)
})"
/>

<template slot-scope="{ result: { data } }" v-if="data">
<VueButton
v-for="suggestion of data.suggestions"
:key="suggestion.id"
:label="$t(suggestion.label)"
:loading="suggestion.busy"
class="suggestion round"
v-tooltip="$t('components.suggestion-bar.suggestion')"
@click="select(suggestion)"
/>
</template>

<VueModal
v-if="showDetails"
:title="$t(currentSuggestion.label)"
class="medium"
@close="showDetails = false"
>
<div class="default-body">
<div
v-if="currentSuggestion.message"
class="info message"
v-html="$t(currentSuggestion.message)"
/>
<div
v-if="currentSuggestion.link"
class="info links"
>
<a :href="currentSuggestion.link" target="_blank">
{{ $t('components.list-item-info.more-info') }}
</a>
</div>
</div>

<div slot="footer" class="actions">
<VueButton
class="big"
:label="$t('components.suggestion-bar.modal.cancel')"
icon="close"
@click="showDetails = false"
/>
<VueButton
class="primary big"
:label="$t('components.suggestion-bar.modal.continue')"
icon="done"
@click="activate(currentSuggestion)"
/>
</div>
</VueModal>
</ApolloQuery>
</template>

<script>
import SUGGESTION_ACTIVATE from '../graphql/suggestionActivate.gql'
export default {
data () {
return {
currentSuggestion: null,
showDetails: false
}
},
methods: {
select (suggestion) {
if (suggestion.message || suggestion.link) {
this.currentSuggestion = suggestion
this.showDetails = true
} else {
this.activate(suggestion)
}
},
async activate (suggestion) {
this.showDetails = false
await this.$apollo.mutate({
mutation: SUGGESTION_ACTIVATE,
variables: {
input: {
id: suggestion.id
}
}
})
}
}
}
</script>

<style lang="stylus" scoped>
@import "~@/style/imports"
.suggestion
&:not(:first-child)
margin-left $padding-item
.info
&:not(:last-child)
margin-bottom $padding-item
</style>
6 changes: 6 additions & 0 deletions packages/@vue/cli-ui/src/components/TopBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,15 @@
icon-left="home"
/>
</VueDropdown>

<portal-target name="top-title" class="title">Vue</portal-target>

<AppLoading/>

<div class="vue-ui-spacer"/>

<SuggestionBar/>

<portal-target name="top-actions" class="actions"/>
</div>
</template>
Expand Down
82 changes: 81 additions & 1 deletion packages/@vue/cli-ui/src/graphql-api/api/PluginApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
const logs = require('../connectors/logs')
const sharedData = require('../connectors/shared-data')
const views = require('../connectors/views')
const suggestions = require('../connectors/suggestions')
const folders = require('../connectors/folders')
const cwd = require('../connectors/cwd')
const progress = require('../connectors/progress')
// Utils
const ipc = require('../utils/ipc')
const { notify } = require('../utils/notification')
Expand All @@ -12,6 +16,8 @@ const { validateDescribeTask, validateAddTask } = require('./task')
const { validateClientAddon } = require('./client-addon')
const { validateView, validateBadge } = require('./view')
const { validateNotify } = require('./notify')
const { validateSuggestion } = require('./suggestion')
const { validateProgress } = require('./progress')

class PluginApi {
constructor ({ plugins }, context) {
Expand Down Expand Up @@ -353,9 +359,50 @@ class PluginApi {
* @param {string} id Plugin id or short id
*/
hasPlugin (id) {
if (['vue-router', 'vuex'].includes(id)) {
const folder = cwd.get()
const pkg = folders.readPackage(folder, this.context, true)
return (pkg.dependencies[id] || pkg.devDependencies[id])
}
return this.plugins.some(p => matchesPluginId(id, p.id))
}

/**
* Display the progress screen.
*
* @param {object} options Progress options
*/
setProgress (options) {
try {
validateProgress(options)
progress.set({
...options,
id: '__plugins__'
}, this.context)
} catch (e) {
logs.add({
type: 'error',
tag: 'PluginApi',
message: `(${this.pluginId || 'unknown plugin'}) 'setProgress' options are invalid\n${e.message}`
}, this.context)
console.error(new Error(`Invalid options: ${e.message}`))
}
}

/**
* Remove the progress screen.
*/
removeProgress () {
progress.remove('__plugins__', this.context)
}

/**
* Get current working directory.
*/
getCwd () {
return cwd.get()
}

/* Namespaced */

/**
Expand Down Expand Up @@ -454,6 +501,34 @@ class PluginApi {
this.db.set(id, value).write()
}

/**
* Add a suggestion for the user.
*
* @param {object} options Suggestion
*/
addSuggestion (options) {
try {
validateSuggestion(options)
suggestions.add(options, this.context)
} catch (e) {
logs.add({
type: 'error',
tag: 'PluginApi',
message: `(${this.pluginId || 'unknown plugin'}) 'addSuggestion' options are invalid\n${e.message}`
}, this.context)
console.error(new Error(`Invalid options: ${e.message}`))
}
}

/**
* Remove a suggestion
*
* @param {string} id Id of the suggestion
*/
removeSuggestion (id) {
suggestions.remove(id, this.context)
}

/**
* Create a namespaced version of:
* - getSharedData
Expand All @@ -474,7 +549,12 @@ class PluginApi {
onAction: (id, cb) => this.onAction(namespace + id, cb),
callAction: (id, params) => this.callAction(namespace + id, params),
storageGet: (id) => this.storageGet(namespace + id),
storageSet: (id, value) => this.storageSet(namespace + id, value)
storageSet: (id, value) => this.storageSet(namespace + id, value),
addSuggestion: (options) => {
options.id = namespace + options.id
return this.addSuggestion(options)
},
removeSuggestion: (id) => this.removeSuggestion(namespace + id)
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions packages/@vue/cli-ui/src/graphql-api/api/progress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const { createSchema, validateSync } = require('@vue/cli-shared-utils')

const schema = createSchema(joi => ({
status: joi.string().required(),
error: joi.string(),
info: joi.string(),
progress: joi.number(),
args: joi.array()
}))

exports.validateProgress = (options) => {
validateSync(options, schema)
}
15 changes: 15 additions & 0 deletions packages/@vue/cli-ui/src/graphql-api/api/suggestion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { createSchema, validateSync } = require('@vue/cli-shared-utils')

const schema = createSchema(joi => ({
id: joi.string().required(),
label: joi.string().required(),
type: joi.string().required(),
handler: joi.func().required(),
importance: joi.string(),
message: joi.string(),
link: joi.string()
}))

exports.validateSuggestion = (options) => {
validateSync(options, schema)
}
5 changes: 4 additions & 1 deletion packages/@vue/cli-ui/src/graphql-api/channels.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@ module.exports = {
SHARED_DATA_UPDATED: 'shared_data_updated',
PLUGIN_ACTION_CALLED: 'plugin_action_called',
PLUGIN_ACTION_RESOLVED: 'plugin_action_resolved',
LOCALE_ADDED: 'locale_added'
LOCALE_ADDED: 'locale_added',
SUGGESTION_ADDED: 'suggestion_added',
SUGGESTION_REMOVED: 'suggestion_removed',
SUGGESTION_UPDATED: 'suggestion_updated'
}
4 changes: 4 additions & 0 deletions packages/@vue/cli-ui/src/graphql-api/connectors/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const clientAddons = require('./client-addons')
const views = require('./views')
const locales = require('./locales')
const sharedData = require('./shared-data')
const suggestions = require('./suggestions')
// Api
const PluginApi = require('../api/PluginApi')
// Utils
Expand Down Expand Up @@ -101,10 +102,13 @@ function resetPluginApi (context) {
}
sharedData.unWatchAll()

suggestions.clear(context)

pluginApi = new PluginApi({
plugins
}, context)
// Run Plugin API
runPluginApi(path.resolve(__dirname, '../../../'), context, 'ui-defaults')
plugins.forEach(plugin => runPluginApi(plugin.id, context))
runPluginApi(cwd.get(), context, 'vue-cli-ui')
// Add client addons
Expand Down
Loading

0 comments on commit 9b068b1

Please sign in to comment.