-
+
+
+
+
+
diff --git a/packages/@vue/cli-ui/src/components/FolderExplorer.vue b/packages/@vue/cli-ui/src/components/FolderExplorer.vue
index 23ffa42470..796dd9dd44 100644
--- a/packages/@vue/cli-ui/src/components/FolderExplorer.vue
+++ b/packages/@vue/cli-ui/src/components/FolderExplorer.vue
@@ -214,13 +214,13 @@ export default {
@import "~@/style/imports"
.toolbar
- padding 12px
+ padding $padding-item
background $color-light-background
h-box()
align-items center
>>> > *
- space-between-x(12px)
+ space-between-x($padding-item)
.current-path
flex 100% 1 1
diff --git a/packages/@vue/cli-ui/src/components/FolderExplorerItem.vue b/packages/@vue/cli-ui/src/components/FolderExplorerItem.vue
index bc677fd3d6..0eb68b03f6 100644
--- a/packages/@vue/cli-ui/src/components/FolderExplorerItem.vue
+++ b/packages/@vue/cli-ui/src/components/FolderExplorerItem.vue
@@ -33,7 +33,7 @@ export default {
.folder-name
flex 100% 1 1
- margin-left 12px
+ margin-left $padding-item
ellipsis()
.vue-project-icon
@@ -44,7 +44,7 @@ export default {
top 5px
.foder-explorer-item
- padding 12px
+ padding $padding-item
h-box()
align-items center
user-select none
diff --git a/packages/@vue/cli-ui/src/components/ListItemInfo.vue b/packages/@vue/cli-ui/src/components/ListItemInfo.vue
new file mode 100644
index 0000000000..29bd56cc2a
--- /dev/null
+++ b/packages/@vue/cli-ui/src/components/ListItemInfo.vue
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
diff --git a/packages/@vue/cli-ui/src/components/ProjectFeatureItem.vue b/packages/@vue/cli-ui/src/components/ProjectFeatureItem.vue
index 62ccf77b00..8a77ef0daa 100644
--- a/packages/@vue/cli-ui/src/components/ProjectFeatureItem.vue
+++ b/packages/@vue/cli-ui/src/components/ProjectFeatureItem.vue
@@ -1,34 +1,32 @@
+
+
diff --git a/packages/@vue/cli-ui/src/components/PromptCheckbox.vue b/packages/@vue/cli-ui/src/components/PromptCheckbox.vue
new file mode 100644
index 0000000000..d545e5ad15
--- /dev/null
+++ b/packages/@vue/cli-ui/src/components/PromptCheckbox.vue
@@ -0,0 +1,65 @@
+
+
+
+
+
+ asnwerCheckbox(choice, value)"
+ >
+ {{ choice.name }}
+
+
+
+
+
+
+
+
diff --git a/packages/@vue/cli-ui/src/components/PromptConfirm.vue b/packages/@vue/cli-ui/src/components/PromptConfirm.vue
new file mode 100644
index 0000000000..4dd74007c4
--- /dev/null
+++ b/packages/@vue/cli-ui/src/components/PromptConfirm.vue
@@ -0,0 +1,33 @@
+
+
+ answer(value)"
+ >
+
+
+
+
+
+
+
+
diff --git a/packages/@vue/cli-ui/src/components/PromptList.vue b/packages/@vue/cli-ui/src/components/PromptList.vue
new file mode 100644
index 0000000000..31158b5c08
--- /dev/null
+++ b/packages/@vue/cli-ui/src/components/PromptList.vue
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+ answer(value)"
+ >
+
+
+
+
+
+
+
+
diff --git a/packages/@vue/cli-ui/src/components/PromptsList.vue b/packages/@vue/cli-ui/src/components/PromptsList.vue
new file mode 100644
index 0000000000..1c60dd0c0f
--- /dev/null
+++ b/packages/@vue/cli-ui/src/components/PromptsList.vue
@@ -0,0 +1,37 @@
+
+
+
+ $emit('answer', { prompt, value })"
+ />
+
+
+
+
+
+
+
diff --git a/packages/@vue/cli-ui/src/components/StatusBar.vue b/packages/@vue/cli-ui/src/components/StatusBar.vue
index f619592f60..c91d38a652 100644
--- a/packages/@vue/cli-ui/src/components/StatusBar.vue
+++ b/packages/@vue/cli-ui/src/components/StatusBar.vue
@@ -65,7 +65,7 @@ export default {
h-box()
align-items center
background $vue-color-light-neutral
- font-size 12px
+ font-size $padding-item
.section
h-box()
diff --git a/packages/@vue/cli-ui/src/components/StepWizard.vue b/packages/@vue/cli-ui/src/components/StepWizard.vue
index c6c853b699..ac5a5e27dd 100644
--- a/packages/@vue/cli-ui/src/components/StepWizard.vue
+++ b/packages/@vue/cli-ui/src/components/StepWizard.vue
@@ -5,22 +5,24 @@
'hide-tabs': hideTabs
}"
>
-
@@ -57,7 +59,11 @@ export default {
@import "~@/style/imports"
.step-wizard
- v-box()
+ box-sizing border-box
+
+ .shell
+ v-box()
+ height 100%
.main-tabs
height 100%
@@ -90,7 +96,7 @@ export default {
justify-content center
.title
- padding 12px
+ padding $padding-item
font-size 24px
text-align center
font-weight lighter
@@ -99,4 +105,12 @@ export default {
>>> .tabs
display none
+ &.frame
+ margin 0 auto
+ padding $padding-item
+ max-width 1200px
+ .shell
+ border solid 1px $vue-color-light-neutral
+ border-radius $br
+
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 bdd5607714..a7f6b07ad9 100644
--- a/packages/@vue/cli-ui/src/graphql-api/connectors/projects.js
+++ b/packages/@vue/cli-ui/src/graphql-api/connectors/projects.js
@@ -3,6 +3,7 @@ const { getPromptModules } = require('@vue/cli/lib/util/createTools')
const { getFeatures } = require('@vue/cli/lib/util/features')
const { toShortPluginId } = require('@vue/cli-shared-utils')
const cwd = require('./cwd')
+const prompts = require('./prompts')
let currentProject = null
let creator = null
@@ -30,7 +31,8 @@ function generatePresetDescription (preset) {
function generateProjectCreation (creator) {
return {
presets,
- features
+ features,
+ prompts: prompts.list()
}
}
@@ -50,6 +52,7 @@ function initCreator () {
id: key,
name: key === 'default' ? 'Default preset' : key,
features,
+ link: null,
raw: preset
}
info.description = generatePresetDescription(info)
@@ -58,8 +61,9 @@ function initCreator () {
),
{
id: 'manual',
- name: 'No preset',
- description: 'No included features',
+ name: 'Manual',
+ description: 'Manually select features',
+ link: null,
features: []
}
]
@@ -87,6 +91,10 @@ function initCreator () {
}
]
+ // Prompts
+ prompts.reset()
+ creator.injectedPrompts.forEach(prompts.add)
+
return creator
}
@@ -97,13 +105,24 @@ function getCreation (context) {
return generateProjectCreation(creator)
}
-function setFeatureEnabled ({ id, enabled }, context) {
+function updatePromptsFeatures () {
+ prompts.changeAnswers(answers => {
+ answers.features = features.filter(
+ f => f.enabled
+ ).map(
+ f => f.id
+ )
+ })
+}
+
+function setFeatureEnabled ({ id, enabled, updatePrompts = true }, context) {
const feature = features.find(f => f.id === id)
if (feature) {
feature.enabled = enabled
} else {
console.warn(`Feature '${id}' not found`)
}
+ if (updatePrompts) updatePromptsFeatures()
return feature
}
@@ -118,18 +137,19 @@ function applyPreset (id, context) {
}
if (preset.raw) {
if (preset.raw.router) {
- setFeatureEnabled({ id: 'router', enabled: true }, context)
+ setFeatureEnabled({ id: 'router', enabled: true, updatePrompts: false }, context)
}
if (preset.raw.vuex) {
- setFeatureEnabled({ id: 'vuex', enabled: true }, context)
+ setFeatureEnabled({ id: 'vuex', enabled: true, updatePrompts: false }, context)
}
if (preset.raw.cssPreprocessor) {
- setFeatureEnabled({ id: 'css-preprocessor', enabled: true }, context)
+ setFeatureEnabled({ id: 'css-preprocessor', enabled: true, updatePrompts: false }, context)
}
if (preset.raw.useConfigFiles) {
- setFeatureEnabled({ id: 'use-config-files', enabled: true }, context)
+ setFeatureEnabled({ id: 'use-config-files', enabled: true, updatePrompts: false }, context)
}
}
+ updatePromptsFeatures()
} else {
console.warn(`Preset '${id}' not found`)
}
@@ -137,10 +157,16 @@ function applyPreset (id, context) {
return generateProjectCreation(creator)
}
+function answerPrompt ({ id, value }, context) {
+ prompts.setValue({ id, value: JSON.parse(value) })
+ return prompts.list()
+}
+
module.exports = {
list,
getCurrent,
getCreation,
applyPreset,
- setFeatureEnabled
+ setFeatureEnabled,
+ answerPrompt
}
diff --git a/packages/@vue/cli-ui/src/graphql-api/connectors/prompts.js b/packages/@vue/cli-ui/src/graphql-api/connectors/prompts.js
new file mode 100644
index 0000000000..149f1adead
--- /dev/null
+++ b/packages/@vue/cli-ui/src/graphql-api/connectors/prompts.js
@@ -0,0 +1,205 @@
+let answers = {}
+let prompts = []
+
+function getPrompt (id) {
+ return prompts.find(
+ p => p.id === id
+ )
+}
+
+function generatePromptError (value) {
+ let message
+ if (typeof value === 'string') {
+ message = value
+ } else {
+ message = 'Invalid input'
+ }
+ return {
+ message
+ }
+}
+
+function getDefaultValue (prompt) {
+ const defaultValue = prompt.raw.default
+ if (typeof defaultValue === 'function') {
+ return defaultValue(answers)
+ } else if (prompt.type === 'checkbox') {
+ const choices = getChoices(prompt)
+ if (choices) {
+ return choices.filter(
+ c => c.checked
+ ).map(
+ c => c.value
+ )
+ }
+ } else if (prompt.type === 'confirm') {
+ return false
+ }
+ return defaultValue
+}
+
+function getEnabled (value) {
+ const type = typeof value
+ if (type === 'function') {
+ return value(answers)
+ } else if (type === 'boolean') {
+ return value
+ } else {
+ return true
+ }
+}
+
+function validateInput (prompt, value) {
+ const validate = prompt.raw.validate
+ if (typeof validate === 'function') {
+ return validate(value)
+ }
+ return true
+}
+
+function getValue (prompt, value) {
+ const filter = prompt.raw.filter
+ if (typeof filter === 'function') {
+ return filter(value)
+ }
+ return value
+}
+
+function getDisplayedValue (prompt, value) {
+ const transform = prompt.raw.transform
+ if (typeof transform === 'function') {
+ value = transform(value)
+ }
+ return JSON.stringify(value)
+}
+
+function generatePromptChoice (prompt, data) {
+ return {
+ value: getDisplayedValue(prompt, data.value),
+ name: data.name,
+ checked: data.checked,
+ disabled: data.disabled
+ }
+}
+
+function getChoices (prompt) {
+ const data = prompt.raw.choices
+ if (!data) {
+ return null
+ }
+
+ let result
+ if (typeof data === 'function') {
+ result = data(answers)
+ } else {
+ result = data
+ }
+ return result.map(
+ item => generatePromptChoice(prompt, item)
+ )
+}
+
+function setAnswer (id, value) {
+ const fields = id.split('.')
+ let obj = answers
+ const l = fields.length
+ for (let i = 0; i < l - 1; i++) {
+ const key = fields[i]
+ if (!obj[key]) {
+ obj[key] = {}
+ }
+ obj = obj[key]
+ }
+ obj[fields[l - 1]] = value
+}
+
+function generatePrompt (data) {
+ return {
+ id: data.name,
+ enabled: true,
+ type: data.type,
+ name: data.short || null,
+ message: data.message,
+ description: data.description || null,
+ link: data.link || null,
+ choices: null,
+ value: null,
+ valueChanged: false,
+ raw: data
+ }
+}
+
+function updatePrompts () {
+ for (const prompt of prompts) {
+ prompt.enabled = getEnabled(prompt.raw.when)
+
+ prompt.choices = getChoices(prompt)
+
+ if (!prompt.valueChanged) {
+ let value = getDefaultValue(prompt)
+ prompt.value = getDisplayedValue(prompt, value)
+ setAnswer(prompt.id, getValue(prompt, value))
+ }
+ }
+}
+
+// Public API
+
+function setAnswers (newAnswers) {
+ answers = newAnswers
+ updatePrompts()
+}
+
+function changeAnswers (cb) {
+ cb(answers)
+ updatePrompts()
+}
+
+function reset () {
+ prompts = []
+ setAnswers({})
+}
+
+function list () {
+ return prompts
+}
+
+function add (data) {
+ prompts.push(generatePrompt(data))
+}
+
+function remove (id) {
+ const index = prompts.findIndex(p => p.id === id)
+ index !== -1 && prompts.splice(index, 1)
+}
+
+function setValue ({ id, value }) {
+ const prompt = getPrompt(id)
+ if (!prompt) {
+ console.warn(`Prompt '${prompt}' not found`)
+ return null
+ }
+
+ const validation = validateInput(prompt, value)
+ if (validation !== true) {
+ prompt.error = generatePromptError(validation)
+ } else {
+ prompt.error = undefined
+ }
+ prompt.value = getDisplayedValue(prompt, value)
+ const finalValue = getValue(prompt, value)
+ prompt.valueChanged = true
+ setAnswer(prompt.id, finalValue)
+ updatePrompts()
+ return prompt
+}
+
+module.exports = {
+ setAnswers,
+ changeAnswers,
+ reset,
+ list,
+ add,
+ remove,
+ setValue
+}
diff --git a/packages/@vue/cli-ui/src/graphql-api/resolvers.js b/packages/@vue/cli-ui/src/graphql-api/resolvers.js
index ff14304b9e..ea8bc9bdc2 100644
--- a/packages/@vue/cli-ui/src/graphql-api/resolvers.js
+++ b/packages/@vue/cli-ui/src/graphql-api/resolvers.js
@@ -27,7 +27,8 @@ module.exports = {
favorite: args.favorite
}, context),
presetApply: (root, { id }, context) => projects.applyPreset(id, context),
- featureSetEnabled: (root, args, context) => projects.setFeatureEnabled(args, context)
+ featureSetEnabled: (root, args, context) => projects.setFeatureEnabled(args, context),
+ promptAnswer: (root, { input }, context) => projects.answerPrompt(input, context)
},
Subscription: {
diff --git a/packages/@vue/cli-ui/src/graphql-api/type-defs.js b/packages/@vue/cli-ui/src/graphql-api/type-defs.js
index d72dfe0889..f9fcba8713 100644
--- a/packages/@vue/cli-ui/src/graphql-api/type-defs.js
+++ b/packages/@vue/cli-ui/src/graphql-api/type-defs.js
@@ -49,6 +49,20 @@ input ProjectImportInput {
path: String!
}
+type Preset {
+ id: ID!
+ name: String
+ description: String
+ link: String
+ features: [String]
+}
+
+type ProjectCreation {
+ presets: [Preset]
+ features: [Feature]
+ prompts: [Prompt]
+}
+
type Version {
current: String!
latest: String
@@ -94,6 +108,9 @@ enum PromptType {
type PromptChoice {
value: String!
+ name: String
+ checked: Boolean
+ disabled: Boolean
}
type PromptError {
@@ -110,7 +127,8 @@ type Prompt {
description: String
link: String
choices: [PromptChoice]
- currentValue: String
+ value: String
+ valueChanged: Boolean
error: PromptError
}
@@ -119,18 +137,6 @@ input PromptInput {
value: String!
}
-type Preset {
- id: ID!
- name: String
- description: String
- features: [String]
-}
-
-type ProjectCreation {
- presets: [Preset]
- features: [Feature]
-}
-
type Query {
cwd: String!
folderCurrent: Folder
@@ -152,7 +158,7 @@ type Mutation {
presetApply (id: ID!): ProjectCreation
featureSetEnabled (id: ID!, enabled: Boolean): Feature
pluginAdd (id: ID!): Plugin
- promptAnswer (input: PromptInput!): Prompt
+ promptAnswer (input: PromptInput!): [Prompt]
}
type Subscription {
diff --git a/packages/@vue/cli-ui/src/graphql/presetFragment.gql b/packages/@vue/cli-ui/src/graphql/presetFragment.gql
index 18b6ffdf65..719c263037 100644
--- a/packages/@vue/cli-ui/src/graphql/presetFragment.gql
+++ b/packages/@vue/cli-ui/src/graphql/presetFragment.gql
@@ -2,5 +2,5 @@ fragment preset on Preset {
id
name
description
- features
+ link
}
diff --git a/packages/@vue/cli-ui/src/graphql/projectCreationFragment.gql b/packages/@vue/cli-ui/src/graphql/projectCreationFragment.gql
index f2f42574fd..f38cf2029a 100644
--- a/packages/@vue/cli-ui/src/graphql/projectCreationFragment.gql
+++ b/packages/@vue/cli-ui/src/graphql/projectCreationFragment.gql
@@ -1,5 +1,6 @@
#import "./presetFragment.gql"
#import "./featureFragment.gql"
+#import "./promptFragment.gql"
fragment projectCreation on ProjectCreation {
presets {
@@ -8,4 +9,7 @@ fragment projectCreation on ProjectCreation {
features {
...feature
}
+ prompts {
+ ...prompt
+ }
}
diff --git a/packages/@vue/cli-ui/src/graphql/promptAnswer.gql b/packages/@vue/cli-ui/src/graphql/promptAnswer.gql
new file mode 100644
index 0000000000..bb28bbb27e
--- /dev/null
+++ b/packages/@vue/cli-ui/src/graphql/promptAnswer.gql
@@ -0,0 +1,7 @@
+#import "./promptFragment.gql"
+
+mutation promptAnswer ($input: PromptInput!) {
+ promptAnswer(input: $input) {
+ ...prompt
+ }
+}
diff --git a/packages/@vue/cli-ui/src/graphql/promptChoiceFragment.gql b/packages/@vue/cli-ui/src/graphql/promptChoiceFragment.gql
new file mode 100644
index 0000000000..85b92804dd
--- /dev/null
+++ b/packages/@vue/cli-ui/src/graphql/promptChoiceFragment.gql
@@ -0,0 +1,6 @@
+fragment promptChoice on PromptChoice {
+ value
+ name
+ checked
+ disabled
+}
diff --git a/packages/@vue/cli-ui/src/graphql/promptErrorFragment.gql b/packages/@vue/cli-ui/src/graphql/promptErrorFragment.gql
new file mode 100644
index 0000000000..719d472cc7
--- /dev/null
+++ b/packages/@vue/cli-ui/src/graphql/promptErrorFragment.gql
@@ -0,0 +1,4 @@
+fragment promptError on PromptError {
+ message
+ link
+}
diff --git a/packages/@vue/cli-ui/src/graphql/promptFragment.gql b/packages/@vue/cli-ui/src/graphql/promptFragment.gql
new file mode 100644
index 0000000000..c761adf9aa
--- /dev/null
+++ b/packages/@vue/cli-ui/src/graphql/promptFragment.gql
@@ -0,0 +1,20 @@
+#import "./promptChoiceFragment.gql"
+#import "./promptErrorFragment.gql"
+
+fragment prompt on Prompt {
+ id
+ enabled
+ type
+ name
+ message
+ description
+ link
+ choices {
+ ...promptChoice
+ }
+ value
+ valueChanged
+ error {
+ ...promptError
+ }
+}
diff --git a/packages/@vue/cli-ui/src/style/imports.styl b/packages/@vue/cli-ui/src/style/imports.styl
index a97d1bf9af..29baddd710 100644
--- a/packages/@vue/cli-ui/src/style/imports.styl
+++ b/packages/@vue/cli-ui/src/style/imports.styl
@@ -1,2 +1,4 @@
@import "~@vue/ui/src/style/imports"
@import "colors"
+@import "vars"
+@import "mixins"
diff --git a/packages/@vue/cli-ui/src/style/main.styl b/packages/@vue/cli-ui/src/style/main.styl
index ba6e8434d2..8e36bd280f 100644
--- a/packages/@vue/cli-ui/src/style/main.styl
+++ b/packages/@vue/cli-ui/src/style/main.styl
@@ -13,13 +13,13 @@ body,
height @width
.actions-bar
- padding 12px
+ padding $padding-item
background $color-light-background
h-box()
box-center()
> *
- space-between-x(12px)
+ space-between-x($padding-item)
&.space-between
justify-content space-between
@@ -31,6 +31,9 @@ body,
justify-content flex-end
.cta-text
- margin 12px
+ margin $padding-item
color lighten($vue-color-dark, 40%)
font-size 18px
+
+.list-item
+ list-item()
diff --git a/packages/@vue/cli-ui/src/style/mixins.styl b/packages/@vue/cli-ui/src/style/mixins.styl
new file mode 100644
index 0000000000..de10c8b9b1
--- /dev/null
+++ b/packages/@vue/cli-ui/src/style/mixins.styl
@@ -0,0 +1,9 @@
+list-item()
+ user-select none
+ cursor pointer
+
+ &.selected
+ background rgba($vue-color-primary, .05)
+
+ &:hover
+ background rgba($vue-color-primary, .1)
diff --git a/packages/@vue/cli-ui/src/style/vars.styl b/packages/@vue/cli-ui/src/style/vars.styl
new file mode 100644
index 0000000000..78f9a06b4f
--- /dev/null
+++ b/packages/@vue/cli-ui/src/style/vars.styl
@@ -0,0 +1 @@
+$padding-item = 12px
diff --git a/packages/@vue/cli-ui/src/views/Home.vue b/packages/@vue/cli-ui/src/views/Home.vue
index 03a759858b..8444cee1ad 100644
--- a/packages/@vue/cli-ui/src/views/Home.vue
+++ b/packages/@vue/cli-ui/src/views/Home.vue
@@ -5,21 +5,17 @@
-
-