├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── index.html └── src ├── App.vue ├── assets └── logo.png ├── components ├── PersonDetail.vue └── PlanetDetail.vue ├── main.js ├── router.js ├── shared ├── config.js └── ellipsis-filter.js ├── store ├── index.js └── modules │ ├── mutation-types.js │ ├── people.js │ └── planets.js ├── styles.scss └── views ├── About.vue ├── Home.vue ├── PeopleList.vue └── PlanetList.vue /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | '@vue/airbnb', 8 | 'plugin:vue/essential', 9 | '@vue/prettier' 10 | ], 11 | plugins: ['prettier'], 12 | // watch this for explaining why some of this is here 13 | // https://www.youtube.com/watch?time_continue=239&v=YIvjKId9m2c 14 | rules: { 15 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 17 | quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], 18 | 'prettier/prettier': [ 19 | 'error', 20 | { 21 | trailingComma: 'es5', 22 | singleQuote: true, 23 | printWidth: '80' 24 | } 25 | ] 26 | }, 27 | parserOptions: { 28 | parser: 'babel-eslint' 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-intro 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/app"] 3 | }; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-intro", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.18.0", 12 | "vue": "^2.5.17", 13 | "vue-router": "^3.0.1", 14 | "vuex": "^3.0.1" 15 | }, 16 | "devDependencies": { 17 | "@vue/cli-plugin-babel": "^3.0.3", 18 | "@vue/cli-plugin-eslint": "^3.0.3", 19 | "@vue/cli-service": "^3.0.3", 20 | "@vue/eslint-config-airbnb": "^3.0.3", 21 | "@vue/eslint-config-prettier": "^3.0.3", 22 | "node-sass": "^4.9.0", 23 | "sass-loader": "^7.0.1", 24 | "vue-template-compiler": "^2.5.17" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/vue-intro/327e23d37a699b7382c7709cda5a8df74a177caf/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | vue-intro 10 | 11 | 12 | 13 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 34 | 35 | 161 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/vue-intro/327e23d37a699b7382c7709cda5a8df74a177caf/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/PersonDetail.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 87 | 88 | 94 | -------------------------------------------------------------------------------- /src/components/PlanetDetail.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 87 | 88 | 94 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import ellipsisFilter from '@/shared/ellipsis-filter'; 2 | import Vue from 'vue'; 3 | import App from './App.vue'; 4 | import router from './router'; 5 | import store from './store'; 6 | 7 | Vue.config.productionTip = false; 8 | 9 | Vue.filter('ellipsis', ellipsisFilter); 10 | 11 | new Vue({ 12 | router, 13 | store, 14 | render: h => h(App), 15 | }).$mount('#app'); 16 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Home from '@/views/Home.vue'; 2 | // import PeopleList from '@/views/PeopleList.vue'; 3 | // import PlanetList from '@/views/PlanetList.vue'; 4 | import Vue from 'vue'; 5 | import Router from 'vue-router'; 6 | 7 | Vue.use(Router); 8 | 9 | export default new Router({ 10 | mode: 'history', 11 | base: process.env.BASE_URL, 12 | routes: [ 13 | { 14 | path: '/', 15 | name: 'home', 16 | component: Home, 17 | }, 18 | { 19 | path: '/people', 20 | name: 'PeopleList', 21 | // component: PeopleList, 22 | component: () => 23 | import(/* webpackChunkName: "people" */ './views/PeopleList.vue'), 24 | }, 25 | { 26 | path: '/planets', 27 | name: 'PlanetList', 28 | // component: PlanetList, 29 | component: () => 30 | import(/* webpackChunkName: "planets" */ './views/PlanetList.vue'), 31 | }, 32 | { 33 | path: '/about', 34 | name: 'about', 35 | // route level code-splitting 36 | // this generates a separate chunk (about.[hash].js) for this route 37 | // which is lazy-loaded when the route is visited. 38 | component: () => 39 | import(/* webpackChunkName: "about" */ './views/About.vue'), 40 | }, 41 | ], 42 | }); 43 | -------------------------------------------------------------------------------- /src/shared/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | API: 'https://swapi.co/api', 3 | }; 4 | -------------------------------------------------------------------------------- /src/shared/ellipsis-filter.js: -------------------------------------------------------------------------------- 1 | export default function(text, maxlength) { 2 | if (!text || text.length === 0) return ''; 3 | 4 | const shortText = text.substring(0, maxlength); 5 | const dots = shortText.length < text.length ? '...' : ''; 6 | return `${shortText}${dots}`; 7 | } 8 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import peopleModule from './modules/people'; 4 | import planetsModule from './modules/planets'; 5 | 6 | export * from './modules/mutation-types'; 7 | 8 | Vue.use(Vuex); 9 | 10 | export default new Vuex.Store({ 11 | strict: process.env.NODE_ENV !== 'production', 12 | modules: { 13 | people: peopleModule, 14 | planets: planetsModule, 15 | }, 16 | state: {}, 17 | }); 18 | -------------------------------------------------------------------------------- /src/store/modules/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const GET_PEOPLE = 'GET_PEOPLE'; 2 | export const ADD_PERSON = 'ADD_PERSON'; 3 | export const UPDATE_PERSON = 'UPDATE_PERSON'; 4 | export const DELETE_PERSON = 'DELETE_PERSON'; 5 | export const GET_PLANETS = 'GET_PLANETS'; 6 | export const ADD_PLANET = 'ADD_PLANET'; 7 | export const UPDATE_PLANET = 'UPDATE_PLANET'; 8 | export const DELETE_PLANET = 'DELETE_PLANET'; 9 | -------------------------------------------------------------------------------- /src/store/modules/people.js: -------------------------------------------------------------------------------- 1 | import config from '@/shared/config'; 2 | import axios from 'axios'; 3 | import { 4 | ADD_PERSON, 5 | DELETE_PERSON, 6 | GET_PEOPLE, 7 | UPDATE_PERSON, 8 | } from './mutation-types'; 9 | 10 | const { API } = config; 11 | 12 | export default { 13 | strict: process.env.NODE_ENV !== 'production', 14 | namespaced: true, 15 | state: { 16 | people: [], 17 | }, 18 | mutations: { 19 | [ADD_PERSON](state, person) { 20 | state.people.push(person); // mutable addition 21 | }, 22 | [UPDATE_PERSON](state, person) { 23 | state.people = [...state.people.filter(p => p.id !== person.id), person]; 24 | // const index = state.people.findIndex(h => person.id === h.id); 25 | // state.people.splice(index, 1, person); 26 | }, 27 | [GET_PEOPLE](state, people) { 28 | state.people = people; 29 | }, 30 | [DELETE_PERSON](state, person) { 31 | state.people = state.people.filter(p => p.id !== person.id); 32 | }, 33 | }, 34 | actions: { 35 | // actions let us get to ({ state, getters, commit, dispatch }) { 36 | getPeople({ commit }) { 37 | let index = 1; 38 | return axios 39 | .get(`${API}/people`) 40 | .then(response => { 41 | const people = response.data.results.map(p => { 42 | p.id = index++; 43 | return p; 44 | }); 45 | commit(GET_PEOPLE, people); 46 | return people; 47 | }) 48 | .catch(console.error); 49 | }, 50 | }, 51 | getters: { 52 | // selectors 53 | people: state => state.people, 54 | sortedPeople(state) { 55 | return [...state.people].sort((a, b) => a.id - b.id); 56 | }, 57 | }, 58 | }; 59 | -------------------------------------------------------------------------------- /src/store/modules/planets.js: -------------------------------------------------------------------------------- 1 | import config from '@/shared/config'; 2 | import axios from 'axios'; 3 | import { 4 | ADD_PLANET, 5 | DELETE_PLANET, 6 | GET_PLANETS, 7 | UPDATE_PLANET, 8 | } from './mutation-types'; 9 | 10 | const { API } = config; 11 | 12 | export default { 13 | strict: process.env.NODE_ENV !== 'production', 14 | namespaced: true, 15 | state: { 16 | planets: [], 17 | }, 18 | mutations: { 19 | [ADD_PLANET](state, planet) { 20 | // state.planets.push(planet); // mutable addition 21 | state.planets = [...state.planets, planet]; // replace the array, still mutating state 22 | }, 23 | [UPDATE_PLANET](state, planet) { 24 | state.planets = [ 25 | ...state.planets.filter(p => p.id !== planet.id), 26 | planet, 27 | ]; 28 | // const index = this.planets.findIndex(h => planet.id === h.id); 29 | // this.planets.splice(index, 1, planet); 30 | }, 31 | [GET_PLANETS](state, planets) { 32 | state.planets = planets; 33 | }, 34 | [DELETE_PLANET](state, planet) { 35 | state.planets = state.planets.filter(p => p.id !== planet.id); 36 | }, 37 | }, 38 | actions: { 39 | // actions let us get to ({ state, getters, commit, dispatch }) { 40 | getPlanets({ commit }) { 41 | let index = 1; 42 | return axios 43 | .get(`${API}/planets`) 44 | .then(response => { 45 | const planets = response.data.results.map(p => { 46 | p.id = index++; 47 | return p; 48 | }); 49 | commit(GET_PLANETS, planets); 50 | return planets; 51 | }) 52 | .catch(console.error); 53 | }, 54 | }, 55 | getters: { 56 | // selectors 57 | planets: state => state.people, 58 | sortedPlanets(state) { 59 | return [...state.planets].sort((a, b) => a.id - b.id); 60 | }, 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | @mixin button-group { 2 | margin: 0.5em; 3 | > button { 4 | margin-right: 1em; 5 | } 6 | } 7 | @mixin button-delete { 8 | background-color: rgb(216, 59, 1); 9 | color: white; 10 | padding: 4px; 11 | position: relative; 12 | font-size: 12px; 13 | max-width: 50px; 14 | } 15 | @mixin selected-item { 16 | background-color: rgb(0, 120, 215) !important; 17 | color: white; 18 | } 19 | @mixin list { 20 | float: left; 21 | margin: 0 0 2em 0; 22 | list-style-type: none; 23 | padding: 0; 24 | width: 20em; 25 | li { 26 | cursor: pointer; 27 | position: relative; 28 | background-color: #f7f7f7; 29 | margin: 0.5em; 30 | height: 3.2em; 31 | border-radius: 4px; 32 | &:hover { 33 | color: #607d8b; 34 | color: rgb(0, 120, 215); 35 | background-color: #ddd; 36 | left: 1px; 37 | } 38 | &.selected:hover { 39 | color: white; 40 | } 41 | } 42 | .text { 43 | position: relative; 44 | top: -3px; 45 | } 46 | .sub-text { 47 | margin: 5px 2.3px; 48 | } 49 | .name { 50 | font-weight: bold; 51 | } 52 | .list-container { 53 | display: flex; 54 | flex-flow: row wrap; 55 | } 56 | > * { 57 | flex: 1 100%; 58 | } 59 | .list-element { 60 | display: flex; 61 | flex-flow: row wrap; 62 | flex: 18 auto; 63 | order: 1; 64 | padding: 0; 65 | margin: 0; 66 | } 67 | .delete-button { 68 | flex: 1 auto; 69 | order: 2; 70 | border-radius: 0 4px 4px 0; 71 | } 72 | .item-text { 73 | flex: 1 auto; 74 | order: 2; 75 | padding: 0.2em 0.5em; 76 | } 77 | .badge { 78 | flex: 1 auto; 79 | order: 1; 80 | font-size: small; 81 | color: #ffffff; 82 | padding: 1.2em 1em 0em 1em; 83 | background-color: #607d8b; 84 | background-color: rgb(0, 120, 215); 85 | background-color: rgb(134, 183, 221); 86 | margin: 0em 0em 0em 0em; 87 | border-radius: 4px 0 0 4px; 88 | max-width: 1.5em; 89 | } 90 | } 91 | 92 | @mixin editarea { 93 | float: left; 94 | input { 95 | margin: 4px; 96 | height: 20px; 97 | color: rgb(0, 120, 215); 98 | } 99 | button { 100 | margin: 8px; 101 | } 102 | .editfields { 103 | margin-left: 12px; 104 | } 105 | } -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/PeopleList.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 80 | 81 | 97 | -------------------------------------------------------------------------------- /src/views/PlanetList.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 71 | 72 | 88 | --------------------------------------------------------------------------------