feat(ui): Initial app

This commit is contained in:
Guillaume Chau
2018-03-04 00:58:57 +01:00
parent c1516c3ea5
commit 8947a450d5
31 changed files with 1641 additions and 43 deletions
+5
View File
@@ -0,0 +1,5 @@
{
"presets": [
"@vue/app"
]
}
+7
View File
@@ -0,0 +1,7 @@
{
"root": true,
"extends": [
"plugin:vue/essential",
"@vue/standard"
]
}
+22
View File
@@ -0,0 +1,22 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
/live/
+5
View File
@@ -0,0 +1,5 @@
{
"plugins": {
"autoprefixer": {}
}
}
+66
View File
@@ -0,0 +1,66 @@
{
"name": "@vue/cli-ui",
"version": "3.0.0-beta.3",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"graphql-api": "vue-cli-service graphql-api",
"run-graphql-api": "vue-cli-service run-graphql-api"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.0.0-beta.3",
"@vue/cli-plugin-eslint": "^3.0.0-beta.3",
"@vue/cli-service": "^3.0.0-beta.3",
"@vue/eslint-config-standard": "^3.0.0-beta.3",
"@vue/ui": "^0.1.6",
"apollo-cache-inmemory": "^1.0.0",
"apollo-client": "^2.0.1",
"apollo-link": "^1.0.0",
"apollo-link-context": "^1.0.5",
"apollo-link-http": "^1.0.0",
"apollo-link-persisted-queries": "^0.1.0",
"apollo-link-ws": "^1.0.0",
"apollo-upload-client": "^7.0.0-alpha.4",
"apollo-utilities": "^1.0.1",
"graphql": "^0.13.0",
"graphql-tag": "^2.5.0",
"lint-staged": "^6.0.0",
"lowdb": "^1.0.0",
"mkdirp": "^0.5.1",
"shortid": "^2.2.8",
"stylus": "^0.54.5",
"stylus-loader": "^3.0.1",
"subscriptions-transport-ws": "^0.9.5",
"vue": "^2.5.13",
"vue-apollo": "^3.0.0-alpha.1",
"vue-cli-plugin-apollo": "^0.4.1",
"vue-router": "^3.0.1",
"vue-template-compiler": "^2.5.13",
"vuex": "^3.0.1"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
],
"engines": {
"node": ">=8"
},
"publishConfig": {
"access": "public"
},
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.js": [
"vue-cli-service lint",
"git add"
],
"*.vue": [
"vue-cli-service lint",
"git add"
]
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+17
View File
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="shortcut icon" href="<%= webpackConfig.output.publicPath %>favicon.ico">
<title>cli-ui</title>
</head>
<body>
<noscript>
<strong>We're sorry but cli-ui doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
+32
View File
@@ -0,0 +1,32 @@
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</div>
</template>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
}
#nav a {
font-weight: bold;
color: #2c3e50;
}
#nav a.router-link-exact-active {
color: #42b983;
}
</style>
+144
View File
@@ -0,0 +1,144 @@
import { ApolloClient } from 'apollo-client'
import { split } from 'apollo-link'
import { HttpLink } from 'apollo-link-http'
import { createUploadLink } from 'apollo-upload-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { SubscriptionClient, MessageTypes } from 'subscriptions-transport-ws'
import { WebSocketLink } from 'apollo-link-ws'
import { getMainDefinition } from 'apollo-utilities'
import { createPersistedQueryLink } from 'apollo-link-persisted-queries'
import { setContext } from 'apollo-link-context'
function getAuth () {
// get the authentication token from local storage if it exists
const token = localStorage.getItem('apollo-token')
// return the headers to the context so httpLink can read them
return token ? `Bearer ${token}` : ''
}
function restartWebsockets (wsClient) {
// Copy current operations
const operations = Object.assign({}, wsClient.operations)
// Close connection
wsClient.close(true)
// Open a new one
wsClient.connect()
// Push all current operations to the new connection
Object.keys(operations).forEach(id => {
wsClient.sendMessage(
id,
MessageTypes.GQL_START,
operations[id].options
)
})
}
// Create the apollo client
export default function createApolloClient ({ ssr, base, endpoints, persisting }) {
let link
let wsClient
let httpLink = new HttpLink({
// You should use an absolute URL here
uri: base + endpoints.graphql
})
// HTTP Auth header injection
const authLink = setContext((_, { headers }) => ({
headers: {
...headers,
authorization: getAuth()
}
}))
// Concat all the http link parts
httpLink = authLink.concat(httpLink)
if (persisting) {
httpLink = createPersistedQueryLink().concat(httpLink)
}
// Apollo cache
const cache = new InMemoryCache()
if (!ssr) {
// If on the client, recover the injected state
if (typeof window !== 'undefined') {
// eslint-disable-next-line no-underscore-dangle
const state = window.__APOLLO_STATE__
if (state) {
// If you have multiple clients, use `state.<client_id>`
cache.restore(state.defaultClient)
}
}
// Web socket
wsClient = new SubscriptionClient(base.replace(/^https?/i, 'ws' + (process.env.NODE_ENV === 'production' ? 's' : '')) +
endpoints.subscription, {
reconnect: true,
connectionParams: () => ({
'Authorization': getAuth()
})
})
// Create the subscription websocket link
const wsLink = new WebSocketLink(wsClient)
// File upload
const uploadLink = authLink.concat(createUploadLink({
uri: base + endpoints.graphql
}))
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
httpLink = split(
operation => operation.getContext().upload,
uploadLink,
httpLink
)
link = split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query)
return kind === 'OperationDefinition' &&
operation === 'subscription'
},
wsLink,
httpLink
)
} else {
// On the server, we don't want WebSockets
link = httpLink
}
const apolloClient = new ApolloClient({
link,
cache,
// Additional options
...(ssr ? {
// Set this on the server to optimize queries when SSR
ssrMode: true
} : {
// This will temporary disable query force-fetching
ssrForceFetchDelay: 100,
// Apollo devtools
connectToDevTools: process.env.NODE_ENV !== 'production'
})
})
apolloClient.$onLogin = token => {
localStorage.setItem('apollo-token', token)
if (wsClient) restartWebsockets(wsClient)
}
apolloClient.$onLogout = () => {
localStorage.removeItem('apollo-token')
if (wsClient) restartWebsockets(wsClient)
apolloClient.resetStore()
}
return apolloClient
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

