feat(ui): wip plugins list

This commit is contained in:
Guillaume Chau
2018-03-11 19:36:23 +01:00
parent 601fb1f08f
commit b9a714c32a
40 changed files with 501 additions and 151 deletions
+2 -1
View File
@@ -5,7 +5,8 @@
'validate',
'openBrowser',
'pluginResolution',
'exit'
'exit',
'request'
].forEach(m => {
Object.assign(exports, require(`./lib/${m}`))
})
@@ -1,7 +1,7 @@
const request = require('request-promise-native')
module.exports = {
async get (uri) {
exports.request = {
get (uri) {
const reqOpts = {
method: 'GET',
resolveWithFullResponse: true,
+1
View File
@@ -11,6 +11,7 @@
"dependencies": {
"graphql": "^0.13.0",
"lowdb": "^1.0.0",
"lru-cache": "^4.1.2",
"mkdirp": "^0.5.1",
"rimraf": "^2.6.2",
"shortid": "^2.2.8"
@@ -0,0 +1,53 @@
<template>
<div class="content-view">
<div class="header">
<h2 v-if="title" class="title">{{ title }}</h2>
<slot name="header"/>
</div>
<div class="content">
<slot/>
</div>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: false
}
}
}
</script>
<style lang="stylus" scoped>
@import "~@/style/imports"
.content-view
height 100%
display grid
grid-template-columns 1fr
grid-template-rows auto 1fr
grid-template-areas "header" "content"
.header
grid-area header
h-box()
box-center()
background $vue-color-light-neutral
padding $padding-item
.title
flex 100% 1 1
width 0
margin 0
>>> > *
space-between-x($padding-item)
.content
grid-area content
position relative
</style>
@@ -101,7 +101,6 @@
</template>
<script>
import FolderExplorerItem from './FolderExplorerItem'
import FOLDER_CURRENT from '../graphql/folderCurrent.gql'
import FOLDERS_FAVORITE from '../graphql/foldersFavorite.gql'
import FOLDER_OPEN from '../graphql/folderOpen.gql'
@@ -110,10 +109,6 @@ import FOLDER_SET_FAVORITE from '../graphql/folderSetFavorite.gql'
import PROJECT_CWD_RESET from '../graphql/projectCwdReset.gql'
export default {
components: {
FolderExplorerItem,
},
data () {
return {
error: false,
@@ -6,8 +6,10 @@
}"
>
<div class="name">{{ name }}</div>
<div v-if="description || link" class="description">
{{ description }}
<div v-if="description || link || showDescription" class="description">
<slot name="description">
{{ description }}
</slot>
<a
v-if="link"
:href="link"
@@ -41,6 +43,11 @@ export default {
selected: {
type: Boolean,
default: false
},
showDescription: {
type: Boolean,
default: false
}
}
}
@@ -57,6 +64,10 @@ export default {
.description
color $color-text-light
>>> .vue-icon
svg
fill @color
&.selected
.name
color $vue-color-primary
@@ -65,17 +65,11 @@
</template>
<script>
import LoggerMessage from './LoggerMessage'
import CONSOLE_LOGS from '../graphql/consoleLogs.gql'
import CONSOLE_LOG_LAST from '../graphql/consoleLogLast.gql'
import CONSOLE_LOGS_CLEAR from '../graphql/consoleLogsClear.gql'
export default {
components: {
LoggerMessage
},
methods: {
onConsoleLogAdded (previousResult, { subscriptionData }) {
this.scrollToBottom()
@@ -20,13 +20,7 @@
</template>
<script>
import ListItemInfo from './ListItemInfo'
export default {
components: {
ListItemInfo
},
props: {
feature: {
type: Object,
@@ -7,22 +7,12 @@
indicator
>
<VueGroupButton
v-for="route of routes"
:key="route.name"
class="flat big icon-button"
value="plugins"
icon-left="widgets"
v-tooltip.right="'Plugins'"
/>
<VueGroupButton
class="flat big icon-button"
value="config"
icon-left="settings_applications"
v-tooltip.right="'Configurations'"
/>
<VueGroupButton
class="flat big icon-button"
value="tasks"
icon-left="assignment"
v-tooltip.right="'Tasks'"
:value="route.name"
:icon-left="route.icon"
v-tooltip.right="route.tooltip"
/>
</VueGroup>
</div>
@@ -30,10 +20,50 @@
</template>
<script>
const BUILTIN_ROUTES = [
{
name: 'project-plugins',
icon: 'widgets',
tooltip: 'Plugins'
},
{
name: 'project-configuration',
icon: 'settings_applications',
tooltip: 'Configuration'
},
{
name: 'project-tasks',
icon: 'assignment',
tooltip: 'Tasks'
}
]
export default {
data () {
return {
currentRoute: 'plugins'
currentRoute: null,
routes: [
...BUILTIN_ROUTES
// Plugins routes here
// TODO
]
}
},
watch: {
currentRoute (name) {
if (this.$route.name !== name) {
this.$router.push({ name })
}
},
'$route.name': {
handler (value) {
if (value !== this.currentRoute) {
this.currentRoute = value
}
},
immediate: true
}
}
}
@@ -0,0 +1,79 @@
<template>
<div class="project-plugin-item list-item">
<ListItemInfo
:name="plugin.id"
:link="plugin.website"
show-description
>
<span slot="description" class="plugin-description">
<span class="info version">
<span class="label">version</span>
<span class="value">{{ plugin.version.current }}</span>
</span>
<span class="info latest">
<span class="label">latest</span>
<VueIcon
v-if="plugin.version.current !== plugin.version.latest"
icon="warning"
class="top medium"
/>
<span class="value">{{ plugin.version.latest }}</span>
</span>
<span v-if="plugin.official" class="info">
<VueIcon
icon="star"
class="top medium"
/>
Official
</span>
<span v-if="plugin.installed" class="info">
<VueIcon
icon="check_circle"
class="top medium"
/>
Installed
</span>
<span v-if="plugin.description" class="package-description">
{{ plugin.description }}
</span>
</span>
</ListItemInfo>
</div>
</template>
<script>
export default {
props: {
plugin: {
type: Object,
required: true
}
}
}
</script>
<style lang="stylus" scoped>
@import "~@/style/imports"
.project-plugin-item
padding $padding-item
.plugin-description
margin-right $padding-item
.label
opacity .7
.info
space-between-x($padding-item)
>>> > *
space-between-x(4px)
.package-description
font-style italic
opacity .7
</style>
@@ -26,13 +26,7 @@
</template>
<script>
import ListItemInfo from './ListItemInfo'
export default {
components: {
ListItemInfo
},
props: {
preset: {
type: Object,
@@ -27,17 +27,11 @@
</template>
<script>
import ProjectSelectListItem from './ProjectSelectListItem'
import PROJECTS from '../graphql/projects.gql'
import PROJECT_OPEN from '../graphql/projectOpen.gql'
import PROJECT_REMOVE from '../graphql/projectRemove.gql'
export default {
components: {
ProjectSelectListItem
},
methods: {
async openProject (project) {
await this.$apollo.mutate({
@@ -47,7 +41,7 @@ export default {
}
})
this.$router.push({ name: 'home' })
this.$router.push({ name: 'project-home' })
},
async removeProject (project) {
@@ -28,13 +28,7 @@
</template>
<script>
import ListItemInfo from './ListItemInfo'
export default {
components: {
ListItemInfo
},
props: {
project: {
type: Object,
@@ -62,9 +62,6 @@
</template>
<script>
import LoggerMessage from '../components/LoggerMessage'
import LoggerView from '../components/LoggerView'
import PROJECT_CURRENT from '../graphql/projectCurrent.gql'
import CONSOLE_LOG_LAST from '../graphql/consoleLogLast.gql'
import CONSOLE_LOG_ADDED from '../graphql/consoleLogAdded.gql'
@@ -72,11 +69,6 @@ import CONSOLE_LOG_ADDED from '../graphql/consoleLogAdded.gql'
let lastRoute
export default {
components: {
LoggerMessage,
LoggerView
},
data () {
return {
consoleLogLast: null,
@@ -110,7 +102,7 @@ export default {
onProjectClick () {
this.$emit('project')
if (this.$route.name === 'project-select') {
this.$router.push(lastRoute || { name: 'home' })
this.$router.push(lastRoute || { name: 'project-home' })
} else {
if (this.$route.name === 'project-create') {
lastRoute = null
@@ -1,6 +1,12 @@
const path = require('path')
const fs = require('fs')
const rimraf = require('rimraf')
const LRU = require('lru-cache')
const pkgCache = new LRU({
max: 500,
maxAge: 1000 * 5
})
const cwd = require('./cwd')
@@ -52,14 +58,22 @@ function isPackage (file, context) {
}
function readPackage (file, context) {
return fs.readFileSync(path.join(file, 'package.json'), { encoding: 'utf8' })
const cachedValue = pkgCache.get(file)
if (cachedValue) {
return cachedValue
}
const pkg = JSON.parse(
fs.readFileSync(path.join(file, 'package.json'), { encoding: 'utf8' })
)
pkgCache.set(file, pkg)
return pkg
}
function isVueProject (file, context) {
if (!isPackage(file)) return false
const contents = readPackage(file)
return contents.includes('@vue/cli-service')
const pkg = readPackage(file, context)
return Object.keys(pkg.devDependencies || {}).includes('@vue/cli-service')
}
function listFavorite (context) {
@@ -0,0 +1,96 @@
const path = require('path')
const fs = require('fs')
const LRU = require('lru-cache')
const { isPlugin, isOfficialPlugin, getPluginLink } = require('@vue/cli-shared-utils')
const getPackageVersion = require('@vue/cli/lib/util/getPackageVersion')
const cwd = require('./cwd')
const folders = require('./folders')
const metadataCache = new LRU({
max: 200,
maxAge: 1000 * 60 * 30
})
function getPath (id) {
return path.join(cwd.get(), 'node_modules', id)
}
function findPlugins (deps) {
return Object.keys(deps).filter(
key => isPlugin(key)
).map(
id => ({
id,
versionRange: deps[id],
official: isOfficialPlugin(id),
installed: fs.existsSync(getPath(id)),
website: getPluginLink(id)
})
)
}
function list (file, context) {
const pkg = folders.readPackage(file, context)
let plugins = []
plugins = plugins.concat(findPlugins(pkg.dependencies || {}))
plugins = plugins.concat(findPlugins(pkg.devDependencies || {}))
return plugins
}
function readPackage (id, context) {
return folders.readPackage(getPath(id), context)
}
async function getMetadata (id) {
let metadata = metadataCache.get(id)
if (metadata) {
return metadata
}
const res = await getPackageVersion(id)
if (res.statusCode === 200) {
metadata = res.body
metadataCache.set(id, metadata)
return metadata
}
}
async function getVersion ({ id, installed, versionRange }, context) {
let current
if (installed) {
const pkg = readPackage(id, context)
current = pkg.version
} else {
current = null
}
let latest
const metadata = await getMetadata(id)
if (metadata) {
latest = metadata['dist-tags'].latest
}
if (!latest) {
// fallback to local version
latest = current
}
return {
current,
latest,
range: versionRange
}
}
async function getDescription ({ id }, context) {
const metadata = await getMetadata(id)
if (metadata) {
return metadata.description
}
return null
}
module.exports = {
list,
getVersion,
getDescription
}
@@ -284,7 +284,7 @@ async function importProject (input, context) {
favorite: 0
}
const packageData = JSON.parse(folders.readPackage(project.path, context))
const packageData = folders.readPackage(project.path, context)
project.name = packageData.name
context.db.get('projects').push(project).write()
@@ -317,11 +317,6 @@ async function remove (id, context) {
return true
}
async function getPlugins (id, context) {
// TODO
return []
}
function resetCwd (context) {
if (currentProject) {
cwd.set(currentProject.path, context)
@@ -339,6 +334,5 @@ module.exports = {
import: importProject,
open,
remove,
getPlugins,
resetCwd
}
@@ -9,6 +9,7 @@ const folders = require('./connectors/folders')
const projects = require('./connectors/projects')
const progress = require('./connectors/progress')
const logs = require('./connectors/logs')
const plugins = require('./connectors/plugins')
// Prevent code from exiting server process
exit.exitProcess = false
@@ -23,7 +24,12 @@ module.exports = {
},
Project: {
plugins: (project, args, context) => projects.getPlugins(project.id, context)
plugins: (project, args, context) => plugins.list(project.path, context)
},
Plugin: {
version: (plugin, args, context) => plugins.getVersion(plugin, context),
description: (plugin, args, context) => plugins.getDescription(plugin, context)
},
Query: {
@@ -69,6 +69,7 @@ type ProjectCreation {
type Version {
current: String!
latest: String
range: String
}
type GitHubStats {
@@ -86,10 +87,6 @@ type Plugin {
prompts: [Prompt]
}
input PluginSearchInput {
terms: String!
}
type Feature {
id: ID!
name: String!
@@ -159,7 +156,6 @@ type Query {
projects: [Project]
projectCurrent: Project
projectCreation: ProjectCreation
pluginSearch (input: PluginSearchInput!): [Plugin]
}
type Mutation {
@@ -3,6 +3,7 @@ fragment plugin on Plugin {
version {
current
latest
range
}
official
installed
@@ -1,7 +1,7 @@
#import "./projectCurrentFragment.gql"
#import "./projectFragment.gql"
mutation projectCreate ($input: ProjectCreateInput!) {
projectCreate(input: $input) {
...projectCurrent
...project
}
}
@@ -1,7 +1,7 @@
#import "./projectCurrentFragment.gql"
#import "./projectFragment.gql"
query projectCurrent {
projectCurrent {
...projectCurrent
...project
}
}
@@ -1,9 +0,0 @@
#import "./projectFragment.gql"
#import "./pluginFragment.gql"
fragment projectCurrent on Project {
...project
plugins {
...plugin
}
}
@@ -1,7 +1,7 @@
#import "./projectCurrentFragment.gql"
#import "./projectFragment.gql"
mutation projectImport ($input: ProjectImportInput!) {
projectImport(input: $input) {
...projectCurrent
...project
}
}
@@ -1,7 +1,7 @@
#import "./projectCurrentFragment.gql"
#import "./projectFragment.gql"
mutation projectOpen ($id: ID!) {
projectOpen(id: $id) {
...projectCurrent
...project
}
}
@@ -0,0 +1,10 @@
#import "./pluginFragment.gql"
query projectPlugins {
projectCurrent {
id
plugins {
...plugin
}
}
}
+2 -1
View File
@@ -4,6 +4,7 @@ import router from './router'
import { apolloProvider } from './vue-apollo'
import VueUi from '@vue/ui'
import * as Filters from './filters'
import './register-components'
Vue.use(VueUi)
@@ -16,7 +17,7 @@ Vue.config.productionTip = false
const app = new Vue({
provide: apolloProvider.provide(),
router,
render: h => h(App)
...App
})
async function start () {
@@ -0,0 +1,35 @@
/**
* We register all the components so future cli-ui plugins
* could use them directly
*/
import Vue from 'vue'
// To extract the component name
const nameReg = /([a-z0-9]+)\./i
function registerGlobalComponents (components) {
components.keys().forEach(key => {
const name = key.match(nameReg)[1]
Vue.component(name, {
name,
...components(key).default
})
})
}
// Require all the components that start with 'BaseXXX.vue'
let components = require.context('./components', true, /[a-z0-9]+\.(jsx?|vue)$/i)
registerGlobalComponents(components)
// Webpack HMR
if (module.hot) {
module.hot.accept(components.id, () => {
try {
const components = require.context('./components', true, /[a-z0-9]+\.(jsx?|vue)$/i)
registerGlobalComponents(components)
} catch (e) {
location.reload()
}
})
}
+28 -4
View File
@@ -2,7 +2,10 @@ import Vue from 'vue'
import Router from 'vue-router'
import { apolloClient } from './vue-apollo'
import Home from './views/Home.vue'
import ProjectHome from './views/ProjectHome.vue'
import ProjectPlugins from './views/ProjectPlugins.vue'
import ProjectConfiguration from './views/ProjectConfiguration.vue'
import ProjectTasks from './views/ProjectTasks.vue'
import ProjectSelect from './views/ProjectSelect.vue'
import ProjectCreate from './views/ProjectCreate.vue'
import About from './views/About.vue'
@@ -16,11 +19,32 @@ const router = new Router({
routes: [
{
path: '/',
name: 'home',
component: Home,
component: ProjectHome,
meta: {
needProject: true
}
},
children: [
{
path: '',
name: 'project-home',
redirect: { name: 'project-plugins' }
},
{
path: 'plugins',
name: 'project-plugins',
component: ProjectPlugins
},
{
path: 'configuration',
name: 'project-configuration',
component: ProjectConfiguration
},
{
path: 'tasks',
name: 'project-tasks',
component: ProjectTasks
}
]
},
{
path: '/project/select',
@@ -0,0 +1,9 @@
<template>
<div class="project-configuration page">
<ContentView
title="Project configuration"
>
WIP
</ContentView>
</div>
</template>
@@ -249,7 +249,7 @@
lazy
>
<div class="content vue-disable-scroll">
<PrompsList
<PromptsList
:prompts="enabledPrompts"
@answer="answerPrompt"
/>
@@ -365,12 +365,6 @@
</template>
<script>
import ProgressScreen from '../components/ProgressScreen'
import ProjectFeatureItem from '../components/ProjectFeatureItem'
import ProjectPresetItem from '../components/ProjectPresetItem'
import PrompsList from '../components/PromptsList'
import StepWizard from '../components/StepWizard'
import CWD from '../graphql/cwd.gql'
import PROJECT_CREATION from '../graphql/projectCreation.gql'
import FEATURE_SET_ENABLED from '../graphql/featureSetEnabled.gql'
@@ -380,8 +374,8 @@ import PROJECT_CREATE from '../graphql/projectCreate.gql'
function formDataFactory () {
return {
folder: 'test-app',
force: true,
folder: '',
force: false,
packageManager: undefined,
selectedPreset: null,
remotePreset: {
@@ -395,13 +389,7 @@ function formDataFactory () {
let formData = formDataFactory()
export default {
components: {
ProgressScreen,
ProjectFeatureItem,
ProjectPresetItem,
PrompsList,
StepWizard
},
name: 'ProjectCreate',
data () {
return {
@@ -538,7 +526,7 @@ export default {
}
}
})
this.$router.push({ name: 'home' })
this.$router.push({ name: 'project-home' })
this.reset()
} catch(e) {
console.error(e)
@@ -1,5 +1,5 @@
<template>
<div class="home page">
<div class="project-home page">
<ProjectNav/>
<div class="content">
@@ -9,21 +9,15 @@
</template>
<script>
import ProjectNav from '../components/ProjectNav'
export default {
name: 'home',
components: {
ProjectNav
}
name: 'ProjectHome',
}
</script>
<style lang="stylus" scoped>
@import "~@/style/imports"
.home
.project-home
display grid
grid-template-columns 46px 1fr
grid-template-rows auto
@@ -0,0 +1,41 @@
<template>
<div class="project-plugins page">
<ContentView
title="Project plugins"
>
<template slot="header">
<VueButton
icon-left="add"
label="Add plugin"
class="primary"
/>
</template>
<ApolloQuery
:query="require('../graphql/projectPlugins.gql')"
fetch-policy="cache-and-network"
>
<template slot-scope="{ result: { data, loading } }">
<VueLoadingIndicator
v-if="loading"
class="overlay"
/>
<div v-else-if="data" class="plugins">
<ProjectPluginItem
v-for="plugin of data.projectCurrent.plugins"
:key="plugin.id"
:plugin="plugin"
/>
</div>
</template>
</ApolloQuery>
</ContentView>
</div>
</template>
<script>
export default {
name: 'ProjectPlugins'
}
</script>
@@ -57,19 +57,11 @@
</template>
<script>
import FolderExplorer from '../components/FolderExplorer'
import ProjectSelectList from '../components/ProjectSelectList'
import StepWizard from '../components/StepWizard'
import FOLDER_CURRENT from '../graphql/folderCurrent.gql'
import PROJECT_IMPORT from '../graphql/projectImport.gql'
export default {
components: {
FolderExplorer,
ProjectSelectList,
StepWizard
},
name: 'ProjectSelect',
data () {
return {
@@ -99,7 +91,7 @@ export default {
}
})
this.$router.push({ name: 'home' })
this.$router.push({ name: 'project-home' })
}
}
}
@@ -0,0 +1,9 @@
<template>
<div class="project-tasks page">
<ContentView
title="Project tasks"
>
WIP
</ContentView>
</div>
</template>
@@ -1,8 +1,8 @@
module.exports = function fetchRemotePreset (name, clone) {
// github shorthand fastpath
if (!clone && /^[\w_-]+\/[\w_-]+$/.test(name)) {
const { get } = require('./request')
return get(`https://raw.githubusercontent.com/${name}/master/preset.json`)
const { request } = require('@vue/cli-shared-utils')
return request.get(`https://raw.githubusercontent.com/${name}/master/preset.json`)
.then(res => res.body)
}
@@ -0,0 +1,16 @@
const { request } = require('@vue/cli-shared-utils')
module.exports = async function getPackageVersion (id, range = '') {
const options = require('../options').loadOptions()
const registry = options.useTaobaoRegistry
? `https://registry.npm.taobao.org`
: `https://registry.npmjs.org`
let result
try {
result = await request.get(`${registry}/${encodeURIComponent(id).replace(/^%40/, '@')}/${range}`)
} catch (e) {
return e
}
return result
}
+2 -7
View File
@@ -8,13 +8,8 @@ module.exports = async function getVersions () {
// test/debug, use local version
latest = process.env.VUE_CLI_LATEST_VERSION = current
} else {
const request = require('./request')
const options = require('../options').loadOptions()
const registry = options.useTaobaoRegistry
? `https://registry.npm.taobao.org`
: `https://registry.npmjs.org`
const res = await request.get(`${registry}/vue-cli-version-marker/latest`)
const getPackageVersion = require('./getPackageVersion')
const res = await getPackageVersion('vue-cli-version-marker', 'latest')
if (res.statusCode === 200) {
latest = process.env.VUE_CLI_LATEST_VERSION = res.body.version
} else {
+1 -2
View File
@@ -1,11 +1,10 @@
const EventEmitter = require('events')
const request = require('./request')
const chalk = require('chalk')
const execa = require('execa')
const readline = require('readline')
const inquirer = require('inquirer')
const { loadOptions, saveOptions } = require('../options')
const { pauseSpinner, resumeSpinner } = require('@vue/cli-shared-utils')
const { request, pauseSpinner, resumeSpinner } = require('@vue/cli-shared-utils')
const debug = require('debug')('vue-cli:install')
+7
View File
@@ -6939,6 +6939,13 @@ lru-cache@^4.0.1, lru-cache@^4.1.1:
pseudomap "^1.0.2"
yallist "^2.1.2"
lru-cache@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.2.tgz#45234b2e6e2f2b33da125624c4664929a0224c3f"
dependencies:
pseudomap "^1.0.2"
yallist "^2.1.2"
lru-cache@~2.6.5:
version "2.6.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5"