@@ -0,0 +1,128 @@
<template>
<div class="apollo-example">
<!-- Cute tiny form -->
<div class="form">
<input
v-model="name"
placeholder="Type a name"
class="input"
>
</div>
<!-- Apollo watched Graphql query -->
<ApolloQuery
:query="require('../graphql/HelloWorld.gql')"
:variables="{ name }"
>
<template slot-scope="{ result: { loading, error, data } }">
<!-- Loading -->
<div v-if="loading" class="loading apollo">Loading...</div>
<!-- Error -->
<div v-else-if="error" class="error apollo">An error occured</div>
<!-- Result -->
<div v-else-if="data" class="result apollo">{{ data.hello }}</div>
<!-- No result -->
<div v-else class="no-result apollo">No result :(</div>
</template>
</ApolloQuery>
<!-- Tchat example -->
<ApolloQuery
:query="require('../graphql/Messages.gql')"
>
<ApolloSubscribeToMore
:document="require('../graphql/MessageAdded.gql')"
:update-query="onMessageAdded"
/>
<div slot-scope="{ result: { data } }">
<template v-if="data">
<div
v-for="message of data.messages"
:key="message.id"
class="message"
>
{{ message.text }}
</div>
</template>
</div>
</ApolloQuery>
<div class="form">
<input
v-model="newMessage"
placeholder="Type a message"
class="input"
@keyup.enter="sendMessage"
>
</div>
</div>
</template>
<script>
import MESSAGE_ADD_MUTATION from '../graphql/MessageAdd.gql'
export default {
data () {
return {
name: 'Anne',
newMessage: ''
}
},
computed: {
formValid () {
return this.newMessage
}
},
methods: {
sendMessage () {
if (this.formValid) {
this.$apollo.mutate({
mutation: MESSAGE_ADD_MUTATION,
variables: {
input: {
text: this.newMessage
}
}
})
this.newMessage = ''
}
},
onMessageAdded (previousResult, { subscriptionData }) {
return {
messages: [
...previousResult.messages,
subscriptionData.data.messageAdded
]
}
}
}
}
</script>
<style scoped>
.form,
.input,
.apollo,
.message {
padding: 12px;
}
.input {
font-family: inherit;
font-size: inherit;
border: solid 2px #ccc;
border-radius: 3px;
}
.error {
color: red;
}
</style>
@@ -0,0 +1,57 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://github.com/vuejs/vue-cli/tree/dev/docs" target="_blank">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank">Twitter</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org/en/essentials/getting-started.html" target="_blank">vue-router</a></li>
<li><a href="https://vuex.vuejs.org/en/intro.html" target="_blank">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org/en" target="_blank">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
@@ -0,0 +1,11 @@
const { db } = require('./utils/db')
const { processUpload } = require('./utils/upload')
// Context passed to all resolvers (third argument)
// eslint-disable-next-line no-unused-vars
module.exports = req => {
return {
db,
processUpload
}
}
@@ -0,0 +1,5 @@
// Enable mocking in vue.config.js with `"pluginOptions": { "graphqlMock": true }`
// Customize mocking: https://www.apollographql.com/docs/graphql-tools/mocking.html#Customizing-mocks
module.exports = {
// Mock resolvers here
}
@@ -0,0 +1,56 @@
const shortid = require('shortid')
module.exports = {
Counter: {
countStr: counter => `Current count: ${counter.count}`
},
Query: {
hello: (root, { name }) => `Hello ${name || 'World'}!`,
messages: (root, args, { db }) => db.get('messages').value(),
uploads: (root, args, { db }) => db.get('uploads').value()
},
Mutation: {
messageAdd: (root, { input }, { pubsub, db }) => {
const message = {
id: shortid.generate(),
text: input.text
}
db
.get('messages')
.push(message)
.last()
.write()
pubsub.publish('messages', { messageAdded: message })
return message
},
singleUpload: (root, { file }, { processUpload }) => processUpload(file),
multipleUpload: (root, { files }, { processUpload }) => Promise.all(files.map(processUpload))
},
Subscription: {
counter: {
subscribe: (parent, args, { pubsub }) => {
const channel = Math.random().toString(36).substring(2, 15) // random channel name
let count = 0
setInterval(() => pubsub.publish(
channel,
{
// eslint-disable-next-line no-plusplus
counter: { count: count++ }
}
), 2000)
return pubsub.asyncIterator(channel)
}
},
messageAdded: {
subscribe: (parent, args, { pubsub }) => pubsub.asyncIterator('messages')
}
}
}
@@ -0,0 +1,54 @@
module.exports = `
# It will increment!
type Counter {
# Number of increments
count: Int!
# Full message for testing
countStr: String
}
# A text message send by users
type Message {
id: ID!
# Message content
text: String!
}
# Input from user to create a message
input MessageInput {
# Message content
text: String!
}
scalar Upload
type File {
id: ID!
path: String!
filename: String!
mimetype: String!
encoding: String!
}
type Query {
# Test query with a parameter
hello(name: String): String!
# List of messages sent by users
messages: [Message]
uploads: [File]
}
type Mutation {
# Add a message and publish it on 'messages' subscription channel
messageAdd (input: MessageInput!): Message!
singleUpload (file: Upload!): File!
multipleUpload (files: [Upload!]!): [File!]!
}
type Subscription {
# This will update every 2 seconds
counter: Counter!
# When a new message is added
messageAdded: Message!
}
`
@@ -0,0 +1,18 @@
const Lowdb = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')
const mkdirp = require('mkdirp')
const { resolve } = require('path')
mkdirp(resolve(__dirname, '../../../live'))
const db = new Lowdb(new FileSync(resolve(__dirname, '../../../live/db.json')))
// Seed an empty DB
db.defaults({
messages: [],
uploads: []
}).write()
module.exports = {
db
}
@@ -0,0 +1,38 @@
const { createWriteStream } = require('fs')
const mkdirp = require('mkdirp')
const shortid = require('shortid')
const { db } = require('./db')
const uploadDir = require('path').resolve(__dirname, '../../../live/uploads')
// Ensure upload directory exists
mkdirp.sync(uploadDir)
const storeUpload = async ({ stream, filename }) => {
const id = shortid.generate()
const path = `${uploadDir}/${id}-${filename}`
return new Promise((resolve, reject) =>
stream
.pipe(createWriteStream(path))
.on('finish', () => resolve({ id, path }))
.on('error', reject)
)
}
const recordFile = file =>
db
.get('uploads')
.push(file)
.last()
.write()
const processUpload = async upload => {
const { stream, filename, mimetype, encoding } = await upload
const { id, path } = await storeUpload({ stream, filename })
return recordFile({ id, filename, mimetype, encoding, path })
}
module.exports = {
processUpload
}
@@ -0,0 +1,3 @@
query HelloWorld ($name: String) {
hello (name: $name)
}
@@ -0,0 +1,7 @@
#import "./MessageFragment.gql"
mutation messageAdd ($input: MessageInput!) {
messageAdd (input: $input) {
...Message
}
}
@@ -0,0 +1,7 @@
#import "./MessageFragment.gql"
subscription messageAdded {
messageAdded {
...Message
}
}
@@ -0,0 +1,4 @@
fragment Message on Message {
id
text
}
@@ -0,0 +1,7 @@
#import "./MessageFragment.gql"
query messages {
messages {
...Message
}
}
+14
View File
@@ -0,0 +1,14 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import { apolloProvider } from './vue-apollo'
Vue.config.productionTip = false
new Vue({
provide: apolloProvider.provide(),
router,
store,
render: h => h(App)
}).$mount('#app')
+21
View File
@@ -0,0 +1,21 @@
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import About from './views/About.vue'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
}
]
})
+16
View File
@@ -0,0 +1,16 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
}
})
+5
View File
@@ -0,0 +1,5 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
+18
View File
@@ -0,0 +1,18 @@
<template>
<div class="home">
<img src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/ApolloExample.vue'
export default {
name: 'home',
components: {
HelloWorld
}
}
</script>
+25
View File
@@ -0,0 +1,25 @@
import Vue from 'vue'
import VueApollo from 'vue-apollo'
import createApolloClient from './apollo'
// Install the vue plugin
Vue.use(VueApollo)
// Config
const options = {
ssr: false,
base: process.env.VUE_APP_GRAPHQL_ENDPOINT || 'http://localhost:4000',
endpoints: {
graphql: process.env.VUE_APP_GRAPHQL_PATH || '/graphql',
subscription: process.env.VUE_APP_GRAPHQL_SUBSCRIPTIONS_PATH || '/graphql'
},
persisting: false
}
// Create apollo client
export const apolloClient = createApolloClient(options)
// Create vue apollo provider
export const apolloProvider = new VueApollo({
defaultClient: apolloClient
})
+8
View File
@@ -0,0 +1,8 @@
module.exports = {
lintOnSave: false,
pluginOptions: {
graphqlMock: true,
apolloEngine: false
}
}
+841 -43
View File
File diff suppressed because it is too large Load Diff