├── chapter08 ├── .meteor │ ├── .gitignore │ ├── release │ ├── platforms │ ├── .id │ ├── .finished-upgraders │ ├── packages │ └── versions ├── .vueignore ├── .gitignore ├── index.html ├── lib │ ├── collections.js │ └── methods.js ├── server │ └── publications.js ├── client │ ├── components │ │ ├── App.vue │ │ ├── ProductionIndicator.vue │ │ ├── ProductionGenerator.vue │ │ └── ProductionDashboard.vue │ ├── main.js │ └── router.js └── package.json ├── Chapter07 ├── chapter7-full │ ├── browserslist │ ├── i18n │ │ ├── index.js │ │ └── locales │ │ │ ├── en.js │ │ │ ├── es.js │ │ │ ├── de.js │ │ │ └── fr.js │ ├── src │ │ ├── styles │ │ │ ├── imports.styl │ │ │ ├── vars.styl │ │ │ ├── mixins.styl │ │ │ └── transitions.styl │ │ ├── plugins.js │ │ ├── utils │ │ │ ├── http.js │ │ │ ├── i18n.js │ │ │ └── animations.js │ │ ├── components │ │ │ ├── __snapshots__ │ │ │ │ └── BaseButton.spec.js.snap │ │ │ ├── AppFooter.vue │ │ │ ├── PageCheckout.vue │ │ │ ├── PageNotFound.vue │ │ │ ├── BaseLoading.vue │ │ │ ├── App.vue │ │ │ ├── BasePage.vue │ │ │ ├── BaseImage.vue │ │ │ ├── BaseButton.spec.js │ │ │ ├── PageHome.vue │ │ │ ├── StoreCart.vue │ │ │ ├── PageLocale.vue │ │ │ ├── StoreItemInfos.vue │ │ │ └── BasePane.vue │ │ ├── components.js │ │ ├── entry-client.js │ │ ├── store │ │ │ ├── ui.js │ │ │ ├── index.js │ │ │ └── items.js │ │ ├── filters.js │ │ ├── mixins │ │ │ └── discount.js │ │ ├── app.js │ │ ├── entry-server.js │ │ └── router.js │ ├── postcss.config.js │ ├── .gitignore │ ├── index.template.html │ ├── webpack │ │ ├── spa.js │ │ ├── server.js │ │ └── client.js │ ├── index.html │ ├── .babelrc │ ├── jest.config.js │ ├── README.md │ ├── .eslintrc.js │ └── db.json └── chapter7-download │ ├── src │ ├── styles │ │ ├── imports.styl │ │ ├── vars.styl │ │ ├── mixins.styl │ │ └── transitions.styl │ ├── plugins.js │ ├── utils │ │ ├── http.js │ │ └── animations.js │ ├── components │ │ ├── AppFooter.vue │ │ ├── PageCheckout.vue │ │ ├── PageNotFound.vue │ │ ├── BaseLoading.vue │ │ ├── App.vue │ │ ├── BasePage.vue │ │ ├── BaseImage.vue │ │ ├── PageHome.vue │ │ ├── StoreCart.vue │ │ ├── PageLocale.vue │ │ ├── StoreItemInfos.vue │ │ └── BasePane.vue │ ├── components.js │ ├── store │ │ ├── ui.js │ │ ├── index.js │ │ └── items.js │ ├── filters.js │ ├── mixins │ │ └── discount.js │ ├── main.js │ └── router.js │ ├── locales │ ├── de.js │ ├── en.js │ ├── es.js │ └── fr.js │ ├── server.js │ └── db.json ├── Chapter05 ├── chapter5-full │ ├── client │ │ ├── src │ │ │ ├── state.js │ │ │ ├── style │ │ │ │ ├── imports.styl │ │ │ │ ├── vars.styl │ │ │ │ ├── transitions.styl │ │ │ │ └── mixins.styl │ │ │ ├── components │ │ │ │ ├── Loading.vue │ │ │ │ ├── Home.vue │ │ │ │ ├── NotFound.vue │ │ │ │ ├── AppLayout.vue │ │ │ │ ├── TicketsLayout.vue │ │ │ │ ├── FAQ.vue │ │ │ │ ├── NavMenu.vue │ │ │ │ ├── Tickets.vue │ │ │ │ ├── SmartForm.vue │ │ │ │ ├── Ticket.vue │ │ │ │ └── NewTicket.vue │ │ │ ├── filters.js │ │ │ ├── plugins │ │ │ │ ├── state.js │ │ │ │ └── fetch.js │ │ │ ├── assets │ │ │ │ └── logo.svg │ │ │ ├── global-components.js │ │ │ ├── main.js │ │ │ ├── mixins │ │ │ │ ├── PersistantData.js │ │ │ │ └── RemoteData.js │ │ │ └── router.js │ │ ├── .babelrc │ │ ├── .gitignore │ │ ├── .editorconfig │ │ ├── index.html │ │ ├── README.md │ │ ├── package.json │ │ └── webpack.config.js │ └── server │ │ ├── README.md │ │ ├── src │ │ ├── connectors │ │ │ ├── questions.js │ │ │ ├── tickets.js │ │ │ └── users.js │ │ ├── utils │ │ │ ├── promise.js │ │ │ └── cache.js │ │ ├── auth.js │ │ └── index.js │ │ └── package.json └── chapter5-download │ ├── style │ ├── imports.styl │ ├── vars.styl │ ├── transitions.styl │ └── mixins.styl │ └── assets │ └── logo.svg ├── Chapter06 ├── chapter6-full │ ├── server │ │ ├── README.md │ │ ├── src │ │ │ ├── providers.js │ │ │ ├── utils │ │ │ │ ├── promise.js │ │ │ │ └── cache.js │ │ │ ├── config.js │ │ │ ├── connectors │ │ │ │ └── users.js │ │ │ ├── auth.js │ │ │ ├── socket.js │ │ │ └── index.js │ │ └── package.json │ └── client │ │ ├── src │ │ ├── styles │ │ │ ├── imports.styl │ │ │ ├── vars.styl │ │ │ ├── transitions.styl │ │ │ └── mixins.styl │ │ ├── filters.js │ │ ├── components │ │ │ ├── content │ │ │ │ ├── NoContent.vue │ │ │ │ ├── PlaceDetails.vue │ │ │ │ ├── Comment.vue │ │ │ │ ├── BlogContent.vue │ │ │ │ ├── LocationInfo.vue │ │ │ │ └── CreatePost.vue │ │ │ ├── App.vue │ │ │ ├── GeoBlog.vue │ │ │ ├── NotFound.vue │ │ │ ├── AppMenu.vue │ │ │ └── Login.vue │ │ ├── main.js │ │ ├── plugins │ │ │ └── fetch.js │ │ ├── router.js │ │ └── store │ │ │ ├── maps.js │ │ │ └── index.js │ │ ├── .babelrc │ │ ├── .gitignore │ │ ├── .editorconfig │ │ ├── index.html │ │ ├── README.md │ │ ├── package.json │ │ └── webpack.config.js └── chapter6-download │ └── styles │ ├── imports.styl │ ├── vars.styl │ ├── transitions.styl │ └── mixins.styl ├── Chapter04 └── chapter4-full │ ├── demo │ ├── .babelrc │ ├── src │ │ ├── Pug.vue │ │ ├── main.js │ │ ├── Sass.vue │ │ ├── Stylus.vue │ │ ├── Less.vue │ │ ├── Test.vue │ │ ├── Movies.vue │ │ └── MoviesJSX.vue │ ├── .gitignore │ ├── .editorconfig │ ├── index.html │ ├── README.md │ ├── package.json │ └── webpack.config.js │ └── movies │ ├── .babelrc │ ├── src │ ├── main.js │ ├── Movie.vue │ └── Movies.vue │ ├── .gitignore │ ├── .editorconfig │ ├── index.html │ ├── README.md │ ├── package.json │ ├── package.json~ │ └── webpack.config.js ├── Chapter03 ├── chapter3-full │ ├── raw │ │ ├── mockup1.png │ │ ├── mockup2.png │ │ └── mockup3.png │ ├── banner-template.svg │ ├── index.html │ ├── svg │ │ ├── food-bar.svg │ │ ├── health-bar.svg │ │ ├── ground0.svg │ │ ├── ground1.svg │ │ ├── food-bubble.svg │ │ ├── card-separator.svg │ │ └── turn.svg │ ├── state.js │ └── transitions.css └── chapter3-download │ ├── banner-template.svg │ ├── state.js │ ├── index.html │ └── svg │ ├── food-bar.svg │ ├── health-bar.svg │ ├── ground0.svg │ ├── ground1.svg │ ├── food-bubble.svg │ └── card-separator.svg ├── Chapter01 └── chapter1-setup │ └── index.html ├── Chapter02 └── chapter2-simple │ ├── index.html │ └── script.js └── LICENSE /chapter08/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /chapter08/.vueignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /chapter08/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.6 2 | -------------------------------------------------------------------------------- /chapter08/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /chapter08/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .meteor/local/ 3 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/browserslist: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | Firefox ESR 4 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/state.js: -------------------------------------------------------------------------------- 1 | export default { 2 | user: null, 3 | } 4 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/server/README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | npm install 3 | npm start 4 | ``` 5 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/server/README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | npm install 3 | npm start 4 | ``` 5 | -------------------------------------------------------------------------------- /Chapter05/chapter5-download/style/imports.styl: -------------------------------------------------------------------------------- 1 | @import "md-colors"; 2 | @import "mixins"; 3 | @import "vars"; 4 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/i18n/index.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | 'en', 3 | 'fr', 4 | 'es', 5 | 'de', 6 | ] 7 | -------------------------------------------------------------------------------- /Chapter06/chapter6-download/styles/imports.styl: -------------------------------------------------------------------------------- 1 | @import "md-colors"; 2 | @import "mixins"; 3 | @import "vars"; 4 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/styles/imports.styl: -------------------------------------------------------------------------------- 1 | @import "md-colors"; 2 | @import "mixins"; 3 | @import "vars"; 4 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/styles/imports.styl: -------------------------------------------------------------------------------- 1 | @import "md-colors"; 2 | @import "mixins"; 3 | @import "vars"; 4 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/style/imports.styl: -------------------------------------------------------------------------------- 1 | @import "md-colors"; 2 | @import "mixins"; 3 | @import "vars"; 4 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/styles/imports.styl: -------------------------------------------------------------------------------- 1 | @import "md-colors"; 2 | @import "mixins"; 3 | @import "vars"; 4 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/plugins.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Http from './utils/http' 3 | 4 | Vue.use(Http) 5 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer'), 4 | ], 5 | } 6 | -------------------------------------------------------------------------------- /chapter08/index.html: -------------------------------------------------------------------------------- 1 | 2 | Production Dashboard 3 | 4 | 5 |
6 | 7 | -------------------------------------------------------------------------------- /chapter08/lib/collections.js: -------------------------------------------------------------------------------- 1 | import { Mongo } from 'meteor/mongo' 2 | 3 | export const Measures = new Mongo.Collection('measures') 4 | -------------------------------------------------------------------------------- /Chapter06/chapter6-download/styles/vars.styl: -------------------------------------------------------------------------------- 1 | $primary-color = $md-indigo; 2 | 3 | $small-screen = 800px; 4 | $medium-screen = 1024px; 5 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/demo/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }], 4 | "stage-3", 5 | "vue" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/styles/vars.styl: -------------------------------------------------------------------------------- 1 | $primary-color = $md-indigo; 2 | 3 | $small-screen = 800px; 4 | $medium-screen = 1024px; 5 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/demo/src/Pug.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/movies/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }], 4 | "stage-3", 5 | "vue" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }], 4 | "stage-3", 5 | "vue" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/components/Loading.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }], 4 | "stage-3", 5 | "vue" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /Chapter03/chapter3-full/raw/mockup1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Vue-js-2-Web-Development-Projects/HEAD/Chapter03/chapter3-full/raw/mockup1.png -------------------------------------------------------------------------------- /Chapter03/chapter3-full/raw/mockup2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Vue-js-2-Web-Development-Projects/HEAD/Chapter03/chapter3-full/raw/mockup2.png -------------------------------------------------------------------------------- /Chapter03/chapter3-full/raw/mockup3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Vue-js-2-Web-Development-Projects/HEAD/Chapter03/chapter3-full/raw/mockup3.png -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/filters.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | export function date (value) { 4 | return moment(value).format('L') 5 | } 6 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/filters.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | export function date (value) { 4 | return moment(value).format('L') 5 | } 6 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/server/src/connectors/questions.js: -------------------------------------------------------------------------------- 1 | import { Questions } from '../providers' 2 | 3 | export function getAll () { 4 | return Questions.find({}) 5 | } 6 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/plugins.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Http from './utils/http' 3 | import VueI18n from 'vue-i18n' 4 | 5 | Vue.use(Http) 6 | Vue.use(VueI18n) 7 | -------------------------------------------------------------------------------- /Chapter03/chapter3-full/banner-template.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Chapter03/chapter3-download/banner-template.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/demo/src/main.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import Vue from 'vue' 3 | import Test from './MoviesJSX.vue' 4 | 5 | new Vue({ 6 | el: '#app', 7 | ...Test, 8 | }) 9 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/movies/src/main.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import Vue from 'vue' 3 | import Movies from './Movies.vue' 4 | 5 | new Vue({ 6 | el: '#app', 7 | ...Movies, 8 | }) 9 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/plugins/state.js: -------------------------------------------------------------------------------- 1 | export default { 2 | install (Vue, state) { 3 | Object.defineProperty(Vue.prototype, '$state', { 4 | get: () => state, 5 | }) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /chapter08/server/publications.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Measures } from '../lib/collections' 3 | 4 | Meteor.publish('measures', function () { 5 | return Measures.find({}) 6 | }) 7 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/demo/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | yarn-error.log 6 | 7 | # Editor directories and files 8 | .idea 9 | *.suo 10 | *.ntvs* 11 | *.njsproj 12 | *.sln 13 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/styles/vars.styl: -------------------------------------------------------------------------------- 1 | $color-primary = $md-red 2 | $color-accent1 = #ff0066 3 | $color-accent2 = #ff8800 4 | 5 | $small-screen = 800px 6 | $medium-screen = 1024px 7 | 8 | $page-size = 900px -------------------------------------------------------------------------------- /Chapter04/chapter4-full/movies/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | yarn-error.log 6 | 7 | # Editor directories and files 8 | .idea 9 | *.suo 10 | *.ntvs* 11 | *.njsproj 12 | *.sln 13 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | yarn-error.log 6 | 7 | # Editor directories and files 8 | .idea 9 | *.suo 10 | *.ntvs* 11 | *.njsproj 12 | *.sln 13 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | yarn-error.log 6 | 7 | # Editor directories and files 8 | .idea 9 | *.suo 10 | *.ntvs* 11 | *.njsproj 12 | *.sln 13 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/styles/vars.styl: -------------------------------------------------------------------------------- 1 | $color-primary = $md-red 2 | $color-accent1 = #ff0066 3 | $color-accent2 = #ff8800 4 | 5 | $small-screen = 800px 6 | $medium-screen = 1024px 7 | 8 | $page-size = 900px -------------------------------------------------------------------------------- /Chapter04/chapter4-full/demo/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/movies/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | yarn-error.log 6 | .reify-cache/ 7 | 8 | # Editor directories and files 9 | .idea 10 | *.suo 11 | *.ntvs* 12 | *.njsproj 13 | *.sln 14 | -------------------------------------------------------------------------------- /Chapter05/chapter5-download/style/vars.styl: -------------------------------------------------------------------------------- 1 | $primary-color = #0697c0; 2 | 3 | $small-screen = "max-width: 800px"; 4 | $not-small-screen = "min-width: 800px"; 5 | $medium-screen = "max-width: 1024px"; 6 | $not-medium-screen = "min-width: 1024px"; 7 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/components/content/NoContent.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/style/vars.styl: -------------------------------------------------------------------------------- 1 | $primary-color = #0697c0; 2 | 3 | $small-screen = "max-width: 800px"; 4 | $not-small-screen = "min-width: 800px"; 5 | $medium-screen = "max-width: 1024px"; 6 | $not-medium-screen = "min-width: 1024px"; 7 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fashion Store 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/locales/de.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'change-lang': 'Sprache ändern', 3 | 'lang': { 4 | 'en': 'English', 5 | 'fr': 'Français', 6 | 'es': 'Español', 7 | 'de': 'Deutsch', 8 | }, 9 | 'back': 'Zurück', 10 | } 11 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/locales/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'change-lang': 'Change language', 3 | 'lang': { 4 | 'en': 'English', 5 | 'fr': 'Français', 6 | 'es': 'Español', 7 | 'de': 'Deutsch', 8 | }, 9 | 'back': 'Back', 10 | } 11 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/locales/es.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'change-lang': 'Cambiar idioma', 3 | 'lang': { 4 | 'en': 'English', 5 | 'fr': 'Français', 6 | 'es': 'Español', 7 | 'de': 'Deutsch', 8 | }, 9 | 'back': 'Atrás', 10 | } 11 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/i18n/locales/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'change-lang': 'Change language', 3 | 'lang': { 4 | 'en': 'English', 5 | 'fr': 'Français', 6 | 'es': 'Español', 7 | 'de': 'Deutsch', 8 | }, 9 | 'back': 'Back', 10 | } 11 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/i18n/locales/es.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'change-lang': 'Cambiar idioma', 3 | 'lang': { 4 | 'en': 'English', 5 | 'fr': 'Français', 6 | 'es': 'Español', 7 | 'de': 'Deutsch', 8 | }, 9 | 'back': 'Atrás', 10 | } 11 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/locales/fr.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'change-lang': 'Changer de langue', 3 | 'lang': { 4 | 'en': 'English', 5 | 'fr': 'Français', 6 | 'es': 'Español', 7 | 'de': 'Deutsch', 8 | }, 9 | 'back': 'Retour', 10 | } 11 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/i18n/locales/de.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'change-lang': 'Sprache ändern', 3 | 'lang': { 4 | 'en': 'English', 5 | 'fr': 'Français', 6 | 'es': 'Español', 7 | 'de': 'Deutsch', 8 | }, 9 | 'back': 'Zurück', 10 | } 11 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/i18n/locales/fr.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'change-lang': 'Changer de langue', 3 | 'lang': { 4 | 'en': 'English', 5 | 'fr': 'Français', 6 | 'es': 'Español', 7 | 'de': 'Deutsch', 8 | }, 9 | 'back': 'Retour', 10 | } 11 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/webpack/spa.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge') 2 | const common = require('./common') 3 | 4 | module.exports = merge(common, { 5 | entry: './src/entry-client', 6 | output: { 7 | filename: 'build.js', 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/utils/http.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const http = axios.create({ 4 | baseURL: 'http://localhost:3000/', 5 | }) 6 | 7 | export function install (Vue) { 8 | Vue.prototype.$http = http 9 | } 10 | 11 | export default http 12 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/components/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/utils/http.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const http = axios.create({ 4 | baseURL: 'http://localhost:3000/', 5 | }) 6 | 7 | export function install (Vue) { 8 | Vue.prototype.$http = http 9 | } 10 | 11 | export default http 12 | -------------------------------------------------------------------------------- /chapter08/lib/methods.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Measures } from './collections' 3 | 4 | Meteor.methods({ 5 | 'measure.add' (measure) { 6 | Measures.insert({ 7 | ...measure, 8 | date: new Date(), 9 | }) 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/demo/src/Sass.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /Chapter05/chapter5-download/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/demo/src/Stylus.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/movies/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fashion Store 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }], 4 | "stage-0" 5 | ], 6 | "env": { 7 | "test": { 8 | "plugins": [ 9 | "transform-es2015-modules-commonjs", 10 | "dynamic-import-node" 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/demo/src/Less.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | '.+\\.jsx?$': '/node_modules/babel-jest', 4 | '.+\\.vue$': '/node_modules/vue-jest', 5 | }, 6 | snapshotSerializers: [ 7 | '/node_modules/jest-serializer-vue', 8 | ], 9 | mapCoverage: true, 10 | } 11 | -------------------------------------------------------------------------------- /Chapter03/chapter3-download/state.js: -------------------------------------------------------------------------------- 1 | // Some usefull variables 2 | var maxHealth = 10 3 | var maxFood = 10 4 | var handSize = 5 5 | var cardUid = 0 6 | var currentPlayingCard = null 7 | 8 | // The consolidated state of our app 9 | var state = { 10 | // World 11 | worldRatio: getWorldRatio(), 12 | // TODO Other things 13 | } 14 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/components/__snapshots__/BaseButton.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`BaseButton snapshot 1`] = ``; 4 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/global-components.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Loading from './components/Loading.vue' 3 | import SmartForm from './components/SmartForm.vue' 4 | import FormInput from './components/FormInput.vue' 5 | 6 | Vue.component('Loading', Loading) 7 | Vue.component('SmartForm', SmartForm) 8 | Vue.component('FormInput', FormInput) 9 | -------------------------------------------------------------------------------- /chapter08/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 1o1f11839n0cvuov2ks 8 | -------------------------------------------------------------------------------- /chapter08/client/components/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 23 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/components/AppFooter.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /chapter08/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chapter8-full", 3 | "private": true, 4 | "scripts": { 5 | "start": "meteor run" 6 | }, 7 | "dependencies": { 8 | "babel-runtime": "^6.20.0", 9 | "meteor-node-stubs": "~0.2.4", 10 | "vue": "^2.5.8", 11 | "vue-meteor-tracker": "^1.2.3", 12 | "vue-progress-path": "0.0.2", 13 | "vue-router": "^3.0.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/movies/src/Movie.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 20 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/components/content/PlaceDetails.vue: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/server/src/providers.js: -------------------------------------------------------------------------------- 1 | import { collectionFactory, modelFactory } from './utils/database' 2 | 3 | const idIndex = { 4 | fieldName: '_id', 5 | unique: true, 6 | } 7 | 8 | export const Users = modelFactory(collectionFactory('users', idIndex)) 9 | export const Posts = modelFactory(collectionFactory('posts', idIndex)) 10 | 11 | // Init 12 | export async function initData () { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/components/Home.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/demo/README.md: -------------------------------------------------------------------------------- 1 | # demo 2 | 3 | > Trying out Vue.js! 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | ``` 17 | 18 | For detailed explanation on how things work, consult the [docs for vue-loader](http://vuejs.github.io/vue-loader). 19 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/movies/README.md: -------------------------------------------------------------------------------- 1 | # demo 2 | 3 | > Trying out Vue.js! 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | ``` 17 | 18 | For detailed explanation on how things work, consult the [docs for vue-loader](http://vuejs.github.io/vue-loader). 19 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/README.md: -------------------------------------------------------------------------------- 1 | # demo 2 | 3 | > Trying out Vue.js! 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | ``` 17 | 18 | For detailed explanation on how things work, consult the [docs for vue-loader](http://vuejs.github.io/vue-loader). 19 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/README.md: -------------------------------------------------------------------------------- 1 | # demo 2 | 3 | > Trying out Vue.js! 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | ``` 17 | 18 | For detailed explanation on how things work, consult the [docs for vue-loader](http://vuejs.github.io/vue-loader). 19 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/README.md: -------------------------------------------------------------------------------- 1 | # chapter7-part1 2 | 3 | > A Vue.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | ``` 17 | 18 | For detailed explanation on how things work, consult the [docs for vue-loader](http://vuejs.github.io/vue-loader). 19 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/components/GeoBlog.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/components.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | // Require all the components that start with 'BaseXXX.vue' 4 | const components = require.context('./components', true, /Base[a-z0-9]+\.(jsx?|vue)$/i) 5 | // To extract the component name 6 | const nameReg = /([a-z0-9]+)\./i 7 | // Registration 8 | components.keys().forEach(key => { 9 | const name = key.match(nameReg)[1] 10 | Vue.component(name, components(key).default) 11 | }) 12 | -------------------------------------------------------------------------------- /Chapter05/chapter5-download/style/transitions.styl: -------------------------------------------------------------------------------- 1 | 2 | .fade-enter-active, 3 | .fade-leave-active { 4 | transition: opacity .15s linear; 5 | } 6 | 7 | .fade-enter, 8 | .fade-leave-to { 9 | opacity: 0; 10 | } 11 | 12 | .zoom-enter-active, 13 | .zoom-leave-active { 14 | transform-origin: center center; 15 | transition: transform .15s cubic-bezier(0.0, 0.0, 0.2, 1); 16 | } 17 | 18 | .zoom-enter, 19 | .zoom-leave-to { 20 | transform: scale(0); 21 | } 22 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/components.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | // Require all the components that start with 'BaseXXX.vue' 4 | const components = require.context('./components', true, /Base[a-z0-9]+\.(jsx?|vue)$/i) 5 | // To extract the component name 6 | const nameReg = /([a-z0-9]+)\./i 7 | // Registration 8 | components.keys().forEach(key => { 9 | const name = key.match(nameReg)[1] 10 | Vue.component(name, components(key).default) 11 | }) 12 | -------------------------------------------------------------------------------- /Chapter06/chapter6-download/styles/transitions.styl: -------------------------------------------------------------------------------- 1 | 2 | .fade-enter-active, 3 | .fade-leave-active { 4 | transition: opacity .15s linear; 5 | } 6 | 7 | .fade-enter, 8 | .fade-leave-to { 9 | opacity: 0; 10 | } 11 | 12 | .zoom-enter-active, 13 | .zoom-leave-active { 14 | transform-origin: center center; 15 | transition: transform .15s cubic-bezier(0.0, 0.0, 0.2, 1); 16 | } 17 | 18 | .zoom-enter, 19 | .zoom-leave-to { 20 | transform: scale(0); 21 | } 22 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/style/transitions.styl: -------------------------------------------------------------------------------- 1 | 2 | .fade-enter-active, 3 | .fade-leave-active { 4 | transition: opacity .15s linear; 5 | } 6 | 7 | .fade-enter, 8 | .fade-leave-to { 9 | opacity: 0; 10 | } 11 | 12 | .zoom-enter-active, 13 | .zoom-leave-active { 14 | transform-origin: center center; 15 | transition: transform .15s cubic-bezier(0.0, 0.0, 0.2, 1); 16 | } 17 | 18 | .zoom-enter, 19 | .zoom-leave-to { 20 | transform: scale(0); 21 | } 22 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/styles/transitions.styl: -------------------------------------------------------------------------------- 1 | 2 | .fade-enter-active, 3 | .fade-leave-active { 4 | transition: opacity .15s linear; 5 | } 6 | 7 | .fade-enter, 8 | .fade-leave-to { 9 | opacity: 0; 10 | } 11 | 12 | .zoom-enter-active, 13 | .zoom-leave-active { 14 | transform-origin: center center; 15 | transition: transform .15s cubic-bezier(0.0, 0.0, 0.2, 1); 16 | } 17 | 18 | .zoom-enter, 19 | .zoom-leave-to { 20 | transform: scale(0); 21 | } 22 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/server/src/utils/promise.js: -------------------------------------------------------------------------------- 1 | 2 | export function promise (func, context, ...args) { 3 | return new Promise((resolve, reject) => { 4 | func.call(context, ...args, (err, ...result) => { 5 | if (err) { 6 | reject(err) 7 | } else { 8 | resolve(...result) 9 | } 10 | }) 11 | }) 12 | } 13 | 14 | export function promiseMethod (object, name, ...args) { 15 | return promise(object[name], object, ...args) 16 | } 17 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/server/src/utils/promise.js: -------------------------------------------------------------------------------- 1 | 2 | export function promise (func, context, ...args) { 3 | return new Promise((resolve, reject) => { 4 | func.call(context, ...args, (err, ...result) => { 5 | if (err) { 6 | reject(err) 7 | } else { 8 | resolve(...result) 9 | } 10 | }) 11 | }) 12 | } 13 | 14 | export function promiseMethod (object, name, ...args) { 15 | return promise(object[name], object, ...args) 16 | } 17 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/entry-client.js: -------------------------------------------------------------------------------- 1 | import { createApp } from './app' 2 | import { getAutoLang } from './utils/i18n' 3 | 4 | const locale = getAutoLang() 5 | createApp({ 6 | locale, 7 | }).then(({ app, store }) => { 8 | // Restore the Vuex store state 9 | // if send by the server 10 | if (window.__INITIAL_STATE__) { 11 | store.replaceState(window.__INITIAL_STATE__) 12 | } 13 | 14 | // Mount the app into the page 15 | app.$mount('#app') 16 | }) 17 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/store/ui.js: -------------------------------------------------------------------------------- 1 | export default { 2 | namespaced: true, 3 | 4 | state () { 5 | return { 6 | showCart: false, 7 | } 8 | }, 9 | 10 | getters: { 11 | showCart: state => state.showCart, 12 | }, 13 | 14 | mutations: { 15 | showCart (state, value) { 16 | state.showCart = value 17 | }, 18 | }, 19 | 20 | actions: { 21 | setShowCart ({ commit }, value) { 22 | commit('showCart', value) 23 | }, 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /chapter08/client/components/ProductionIndicator.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/store/ui.js: -------------------------------------------------------------------------------- 1 | export default { 2 | namespaced: true, 3 | 4 | state () { 5 | return { 6 | showCart: false, 7 | } 8 | }, 9 | 10 | getters: { 11 | showCart: state => state.showCart, 12 | }, 13 | 14 | mutations: { 15 | showCart (state, value) { 16 | state.showCart = value 17 | }, 18 | }, 19 | 20 | actions: { 21 | setShowCart ({ commit }, value) { 22 | commit('showCart', value) 23 | }, 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/components/AppFooter.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/filters.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** 4 | * Formats a percentage. Ex: .12 => '12%' 5 | * 6 | * @export 7 | * @param {number} value 8 | * @returns 9 | */ 10 | export function percentage (value) { 11 | return `${Math.round(value * 100).toLocaleString()}%` 12 | } 13 | 14 | /** 15 | * Formats an amount of dollars using number localization. Ex: 15.05 => '$15.05' 16 | * 17 | * @export 18 | * @param {number} value 19 | * @returns 20 | */ 21 | export function money (value) { 22 | return `$${value.toLocaleString()}` 23 | } 24 | -------------------------------------------------------------------------------- /chapter08/client/main.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import Vue from 'vue' 3 | import App from './components/App.vue' 4 | import VueMeteorTracker from 'vue-meteor-tracker' 5 | import router from './router' 6 | import 'vue-progress-path/dist/vue-progress-path.css' 7 | import VueProgress from 'vue-progress-path' 8 | 9 | Vue.use(VueMeteorTracker) 10 | 11 | Vue.use(VueProgress, { 12 | defaultShape: 'semicircle', 13 | }) 14 | 15 | Meteor.startup(() => { 16 | new Vue({ 17 | el: '#app', 18 | router, 19 | ...App, 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/filters.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** 4 | * Formats a percentage. Ex: .12 => '12%' 5 | * 6 | * @export 7 | * @param {number} value 8 | * @returns 9 | */ 10 | export function percentage (value) { 11 | return `${Math.round(value * 100).toLocaleString()}%` 12 | } 13 | 14 | /** 15 | * Formats an amount of dollars using number localization. Ex: 15.05 => '$15.05' 16 | * 17 | * @export 18 | * @param {number} value 19 | * @returns 20 | */ 21 | export function money (value) { 22 | return `$${value.toLocaleString()}` 23 | } 24 | -------------------------------------------------------------------------------- /chapter08/client/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | 4 | import ProductionDashboard from './components/ProductionDashboard.vue' 5 | import ProductionGenerator from './components/ProductionGenerator.vue' 6 | 7 | Vue.use(VueRouter) 8 | 9 | const routes = [ 10 | { path: '/', name: 'dashboard', component: ProductionDashboard }, 11 | { path: '/generate', name: 'generate', component: ProductionGenerator }, 12 | ] 13 | 14 | const router = new VueRouter({ 15 | mode: 'history', 16 | routes, 17 | }) 18 | 19 | export default router 20 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/mixins/discount.js: -------------------------------------------------------------------------------- 1 | export default function discountMixin ({ 2 | itemProp = 'item', 3 | discountProp = 'discount', 4 | priceProp = 'price', 5 | originalPriceProp = 'originalPrice', 6 | } = {}) { 7 | return { 8 | computed: { 9 | [discountProp] () { 10 | const item = this[itemProp] 11 | const price = item[priceProp] 12 | const originalPrice = item[originalPriceProp] 13 | 14 | if (originalPrice) { 15 | return 1 - price / originalPrice 16 | } 17 | return 0 18 | }, 19 | }, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/server/src/config.js: -------------------------------------------------------------------------------- 1 | export const PORT = process.env.PORT || 3000 2 | export const SECRET = process.env.SECRET || 'TR7_9cDZ5Re-@lT3Z1|58F' 3 | export const CLIENT_ORIGIN = process.env.CLIENT_ORIGIN || 'http://localhost:8080' 4 | export const PUBLIC_URL = process.env.PUBLIC_URL || 'http://localhost:3000' 5 | export const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID 6 | export const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET 7 | export const DB_PATH = process.env.DB_PATH || './db' 8 | export const COMPACTION_INTERVAL = process.env.COMPACTION_INTERVAL || 12 * 60 * 60 * 1000 9 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/mixins/discount.js: -------------------------------------------------------------------------------- 1 | export default function discountMixin ({ 2 | itemProp = 'item', 3 | discountProp = 'discount', 4 | priceProp = 'price', 5 | originalPriceProp = 'originalPrice', 6 | } = {}) { 7 | return { 8 | computed: { 9 | [discountProp] () { 10 | const item = this[itemProp] 11 | const price = item[priceProp] 12 | const originalPrice = item[originalPriceProp] 13 | 14 | if (originalPrice) { 15 | return 1 - price / originalPrice 16 | } 17 | return 0 18 | }, 19 | }, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/components/PageCheckout.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 27 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/components/PageCheckout.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 27 | -------------------------------------------------------------------------------- /chapter08/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | 1.3.0-split-minifiers-package 14 | 1.4.0-remove-old-dev-bundle-link 15 | 1.4.1-add-shell-server-package 16 | 1.4.3-split-account-service-packages 17 | 1.5-add-dynamic-import-package 18 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/components/NotFound.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/components/NotFound.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/server/src/connectors/users.js: -------------------------------------------------------------------------------- 1 | import { Users } from '../providers' 2 | 3 | export function getById (id) { 4 | return Users.findOne({ _id: id }) 5 | } 6 | 7 | export async function findOrCreate (data) { 8 | const { profile } = data 9 | const user = await getById(profile.id) 10 | if (user) { 11 | Users.updateOne({ 12 | _id: user._id, 13 | }, data) 14 | console.log(user, data) 15 | return { 16 | ...user, 17 | ...data, 18 | } 19 | } else { 20 | const result = await Users.insert({ 21 | _id: profile.id, 22 | ...data, 23 | }) 24 | 25 | return result 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/main.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import Vue from 'vue' 3 | import App from './components/App.vue' 4 | import router from './router' 5 | import store from './store' 6 | import { sync } from 'vuex-router-sync' 7 | import * as filters from './filters' 8 | import './plugins' 9 | import './components' 10 | 11 | // Global filters 12 | for (const key in filters) { 13 | Vue.filter(key, filters[key]) 14 | } 15 | 16 | sync(store, router) 17 | 18 | async function main () { 19 | await store.dispatch('init') 20 | 21 | new Vue({ 22 | el: '#app', 23 | router, 24 | store, 25 | ...App, 26 | }) 27 | } 28 | 29 | main() 30 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/components/PageNotFound.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 22 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/components/PageNotFound.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 22 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/components/content/Comment.vue: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/movies/src/Movies.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 28 | 29 | 37 | -------------------------------------------------------------------------------- /chapter08/client/components/ProductionGenerator.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 28 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/components/BaseLoading.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 35 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/components/BaseLoading.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 35 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/demo/src/Movies.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | 22 | 30 | 31 | 38 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/webpack/server.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge') 2 | const common = require('./common') 3 | const nodeExternals = require('webpack-node-externals') 4 | const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') 5 | 6 | module.exports = merge(common, { 7 | entry: './src/entry-server', 8 | target: 'node', 9 | devtool: 'source-map', 10 | output: { 11 | libraryTarget: 'commonjs2', 12 | }, 13 | // Skip webpack processing on node_modules 14 | externals: nodeExternals({ 15 | // Force css files imported from no_modules 16 | // to be processed by webpack 17 | whitelist: /\.css$/, 18 | }), 19 | plugins: [ 20 | // Generates the server bundle file 21 | new VueSSRServerPlugin(), 22 | ], 23 | }) 24 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/components/AppLayout.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | 25 | 28 | 29 | 37 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/webpack/client.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const merge = require('webpack-merge') 3 | const common = require('./common') 4 | const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') 5 | 6 | module.exports = merge(common, { 7 | entry: './src/entry-client', 8 | plugins: [ 9 | // Important: this splits the webpack runtime into a leading chunk 10 | // so that async chunks can be injected right after it. 11 | // this also enables better caching for your app/vendor code. 12 | new webpack.optimize.CommonsChunkPlugin({ 13 | name: 'manifest', 14 | minChunks: Infinity, 15 | }), 16 | // Generates the client manifest file used by the renderer 17 | new VueSSRClientPlugin(), 18 | ], 19 | }) 20 | -------------------------------------------------------------------------------- /Chapter01/chapter1-setup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | VueJS 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

{{ message }}

12 | 13 |
14 | 15 | 16 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "support-center-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "nodemon src/index.js --exec babel-node --presets es2015,stage-0" 7 | }, 8 | "author": "", 9 | "license": "ISC", 10 | "dependencies": { 11 | "babel-cli": "^6.24.1", 12 | "babel-preset-es2015": "^6.24.1", 13 | "babel-preset-stage-0": "^6.24.1", 14 | "bcrypt": "^1.0.2", 15 | "body-parser": "^1.17.2", 16 | "cookie-parser": "^1.4.3", 17 | "cors": "^2.8.3", 18 | "express": "^4.15.3", 19 | "express-session": "^1.15.3", 20 | "nedb": "^1.8.0", 21 | "nodemon": "^1.11.0", 22 | "passport": "^0.3.2", 23 | "passport-local": "^1.0.0", 24 | "uuid": "^3.0.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/components/App.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 33 | 34 | 37 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/components/App.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 33 | 34 | 37 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/components/TicketsLayout.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | 31 | -------------------------------------------------------------------------------- /Chapter03/chapter3-download/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Castle Duel 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/components/BasePage.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 | 44 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/components/BasePage.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 | 44 | -------------------------------------------------------------------------------- /Chapter02/chapter2-simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notebook 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 |
18 | 19 |
20 | 21 | 22 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/main.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import Vue from 'vue' 3 | import AppLayout from './components/AppLayout.vue' 4 | import router from './router' 5 | import state from './state' 6 | import VueFetch, { $fetch } from './plugins/fetch' 7 | import VueState from './plugins/state' 8 | import './global-components' 9 | import * as filters from './filters' 10 | 11 | Vue.use(VueFetch, { 12 | baseUrl: 'http://localhost:3000/', 13 | }) 14 | 15 | Vue.use(VueState, state) 16 | 17 | for (const key in filters) { 18 | Vue.filter(key, filters[key]) 19 | } 20 | 21 | async function main () { 22 | // Get user info 23 | try { 24 | state.user = await $fetch('user') 25 | } catch (e) { 26 | console.warn(e) 27 | } 28 | // Launch app 29 | new Vue({ 30 | el: '#app', 31 | data: state, 32 | router, 33 | render: h => h(AppLayout), 34 | }) 35 | } 36 | 37 | main() 38 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/mixins/PersistantData.js: -------------------------------------------------------------------------------- 1 | export default function (id, fields) { 2 | return { 3 | created () { 4 | for (const field of fields) { 5 | const savedValue = localStorage.getItem(`${id}.${field}`) 6 | if (savedValue !== null) { 7 | this.$data[field] = JSON.parse(savedValue) 8 | } 9 | } 10 | }, 11 | 12 | watch: fields.reduce((obj, field) => { 13 | obj[field] = function (val) { 14 | localStorage.setItem(`${id}.${field}`, JSON.stringify(val)) 15 | } 16 | return obj 17 | }, {}), 18 | 19 | methods: { 20 | saveAllPersistantData () { 21 | for (const field of fields) { 22 | localStorage.setItem(`${id}.${field}`, JSON.stringify(this.$data[field])) 23 | } 24 | }, 25 | }, 26 | 27 | beforeDestroy () { 28 | this.saveAllPersistantData() 29 | }, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Use only this configuration 3 | root: true, 4 | // File parser 5 | parser: 'vue-eslint-parser', 6 | parserOptions: { 7 | // Use babel-eslint for JavaScript 8 | 'parser': 'babel-eslint', 9 | 'ecmaVersion': 2017, 10 | // With import/export syntax 11 | 'sourceType': 'module' 12 | }, 13 | // Environment global objects 14 | env: { 15 | browser: true, 16 | es6: true, 17 | jest: true, 18 | }, 19 | extends: [ 20 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 21 | 'standard', 22 | // https://github.com/vuejs/eslint-plugin-vue#bulb-rules 23 | 'plugin:vue/recommended', 24 | ], 25 | rules: { 26 | // https://github.com/babel/babel-eslint/issues/517 27 | 'no-use-before-define': 'off', 28 | 'comma-dangle': ['error', 'always-multiline'], 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geolocated-blog-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "nodemon src/index.js --exec babel-node --presets es2015,stage-0" 7 | }, 8 | "author": "", 9 | "license": "ISC", 10 | "dependencies": { 11 | "babel-cli": "^6.24.1", 12 | "babel-preset-es2015": "^6.24.1", 13 | "babel-preset-stage-0": "^6.24.1", 14 | "bcrypt": "^1.0.2", 15 | "body-parser": "^1.17.2", 16 | "cookie-parser": "^1.4.3", 17 | "cors": "^2.8.3", 18 | "express": "^4.15.3", 19 | "express-session": "^1.15.3", 20 | "geolib": "^2.0.23", 21 | "nedb": "^1.8.0", 22 | "nedb-session-store": "^1.1.1", 23 | "nodemon": "^1.11.0", 24 | "passport": "^0.4.0", 25 | "passport-google-oauth": "^1.0.0", 26 | "passport.socketio": "^3.7.0", 27 | "socket.io": "^2.0.3", 28 | "uuid": "^3.0.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/app.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import Vue from 'vue' 3 | import App from './components/App.vue' 4 | import { createRouter } from './router' 5 | import { createStore } from './store' 6 | import { sync } from 'vuex-router-sync' 7 | import * as filters from './filters' 8 | import './plugins' 9 | import './components' 10 | import { createI18n } from './utils/i18n' 11 | 12 | // Global filters 13 | for (const key in filters) { 14 | Vue.filter(key, filters[key]) 15 | } 16 | 17 | export async function createApp (context) { 18 | const router = createRouter() 19 | const store = createStore() 20 | 21 | sync(store, router) 22 | 23 | const i18n = await createI18n(context.locale) 24 | await store.dispatch('init') 25 | 26 | const app = new Vue({ 27 | router, 28 | store, 29 | i18n, 30 | ...App, 31 | }) 32 | 33 | return { 34 | app, 35 | router, 36 | store, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/main.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import Vue from 'vue' 3 | import VueFetch, { $fetch } from './plugins/fetch' 4 | import App from './components/App.vue' 5 | import router from './router' 6 | import store from './store' 7 | import { sync } from 'vuex-router-sync' 8 | import VueGoogleMaps, * as GMap from 'vue-googlemaps' 9 | import * as filters from './filters' 10 | 11 | // Filters 12 | for (const key in filters) { 13 | Vue.filter(key, filters[key]) 14 | } 15 | 16 | Vue.use(VueFetch, { 17 | baseUrl: 'http://localhost:3000/', 18 | }) 19 | 20 | Vue.use(VueGoogleMaps, { 21 | load: { 22 | apiKey: 'AIzaSyBl_r42YN1g-pY3DVsy_0x3k15f0-oOJeo', 23 | libraries: ['places'], 24 | }, 25 | }) 26 | 27 | sync(store, router) 28 | 29 | async function main () { 30 | await store.dispatch('init') 31 | 32 | new Vue({ 33 | ...App, 34 | el: '#app', 35 | router, 36 | store, 37 | }) 38 | } 39 | 40 | main() 41 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/components/BaseImage.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 37 | 38 | 52 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/components/BaseImage.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 37 | 38 | 52 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/demo/src/MoviesJSX.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 35 | 36 | 43 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/components/AppMenu.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 35 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import cart from './cart' 5 | import item from './item' 6 | import items from './items' 7 | import ui from './ui' 8 | 9 | Vue.use(Vuex) 10 | 11 | const store = new Vuex.Store({ 12 | strict: process.env.NODE_ENV !== 'production', 13 | 14 | actions: { 15 | init () { 16 | console.log('store init') 17 | }, 18 | }, 19 | 20 | modules: { 21 | cart, 22 | item, 23 | items, 24 | ui, 25 | }, 26 | }) 27 | 28 | // Hot module replacement for the store 29 | if (module.hot) { 30 | module.hot.accept([ 31 | './cart', 32 | './item', 33 | './items', 34 | './ui', 35 | ], () => { 36 | store.hotUpdate({ 37 | modules: { 38 | cart: require('./cart').default, 39 | item: require('./item').default, 40 | items: require('./items').default, 41 | ui: require('./ui').default, 42 | }, 43 | }) 44 | }) 45 | } 46 | 47 | export default store 48 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/plugins/fetch.js: -------------------------------------------------------------------------------- 1 | import store from '../store' 2 | 3 | let baseUrl 4 | 5 | export async function $fetch (url, options) { 6 | const finalOptions = Object.assign({}, { 7 | headers: { 8 | 'Content-Type': 'application/json', 9 | }, 10 | credentials: 'include', 11 | }, options) 12 | const response = await fetch(`${baseUrl}${url}`, finalOptions) 13 | if (response.ok) { 14 | const data = await response.json() 15 | return data 16 | } else if (response.status === 403) { 17 | // If the session is no longer valid 18 | // We logout 19 | store.dispatch('logout') 20 | } else { 21 | const message = await response.text() 22 | const error = new Error(message) 23 | error.response = response 24 | throw error 25 | } 26 | } 27 | 28 | export default { 29 | install (Vue, options) { 30 | console.log('Installed!', options) 31 | 32 | // Plugin options 33 | baseUrl = options.baseUrl 34 | 35 | // Fetch 36 | Vue.prototype.$fetch = $fetch 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/server/src/connectors/tickets.js: -------------------------------------------------------------------------------- 1 | import { Tickets } from '../providers' 2 | import * as Users from './users' 3 | 4 | async function populateUser (ticket) { 5 | ticket.user = await Users.getById(ticket.user_id) 6 | } 7 | 8 | export async function getAll ({ user }) { 9 | const tickets = await Tickets.find({ 10 | user_id: user._id, 11 | }) 12 | await Promise.all( 13 | tickets.map(ticket => populateUser(ticket)) 14 | ) 15 | tickets.sort((a, b) => b.date.getTime() - a.date.getTime()) 16 | return tickets 17 | } 18 | 19 | export async function getById ({ user }, id) { 20 | const ticket = await Tickets.findOne({ 21 | user_id: user._id, 22 | _id: id, 23 | }) 24 | if (ticket) { 25 | await populateUser(ticket) 26 | } 27 | return ticket 28 | } 29 | 30 | export function create ({ user }, { title, description }) { 31 | return Tickets.insert({ 32 | title, 33 | description, 34 | user_id: user._id, 35 | date: new Date(), 36 | comments: [], 37 | status: 'new', 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | 4 | import PageHome from './components/PageHome.vue' 5 | import PageStoreItem from './components/PageStoreItem.vue' 6 | import PageCheckout from './components/PageCheckout.vue' 7 | import PageNotFound from './components/PageNotFound.vue' 8 | 9 | Vue.use(VueRouter) 10 | 11 | const routes = [ 12 | { path: '/', name: 'home', component: PageHome }, 13 | { path: '/item/:id', name: 'store-item', component: PageStoreItem, props: route => ({ id: parseInt(route.params.id) }) }, 14 | { path: '/checkout', name: 'checkout', component: PageCheckout }, 15 | { path: '*', component: PageNotFound }, 16 | ] 17 | 18 | const router = new VueRouter({ 19 | routes, 20 | mode: 'history', 21 | scrollBehavior (to, from, savedPosition) { 22 | if (savedPosition) { 23 | return savedPosition 24 | } 25 | if (to.hash) { 26 | return { selector: to.hash } 27 | } 28 | return { x: 0, y: 0 } 29 | }, 30 | }) 31 | 32 | export default router 33 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/movies/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "description": "Trying out Vue.js!", 4 | "version": "1.0.0", 5 | "author": "Guillaume Chau", 6 | "license": "MIT", 7 | "private": true, 8 | "scripts": { 9 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", 10 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" 11 | }, 12 | "dependencies": { 13 | "vue": "^2.5.8" 14 | }, 15 | "browserslist": [ 16 | "> 1%", 17 | "last 2 versions", 18 | "not ie <= 8" 19 | ], 20 | "devDependencies": { 21 | "babel-core": "^6.26.0", 22 | "babel-loader": "^7.1.2", 23 | "babel-polyfill": "^6.26.0", 24 | "babel-preset-env": "^1.6.0", 25 | "babel-preset-stage-3": "^6.24.1", 26 | "babel-preset-vue": "^1.2.1", 27 | "cross-env": "^5.0.5", 28 | "css-loader": "^0.28.7", 29 | "file-loader": "^1.1.4", 30 | "vue-loader": "^13.0.5", 31 | "vue-template-compiler": "^2.5.8", 32 | "webpack": "^3.6.0", 33 | "webpack-dev-server": "^2.9.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/movies/package.json~: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "description": "Trying out Vue.js!", 4 | "version": "1.0.0", 5 | "author": "Guillaume Chau ", 6 | "license": "MIT", 7 | "private": true, 8 | "scripts": { 9 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", 10 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" 11 | }, 12 | "dependencies": { 13 | "vue": "^2.4.4" 14 | }, 15 | "browserslist": [ 16 | "> 1%", 17 | "last 2 versions", 18 | "not ie <= 8" 19 | ], 20 | "devDependencies": { 21 | "babel-core": "^6.26.0", 22 | "babel-loader": "^7.1.2", 23 | "babel-polyfill": "^6.26.0", 24 | "babel-preset-env": "^1.6.0", 25 | "babel-preset-stage-3": "^6.24.1", 26 | "babel-preset-vue": "^1.2.1", 27 | "cross-env": "^5.0.5", 28 | "css-loader": "^0.28.7", 29 | "file-loader": "^1.1.4", 30 | "vue-loader": "^13.0.5", 31 | "vue-template-compiler": "^2.4.4", 32 | "webpack": "^3.6.0", 33 | "webpack-dev-server": "^2.9.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/utils/i18n.js: -------------------------------------------------------------------------------- 1 | import VueI18n from 'vue-i18n' 2 | import langs from '../../i18n' 3 | 4 | /** 5 | * Creates and loads a localization. 6 | * 7 | * @export 8 | * @param {string} locale 9 | * @returns A promise with the instance of VueI18n 10 | */ 11 | export async function createI18n (locale) { 12 | const { default: localeMessages } = await import(`../../i18n/locales/${locale}`) 13 | const messages = { 14 | [locale]: localeMessages, 15 | } 16 | 17 | const i18n = new VueI18n({ 18 | locale, 19 | messages, 20 | }) 21 | 22 | return i18n 23 | } 24 | 25 | /** 26 | * Detects the lang used by the user. 27 | * 28 | * @export 29 | * @returns The lang if present in the available locales, or 'en' if not. 30 | */ 31 | export function getAutoLang () { 32 | // IE: `window.navigator.userLanguage` 33 | let result = window.navigator.userLanguage || window.navigator.language 34 | if (result) { 35 | result = result.substr(0, 2) 36 | } 37 | if (langs.indexOf(result) === -1) { 38 | return 'en' 39 | } else { 40 | return result 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/components/FAQ.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 49 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/server/src/auth.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport' 2 | import { Strategy as LocalStrategy } from 'passport-local' 3 | import * as Users from './connectors/users' 4 | 5 | async function authUser (username, password) { 6 | const user = await Users.getByUsername(username) 7 | let valid = false 8 | if (user) { 9 | valid = await Users.isPasswordMatching(user, password) 10 | } 11 | return { 12 | valid, 13 | user, 14 | } 15 | } 16 | 17 | passport.use('local', new LocalStrategy( 18 | async (username, password, done) => { 19 | const { valid, user } = await authUser(username, password) 20 | if (valid) { 21 | return done(null, user) 22 | } else { 23 | return done('Invalid username or password') 24 | } 25 | } 26 | )) 27 | 28 | passport.serializeUser( 29 | (user, done) => { 30 | done(null, user._id) 31 | } 32 | ) 33 | 34 | passport.deserializeUser( 35 | async (id, done) => { 36 | const user = await Users.getById(id) 37 | const err = !user ? new Error('User not found') : null 38 | done(err, user || null) 39 | } 40 | ) 41 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/components/Login.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 47 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import cart from './cart' 5 | import item from './item' 6 | import items from './items' 7 | import ui from './ui' 8 | 9 | Vue.use(Vuex) 10 | 11 | export function createStore () { 12 | const store = new Vuex.Store({ 13 | strict: process.env.NODE_ENV !== 'production', 14 | 15 | actions: { 16 | init () { 17 | console.log('store init') 18 | }, 19 | }, 20 | 21 | modules: { 22 | cart, 23 | item, 24 | items, 25 | ui, 26 | }, 27 | }) 28 | 29 | // Hot module replacement for the store 30 | if (module.hot) { 31 | module.hot.accept([ 32 | './cart', 33 | './item', 34 | './items', 35 | './ui', 36 | ], () => { 37 | store.hotUpdate({ 38 | modules: { 39 | cart: require('./cart').default, 40 | item: require('./item').default, 41 | items: require('./items').default, 42 | ui: require('./ui').default, 43 | }, 44 | }) 45 | }) 46 | } 47 | 48 | return store 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/entry-server.js: -------------------------------------------------------------------------------- 1 | import { createApp } from './app' 2 | 3 | export default context => { 4 | return new Promise(async (resolve, reject) => { 5 | const { app, router, store } = await createApp(context) 6 | 7 | router.push(context.url) 8 | 9 | router.onReady(() => { 10 | const matchedComponents = router.getMatchedComponents() 11 | 12 | Promise.all(matchedComponents.map(Component => { 13 | if (Component.asyncData) { 14 | return Component.asyncData({ 15 | store, 16 | route: router.currentRoute, 17 | }) 18 | } 19 | })).then(() => { 20 | // After all preFetch hooks are resolved, our store is now 21 | // filled with the state needed to render the app. 22 | // When we attach the state to the context, and the `template` option 23 | // is used for the renderer, the state will automatically be 24 | // serialized and injected into the HTML as `window.__INITIAL_STATE__`. 25 | context.state = store.state 26 | 27 | resolve(app) 28 | }).catch(reject) 29 | }, reject) 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/components/content/BlogContent.vue: -------------------------------------------------------------------------------- 1 | 48 | -------------------------------------------------------------------------------- /chapter08/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-base@1.2.0 # Packages every Meteor app needs to have 8 | mobile-experience@1.0.5 # Packages for a great mobile UX 9 | mongo@1.3.0 # The database Meteor supports right now 10 | static-html # Define static page content in .html files 11 | reactive-var@1.0.11 # Reactive variable for tracker 12 | tracker@1.1.3 # Meteor's client-side reactive programming library 13 | 14 | standard-minifier-css@1.3.5 # CSS minifier run for production mode 15 | standard-minifier-js@2.2.0 # JS minifier run for production mode 16 | es5-shim@4.6.15 # ECMAScript 5 compatibility for older browsers 17 | ecmascript@0.9.0 # Enable ECMAScript2015+ syntax in app code 18 | shell-server@0.3.0 # Server-side component of the `meteor shell` command 19 | akryum:vue-component 20 | akryum:vue-stylus 21 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/utils/animations.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | /** 4 | * Creates a floating image that animate from an element to a target element. 5 | * 6 | * @export 7 | * @param {object} { el, targetEl, imageUrl, imageClass } 8 | */ 9 | export function flyingImage ({ el, targetEl, imageUrl, imageClass }) { 10 | if (targetEl) { 11 | const bounds = el.getBoundingClientRect() 12 | const animatedEl = document.createElement('img') 13 | animatedEl.setAttribute('src', imageUrl) 14 | animatedEl.classList.add('animation') 15 | animatedEl.classList.add('flying-image') 16 | animatedEl.classList.add(imageClass) 17 | animatedEl.style.top = `${bounds.top}px` 18 | animatedEl.style.left = `${bounds.left}px` 19 | document.body.appendChild(animatedEl) 20 | Vue.nextTick(() => { 21 | const targetBounds = targetEl.getBoundingClientRect() 22 | animatedEl.style.top = `${targetBounds.top + targetBounds.height / 2}px` 23 | animatedEl.style.left = `${targetBounds.left + targetBounds.width / 2}px` 24 | setTimeout(() => { 25 | document.body.removeChild(animatedEl) 26 | }, 500) 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/utils/animations.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | /** 4 | * Creates a floating image that animate from an element to a target element. 5 | * 6 | * @export 7 | * @param {object} { el, targetEl, imageUrl, imageClass } 8 | */ 9 | export function flyingImage ({ el, targetEl, imageUrl, imageClass }) { 10 | if (targetEl) { 11 | const bounds = el.getBoundingClientRect() 12 | const animatedEl = document.createElement('img') 13 | animatedEl.setAttribute('src', imageUrl) 14 | animatedEl.classList.add('animation') 15 | animatedEl.classList.add('flying-image') 16 | animatedEl.classList.add(imageClass) 17 | animatedEl.style.top = `${bounds.top}px` 18 | animatedEl.style.left = `${bounds.left}px` 19 | document.body.appendChild(animatedEl) 20 | Vue.nextTick(() => { 21 | const targetBounds = targetEl.getBoundingClientRect() 22 | animatedEl.style.top = `${targetBounds.top + targetBounds.height / 2}px` 23 | animatedEl.style.left = `${targetBounds.left + targetBounds.width / 2}px` 24 | setTimeout(() => { 25 | document.body.removeChild(animatedEl) 26 | }, 500) 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/components/NavMenu.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 32 | 33 | 40 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/components/Tickets.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 42 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/server/src/connectors/users.js: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcrypt' 2 | import { Users } from '../providers' 3 | import * as Tickets from './tickets' 4 | 5 | const SALT_ROUNDS = 10 6 | 7 | export async function getById (id) { 8 | return await Users.findOne({ _id: id }) 9 | } 10 | 11 | export async function getByUsername (username) { 12 | return await Users.findOne({ username }) 13 | } 14 | 15 | export async function isPasswordMatching (user, password) { 16 | return await bcrypt.compare(password, user.password) 17 | } 18 | 19 | export async function hashPassword (password) { 20 | return await bcrypt.hash(password, SALT_ROUNDS) 21 | } 22 | 23 | export async function createUser ({ username, email, password }) { 24 | const user = await getByUsername(username) 25 | if (user) { 26 | throw new Error('Duplicate username') 27 | } else { 28 | const hash = await hashPassword(password) 29 | const result = await Users.insert({ 30 | username, 31 | email, 32 | password: hash, 33 | }) 34 | 35 | Tickets.create({ user: result }, { 36 | title: 'Welcome', 37 | description: 'Welcome to our support center!', 38 | }) 39 | 40 | return result 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/server/src/auth.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport' 2 | import { OAuth2Strategy as GoogleStrategy } from 'passport-google-oauth' 3 | import * as Users from './connectors/users' 4 | import { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, PUBLIC_URL } from './config' 5 | 6 | passport.use(new GoogleStrategy( 7 | { 8 | clientID: GOOGLE_CLIENT_ID, 9 | clientSecret: GOOGLE_CLIENT_SECRET, 10 | callbackURL: `${PUBLIC_URL}/auth/google/callback`, 11 | }, 12 | async (accessToken, refreshToken, profile, done) => { 13 | try { 14 | const user = await Users.findOrCreate({ 15 | profile: { 16 | id: profile.id, 17 | displayName: profile.displayName, 18 | photos: profile.photos, 19 | }, 20 | accessToken, 21 | refreshToken, 22 | }) 23 | done(null, user) 24 | } catch (e) { 25 | done(e) 26 | } 27 | } 28 | )) 29 | 30 | passport.serializeUser( 31 | (user, done) => { 32 | done(null, user._id) 33 | } 34 | ) 35 | 36 | passport.deserializeUser( 37 | async (id, done) => { 38 | const user = await Users.getById(id) 39 | const err = !user ? new Error('User not found') : null 40 | done(err, user || null) 41 | } 42 | ) 43 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | 4 | import PageHome from './components/PageHome.vue' 5 | import PageStoreItem from './components/PageStoreItem.vue' 6 | import PageCheckout from './components/PageCheckout.vue' 7 | import PageLocale from './components/PageLocale.vue' 8 | import PageNotFound from './components/PageNotFound.vue' 9 | 10 | Vue.use(VueRouter) 11 | 12 | const routes = [ 13 | { path: '/', name: 'home', component: PageHome }, 14 | { path: '/item/:id', name: 'store-item', component: PageStoreItem, props: route => ({ id: parseInt(route.params.id) }) }, 15 | { path: '/checkout', name: 'checkout', component: PageCheckout }, 16 | { path: '/locale', name: 'locale', component: PageLocale }, 17 | { path: '*', component: PageNotFound }, 18 | ] 19 | 20 | export function createRouter () { 21 | const router = new VueRouter({ 22 | routes, 23 | mode: 'history', 24 | scrollBehavior (to, from, savedPosition) { 25 | if (savedPosition) { 26 | return savedPosition 27 | } 28 | if (to.hash) { 29 | return { selector: to.hash } 30 | } 31 | return { x: 0, y: 0 } 32 | }, 33 | }) 34 | 35 | return router 36 | } 37 | -------------------------------------------------------------------------------- /Chapter03/chapter3-full/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Castle Duel 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/components/BaseButton.spec.js: -------------------------------------------------------------------------------- 1 | import BaseButton from './BaseButton.vue' 2 | import { shallow } from 'vue-test-utils' 3 | import { createRenderer } from 'vue-server-renderer' 4 | 5 | describe('BaseButton', () => { 6 | test('click event', () => { 7 | const wrapper = shallow(BaseButton) 8 | wrapper.trigger('click') 9 | expect(wrapper.emitted().click).toBeTruthy() 10 | }) 11 | 12 | test('icon prop', () => { 13 | const wrapper = shallow(BaseButton, { 14 | propsData: { 15 | icon: 'add', 16 | }, 17 | }) 18 | expect(wrapper.contains('.icon')).toBe(true) 19 | const icon = wrapper.find('.icon') 20 | expect(icon.text()).toBe('add') 21 | }) 22 | 23 | test('snapshot', () => { 24 | const renderer = createRenderer() 25 | const wrapper = shallow(BaseButton, { 26 | // Props values 27 | propsData: { 28 | icon: 'add', 29 | disabled: true, 30 | badge: '3', 31 | }, 32 | // Slots content 33 | slots: { 34 | default: 'Add Item', 35 | }, 36 | }) 37 | renderer.renderToString(wrapper.vm, (err, str) => { 38 | if (err) throw new Error(err) 39 | expect(str).toMatchSnapshot() 40 | }) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "support-center", 3 | "version": "1.0.0", 4 | "description": "A web app wich allows customers posting support ticket and reading a FAQ.", 5 | "author": "Guillaume Chau ", 6 | "license": "MIT", 7 | "private": true, 8 | "scripts": { 9 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", 10 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" 11 | }, 12 | "dependencies": { 13 | "moment": "^2.19.2", 14 | "vue": "^2.5.8", 15 | "vue-router": "^3.0.1" 16 | }, 17 | "browserslist": [ 18 | "> 1%", 19 | "last 2 versions", 20 | "not ie <= 8" 21 | ], 22 | "devDependencies": { 23 | "babel-core": "^6.26.0", 24 | "babel-loader": "^7.1.2", 25 | "babel-polyfill": "^6.26.0", 26 | "babel-preset-env": "^1.6.0", 27 | "babel-preset-stage-3": "^6.24.1", 28 | "babel-preset-vue": "^1.2.1", 29 | "cross-env": "^5.0.5", 30 | "css-loader": "^0.28.7", 31 | "file-loader": "^1.1.4", 32 | "stylus": "^0.54.5", 33 | "stylus-loader": "^3.0.1", 34 | "vue-loader": "^13.0.5", 35 | "vue-template-compiler": "^2.5.8", 36 | "webpack": "^3.6.0", 37 | "webpack-dev-server": "^2.9.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/server/src/index.js: -------------------------------------------------------------------------------- 1 | import cookieParser from 'cookie-parser' 2 | import bodyParser from 'body-parser' 3 | import express from 'express' 4 | import passport from 'passport' 5 | import session from 'express-session' 6 | import cors from 'cors' 7 | import uuid from 'uuid/v4' 8 | 9 | import './auth' 10 | 11 | import routes from './routes' 12 | 13 | const PORT = process.env.PORT || 3000 14 | const SECRET = process.env.SECRET || 'TR7_9cDZ5Re-@lT3Z1|58F' 15 | const CLIENT_ORIGIN = process.env.CLIENT_ORIGIN || 'http://localhost:8080' 16 | 17 | const corsOptions = { 18 | origin: CLIENT_ORIGIN, 19 | credentials: true, 20 | } 21 | 22 | const app = express() 23 | 24 | app.use(cors(corsOptions)) 25 | 26 | app.use(cookieParser(SECRET)) 27 | 28 | app.use(bodyParser.urlencoded({ extended: true })) 29 | app.use(bodyParser.json()) 30 | 31 | app.use(session({ 32 | genid: () => uuid(), 33 | secret: SECRET, 34 | resave: true, 35 | saveUninitialized: true, 36 | cookie: { 37 | maxAge: 3 * 60 * 60 * 1000, 38 | secure: process.env.NODE_ENV === 'production', 39 | }, 40 | })) 41 | 42 | app.use(passport.initialize()) 43 | app.use(passport.session()) 44 | 45 | 46 | routes(app) 47 | 48 | app.listen(PORT, () => { 49 | console.log(`Server listening on port ${PORT}`) 50 | }) 51 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/components/SmartForm.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 60 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/components/Ticket.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 53 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "description": "Trying out Vue.js!", 4 | "version": "1.0.0", 5 | "author": "Guillaume Chau", 6 | "license": "MIT", 7 | "private": true, 8 | "scripts": { 9 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", 10 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" 11 | }, 12 | "dependencies": { 13 | "vue": "^2.5.8" 14 | }, 15 | "browserslist": [ 16 | "> 1%", 17 | "last 2 versions", 18 | "not ie <= 8" 19 | ], 20 | "devDependencies": { 21 | "babel-core": "^6.26.0", 22 | "babel-loader": "^7.1.2", 23 | "babel-polyfill": "^6.26.0", 24 | "babel-preset-env": "^1.6.0", 25 | "babel-preset-stage-3": "^6.24.1", 26 | "babel-preset-vue": "^1.2.1", 27 | "cross-env": "^5.0.5", 28 | "css-loader": "^0.28.7", 29 | "file-loader": "^1.1.4", 30 | "less": "^2.7.3", 31 | "less-loader": "^4.0.5", 32 | "node-sass": "^4.7.2", 33 | "pug": "^2.0.0-rc.4", 34 | "pug-loader": "^2.3.0", 35 | "sass-loader": "^6.0.6", 36 | "stylus": "^0.54.5", 37 | "stylus-loader": "^3.0.1", 38 | "vue-loader": "^13.0.5", 39 | "vue-template-compiler": "^2.5.8", 40 | "webpack": "^3.6.0", 41 | "webpack-dev-server": "^2.9.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Chapter03/chapter3-full/svg/food-bar.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Chapter03/chapter3-download/svg/food-bar.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Chapter03/chapter3-full/svg/health-bar.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Chapter03/chapter3-download/svg/health-bar.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import store from './store' 4 | 5 | import Login from './components/Login.vue' 6 | import GeoBlog from './components/GeoBlog.vue' 7 | import NotFound from './components/NotFound.vue' 8 | 9 | Vue.use(VueRouter) 10 | 11 | const routes = [ 12 | { path: '/', name: 'home', component: GeoBlog, meta: { private: true } }, 13 | { path: '/login', name: 'login', component: Login }, 14 | { path: '*', component: NotFound }, 15 | ] 16 | 17 | const router = new VueRouter({ 18 | routes, 19 | mode: 'history', 20 | scrollBehavior (to, from, savedPosition) { 21 | if (savedPosition) { 22 | return savedPosition 23 | } 24 | if (to.hash) { 25 | return { selector: to.hash } 26 | } 27 | return { x: 0, y: 0 } 28 | }, 29 | }) 30 | 31 | router.beforeEach((to, from, next) => { 32 | console.log('to', to.name) 33 | const user = store.getters.user 34 | if (to.matched.some(r => r.meta.private) && !user) { 35 | next({ 36 | name: 'login', 37 | params: { 38 | wantedRoute: to.fullPath, 39 | }, 40 | }) 41 | return 42 | } 43 | if (to.matched.some(r => r.meta.guest) && user) { 44 | next({ name: 'home' }) 45 | return 46 | } 47 | next() 48 | }) 49 | 50 | export default router 51 | -------------------------------------------------------------------------------- /Chapter03/chapter3-full/state.js: -------------------------------------------------------------------------------- 1 | // Some usefull variables 2 | var maxHealth = 10 3 | var maxFood = 10 4 | var handSize = 5 5 | var cardUid = 0 6 | var currentPlayingCard = null 7 | 8 | // The consolidated state of our app 9 | var state = { 10 | // UI 11 | activeOverlay: null, 12 | // World 13 | worldRatio: getWorldRatio(), 14 | // Game 15 | turn: 1, 16 | players: [ 17 | { 18 | name: 'Anne of Cleves', 19 | food: 10, 20 | health: 10, 21 | skipTurn: false, 22 | skippedTurn: false, 23 | hand: [], 24 | lastPlayedCardId: null, 25 | dead: false, 26 | }, 27 | { 28 | name: 'William the Bald', 29 | food: 10, 30 | health: 10, 31 | skipTurn: false, 32 | skippedTurn: false, 33 | hand: [], 34 | lastPlayedCardId: null, 35 | dead: false, 36 | }, 37 | ], 38 | currentPlayerIndex: Math.round(Math.random()), 39 | get currentPlayer () { 40 | return state.players[state.currentPlayerIndex] 41 | }, 42 | get currentOpponentId () { 43 | return state.currentPlayerIndex === 0 ? 1 : 0 44 | }, 45 | get currentOpponent () { 46 | return state.players[state.currentOpponentId] 47 | }, 48 | get currentHand () { 49 | return state.currentPlayer.hand 50 | }, 51 | drawPile: pile, 52 | discardPile: {}, 53 | canPlay: false, 54 | } 55 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/plugins/fetch.js: -------------------------------------------------------------------------------- 1 | import state from '../state' 2 | import router from '../router' 3 | 4 | let baseUrl 5 | 6 | export async function $fetch (url, options) { 7 | const finalOptions = Object.assign({}, { 8 | headers: { 9 | 'Content-Type': 'application/json', 10 | }, 11 | credentials: 'include', 12 | }, options) 13 | const response = await fetch(`${baseUrl}${url}`, finalOptions) 14 | if (response.ok) { 15 | const data = await response.json() 16 | return data 17 | } else if (response.status === 403) { 18 | // If the session is no longer valid 19 | // We logout 20 | state.user = null 21 | 22 | // If the route is private 23 | // We go to the login screen 24 | if (router.currentRoute.matched.some(r => r.meta.private)) { 25 | router.replace({ name: 'login', params: { 26 | wantedRoute: router.currentRoute.fullPath, 27 | }}) 28 | } 29 | } else { 30 | const message = await response.text() 31 | const error = new Error(message) 32 | error.response = response 33 | throw error 34 | } 35 | } 36 | 37 | export default { 38 | install (Vue, options) { 39 | console.log('Installed!', options) 40 | 41 | // Plugin options 42 | baseUrl = options.baseUrl 43 | 44 | // Fetch 45 | Vue.prototype.$fetch = $fetch 46 | }, 47 | } 48 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geolocated-blog", 3 | "version": "1.0.0", 4 | "description": "A geolocated blog web app.", 5 | "author": "Guillaume Chau", 6 | "license": "MIT", 7 | "private": true, 8 | "scripts": { 9 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", 10 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" 11 | }, 12 | "dependencies": { 13 | "moment": "^2.19.2", 14 | "socket.io-client": "^2.0.4", 15 | "vue": "^2.5.8", 16 | "vue-googlemaps": "^0.0.6", 17 | "vue-router": "^3.0.1", 18 | "vuex": "^3.0.1", 19 | "vuex-router-sync": "^5.0.0" 20 | }, 21 | "browserslist": [ 22 | "> 1%", 23 | "last 2 versions", 24 | "not ie <= 8" 25 | ], 26 | "devDependencies": { 27 | "babel-core": "^6.26.0", 28 | "babel-loader": "^7.1.2", 29 | "babel-polyfill": "^6.26.0", 30 | "babel-preset-env": "^1.6.0", 31 | "babel-preset-stage-3": "^6.24.1", 32 | "babel-preset-vue": "^1.2.1", 33 | "cross-env": "^5.0.5", 34 | "css-loader": "^0.28.7", 35 | "file-loader": "^1.1.4", 36 | "stylus": "^0.54.5", 37 | "stylus-loader": "^3.0.1", 38 | "vue-loader": "^13.0.5", 39 | "vue-template-compiler": "^2.5.8", 40 | "webpack": "^3.6.0", 41 | "webpack-dev-server": "^2.9.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/components/content/LocationInfo.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 51 | -------------------------------------------------------------------------------- /Chapter05/chapter5-download/style/mixins.styl: -------------------------------------------------------------------------------- 1 | ellipsis() { 2 | overflow: hidden; 3 | -ms-text-overflow: ellipsis; 4 | text-overflow: ellipsis; 5 | white-space: nowrap; 6 | } 7 | 8 | bounds($distance) { 9 | top: $distance; 10 | bottom: $distance; 11 | right: $distance; 12 | left: $distance; 13 | } 14 | 15 | overlay() { 16 | position: absolute; 17 | bounds(0); 18 | } 19 | 20 | flex-box() { 21 | display: flex; 22 | 23 | & > * { 24 | flex: auto 0 0; 25 | } 26 | } 27 | 28 | h-box() { 29 | flex-box(); 30 | flex-direction: row; 31 | } 32 | 33 | v-box() { 34 | flex-box(); 35 | flex-direction: column; 36 | } 37 | 38 | flex-control() { 39 | width: 0 !important; 40 | } 41 | 42 | box-center() { 43 | align-items: center; 44 | justify-content: center; 45 | } 46 | 47 | toolbar-btn($bg) { 48 | background: fade($bg, 80%); 49 | color: black; 50 | transition: background 0.2s; 51 | 52 | &:hover { 53 | color: black; 54 | background: $bg; 55 | } 56 | } 57 | 58 | space-between-x($margin) { 59 | margin-right: $margin; 60 | 61 | &:last-child { 62 | margin-right: 0; 63 | } 64 | } 65 | 66 | space-between-y($margin) { 67 | margin-bottom: $margin; 68 | 69 | &:last-child { 70 | margin-bottom: 0; 71 | } 72 | } 73 | 74 | unselectable() { 75 | -moz-user-select: none; 76 | -webkit-user-select: none; 77 | -ms-user-select: none; 78 | user-select: none; 79 | } 80 | -------------------------------------------------------------------------------- /Chapter06/chapter6-download/styles/mixins.styl: -------------------------------------------------------------------------------- 1 | ellipsis() { 2 | overflow: hidden; 3 | -ms-text-overflow: ellipsis; 4 | text-overflow: ellipsis; 5 | white-space: nowrap; 6 | } 7 | 8 | bounds($distance) { 9 | top: $distance; 10 | bottom: $distance; 11 | right: $distance; 12 | left: $distance; 13 | } 14 | 15 | overlay() { 16 | position: absolute; 17 | bounds(0); 18 | } 19 | 20 | flex-box() { 21 | display: flex; 22 | 23 | & > * { 24 | flex: auto 0 0; 25 | } 26 | } 27 | 28 | h-box() { 29 | flex-box(); 30 | flex-direction: row; 31 | } 32 | 33 | v-box() { 34 | flex-box(); 35 | flex-direction: column; 36 | } 37 | 38 | flex-control() { 39 | width: 0 !important; 40 | } 41 | 42 | box-center() { 43 | align-items: center; 44 | justify-content: center; 45 | } 46 | 47 | toolbar-btn($bg) { 48 | background: fade($bg, 80%); 49 | color: black; 50 | transition: background 0.2s; 51 | 52 | &:hover { 53 | color: black; 54 | background: $bg; 55 | } 56 | } 57 | 58 | space-between-x($margin) { 59 | margin-right: $margin; 60 | 61 | &:last-child { 62 | margin-right: 0; 63 | } 64 | } 65 | 66 | space-between-y($margin) { 67 | margin-bottom: $margin; 68 | 69 | &:last-child { 70 | margin-bottom: 0; 71 | } 72 | } 73 | 74 | unselectable() { 75 | -moz-user-select: none; 76 | -webkit-user-select: none; 77 | -ms-user-select: none; 78 | user-select: none; 79 | } 80 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/styles/mixins.styl: -------------------------------------------------------------------------------- 1 | ellipsis() { 2 | overflow: hidden; 3 | -ms-text-overflow: ellipsis; 4 | text-overflow: ellipsis; 5 | white-space: nowrap; 6 | } 7 | 8 | bounds($distance) { 9 | top: $distance; 10 | bottom: $distance; 11 | right: $distance; 12 | left: $distance; 13 | } 14 | 15 | overlay() { 16 | position: absolute; 17 | bounds(0); 18 | } 19 | 20 | flex-box() { 21 | display: flex; 22 | 23 | & > * { 24 | flex: auto 0 0; 25 | } 26 | } 27 | 28 | h-box() { 29 | flex-box(); 30 | flex-direction: row; 31 | } 32 | 33 | v-box() { 34 | flex-box(); 35 | flex-direction: column; 36 | } 37 | 38 | flex-control() { 39 | width: 0 !important; 40 | } 41 | 42 | box-center() { 43 | align-items: center; 44 | justify-content: center; 45 | } 46 | 47 | toolbar-btn($bg) { 48 | background: fade($bg, 80%); 49 | color: black; 50 | transition: background 0.2s; 51 | 52 | &:hover { 53 | color: black; 54 | background: $bg; 55 | } 56 | } 57 | 58 | space-between-x($margin) { 59 | margin-right: $margin; 60 | 61 | &:last-child { 62 | margin-right: 0; 63 | } 64 | } 65 | 66 | space-between-y($margin) { 67 | margin-bottom: $margin; 68 | 69 | &:last-child { 70 | margin-bottom: 0; 71 | } 72 | } 73 | 74 | unselectable() { 75 | -moz-user-select: none; 76 | -webkit-user-select: none; 77 | -ms-user-select: none; 78 | user-select: none; 79 | } 80 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/style/mixins.styl: -------------------------------------------------------------------------------- 1 | ellipsis() { 2 | overflow: hidden; 3 | -ms-text-overflow: ellipsis; 4 | text-overflow: ellipsis; 5 | white-space: nowrap; 6 | } 7 | 8 | bounds($distance) { 9 | top: $distance; 10 | bottom: $distance; 11 | right: $distance; 12 | left: $distance; 13 | } 14 | 15 | overlay() { 16 | position: absolute; 17 | bounds(0); 18 | } 19 | 20 | flex-box() { 21 | display: flex; 22 | 23 | & > * { 24 | flex: auto 0 0; 25 | } 26 | } 27 | 28 | h-box() { 29 | flex-box(); 30 | flex-direction: row; 31 | } 32 | 33 | v-box() { 34 | flex-box(); 35 | flex-direction: column; 36 | } 37 | 38 | flex-control() { 39 | width: 0 !important; 40 | } 41 | 42 | box-center() { 43 | align-items: center; 44 | justify-content: center; 45 | } 46 | 47 | toolbar-btn($bg) { 48 | background: fade($bg, 80%); 49 | color: black; 50 | transition: background 0.2s; 51 | 52 | &:hover { 53 | color: black; 54 | background: $bg; 55 | } 56 | } 57 | 58 | space-between-x($margin) { 59 | margin-right: $margin; 60 | 61 | &:last-child { 62 | margin-right: 0; 63 | } 64 | } 65 | 66 | space-between-y($margin) { 67 | margin-bottom: $margin; 68 | 69 | &:last-child { 70 | margin-bottom: 0; 71 | } 72 | } 73 | 74 | unselectable() { 75 | -moz-user-select: none; 76 | -webkit-user-select: none; 77 | -ms-user-select: none; 78 | user-select: none; 79 | } 80 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/styles/mixins.styl: -------------------------------------------------------------------------------- 1 | ellipsis() { 2 | overflow: hidden; 3 | -ms-text-overflow: ellipsis; 4 | text-overflow: ellipsis; 5 | white-space: nowrap; 6 | } 7 | 8 | bounds($distance) { 9 | top: $distance; 10 | bottom: $distance; 11 | right: $distance; 12 | left: $distance; 13 | } 14 | 15 | overlay() { 16 | position: absolute; 17 | bounds(0); 18 | } 19 | 20 | flex-box() { 21 | display: flex; 22 | 23 | & > * { 24 | flex: auto 0 0; 25 | } 26 | } 27 | 28 | h-box() { 29 | flex-box(); 30 | flex-direction: row; 31 | } 32 | 33 | v-box() { 34 | flex-box(); 35 | flex-direction: column; 36 | } 37 | 38 | flex-control() { 39 | width: 0 !important; 40 | } 41 | 42 | box-center() { 43 | align-items: center; 44 | justify-content: center; 45 | } 46 | 47 | toolbar-btn($bg) { 48 | background: fade($bg, 80%); 49 | color: black; 50 | transition: background 0.2s; 51 | 52 | &:hover { 53 | color: black; 54 | background: $bg; 55 | } 56 | } 57 | 58 | space-between-x($margin) { 59 | margin-right: $margin; 60 | 61 | &:last-child { 62 | margin-right: 0; 63 | } 64 | } 65 | 66 | space-between-y($margin) { 67 | margin-bottom: $margin; 68 | 69 | &:last-child { 70 | margin-bottom: 0; 71 | } 72 | } 73 | 74 | unselectable() { 75 | -moz-user-select: none; 76 | -webkit-user-select: none; 77 | -ms-user-select: none; 78 | user-select: none; 79 | } 80 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/styles/mixins.styl: -------------------------------------------------------------------------------- 1 | ellipsis() { 2 | overflow: hidden; 3 | -ms-text-overflow: ellipsis; 4 | text-overflow: ellipsis; 5 | white-space: nowrap; 6 | } 7 | 8 | bounds($distance) { 9 | top: $distance; 10 | bottom: $distance; 11 | right: $distance; 12 | left: $distance; 13 | } 14 | 15 | overlay() { 16 | position: absolute; 17 | bounds(0); 18 | } 19 | 20 | flex-box() { 21 | display: flex; 22 | 23 | & > * { 24 | flex: auto 0 0; 25 | } 26 | } 27 | 28 | h-box() { 29 | flex-box(); 30 | flex-direction: row; 31 | } 32 | 33 | v-box() { 34 | flex-box(); 35 | flex-direction: column; 36 | } 37 | 38 | flex-control() { 39 | width: 0 !important; 40 | } 41 | 42 | box-center() { 43 | align-items: center; 44 | justify-content: center; 45 | } 46 | 47 | toolbar-btn($bg) { 48 | background: fade($bg, 80%); 49 | color: black; 50 | transition: background 0.2s; 51 | 52 | &:hover { 53 | color: black; 54 | background: $bg; 55 | } 56 | } 57 | 58 | space-between-x($margin) { 59 | margin-right: $margin; 60 | 61 | &:last-child { 62 | margin-right: 0; 63 | } 64 | } 65 | 66 | space-between-y($margin) { 67 | margin-bottom: $margin; 68 | 69 | &:last-child { 70 | margin-bottom: 0; 71 | } 72 | } 73 | 74 | unselectable() { 75 | -moz-user-select: none; 76 | -webkit-user-select: none; 77 | -ms-user-select: none; 78 | user-select: none; 79 | } 80 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/store/items.js: -------------------------------------------------------------------------------- 1 | import http from '../utils/http' 2 | 3 | export default { 4 | namespaced: true, 5 | 6 | state () { 7 | return { 8 | items: [], 9 | loading: false, 10 | searchText: '', 11 | } 12 | }, 13 | 14 | getters: { 15 | items: state => { 16 | if (state.searchText) { 17 | const reg = new RegExp(state.searchText.trim().toLowerCase().replace(/\s+/g, '|')) 18 | return state.items.filter( 19 | item => item.title.toLowerCase().search(reg) !== -1 20 | ) 21 | } else { 22 | return state.items 23 | } 24 | }, 25 | itemsMap: state => state.items.reduce((obj, item) => { 26 | obj[item.id] = item 27 | return obj 28 | }, {}), 29 | loading: state => state.loading, 30 | searchText: state => state.searchText, 31 | }, 32 | 33 | mutations: { 34 | items (state, value) { 35 | state.items = value 36 | }, 37 | loading (state, value) { 38 | state.loading = value 39 | }, 40 | searchText (state, value) { 41 | state.searchText = value 42 | }, 43 | }, 44 | 45 | actions: { 46 | async fetchItems ({ commit }) { 47 | commit('loading', true) 48 | const result = await http.get('items') 49 | commit('items', result.data) 50 | commit('loading', false) 51 | }, 52 | 53 | setSearchText ({ commit }, value) { 54 | commit('searchText', value) 55 | }, 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/store/items.js: -------------------------------------------------------------------------------- 1 | import http from '../utils/http' 2 | 3 | export default { 4 | namespaced: true, 5 | 6 | state () { 7 | return { 8 | items: [], 9 | loading: false, 10 | searchText: '', 11 | } 12 | }, 13 | 14 | getters: { 15 | items: state => { 16 | if (state.searchText) { 17 | const reg = new RegExp(state.searchText.trim().toLowerCase().replace(/\s+/g, '|')) 18 | return state.items.filter( 19 | item => item.title.toLowerCase().search(reg) !== -1 20 | ) 21 | } else { 22 | return state.items 23 | } 24 | }, 25 | itemsMap: state => state.items.reduce((obj, item) => { 26 | obj[item.id] = item 27 | return obj 28 | }, {}), 29 | loading: state => state.loading, 30 | searchText: state => state.searchText, 31 | }, 32 | 33 | mutations: { 34 | items (state, value) { 35 | state.items = value 36 | }, 37 | loading (state, value) { 38 | state.loading = value 39 | }, 40 | searchText (state, value) { 41 | state.searchText = value 42 | }, 43 | }, 44 | 45 | actions: { 46 | async fetchItems ({ commit }) { 47 | commit('loading', true) 48 | const result = await http.get('items') 49 | commit('items', result.data) 50 | commit('loading', false) 51 | }, 52 | 53 | setSearchText ({ commit }, value) { 54 | commit('searchText', value) 55 | }, 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/components/PageHome.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 52 | 53 | 72 | -------------------------------------------------------------------------------- /Chapter02/chapter2-simple/script.js: -------------------------------------------------------------------------------- 1 | // New VueJS instance 2 | new Vue({ 3 | name: 'notebook', 4 | 5 | // CSS selector of the root DOM element 6 | el: '#notebook', 7 | 8 | // Some data 9 | data () { 10 | return { 11 | // content: 'This is a note', 12 | content: localStorage.getItem('content') || 'You can write in **markdown**', 13 | } 14 | }, 15 | 16 | // Computed properties 17 | computed: { 18 | notePreview () { 19 | // Markdown rendered to HTML 20 | return marked(this.content) 21 | }, 22 | }, 23 | 24 | // Change watchers 25 | watch: { 26 | /*content: { 27 | handler (val, oldVal) { 28 | console.log('new note:', val, 'old note:', oldVal) 29 | localStorage.setItem('content', val) 30 | }, 31 | immediate: true, 32 | },*/ 33 | 34 | /*content (val) { 35 | localStorage.setItem('content', val) 36 | },*/ 37 | 38 | /*content: { 39 | handler: 'saveNote', 40 | },*/ 41 | 42 | content: 'saveNote', 43 | }, 44 | 45 | methods: { 46 | saveNote (val, oldVal) { 47 | console.log('new note:', val, 'old note:', oldVal) 48 | console.log('saving note:', this.content) 49 | localStorage.setItem('content', this.content) 50 | this.reportOperation('saving') 51 | }, 52 | reportOperation (opName) { 53 | console.log('The', opName, 'operation was completed!') 54 | }, 55 | }, 56 | 57 | /* created () { 58 | this.content = localStorage.getItem('content') || 'You can write in **markdown**' 59 | }, */ 60 | }) 61 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/server/src/utils/cache.js: -------------------------------------------------------------------------------- 1 | 2 | import LRU from 'lru-cache' 3 | 4 | export function cacheFactory (options) { 5 | const finalOptions = Object.assign({}, { 6 | max: 100, 7 | maxAge: 1000 * 60 * 60, 8 | key: 'id', 9 | refresh: null, 10 | resolveKey: null, 11 | }, options) 12 | 13 | const cache = { 14 | lru: LRU({ 15 | max: finalOptions.max, 16 | maxAge: finalOptions.maxAge, 17 | }), 18 | 19 | async set (item, ...args) { 20 | let key 21 | if (args && cache.resolveKey) { 22 | key = await cache.resolveKey(...args) 23 | } else { 24 | key = item[cache.key] 25 | } 26 | cache.lru.set(key, item) 27 | }, 28 | 29 | async put (items, mapKeyArgs = null) { 30 | for (const item of items) { 31 | if (mapKeyArgs) { 32 | const args = await mapKeyArgs(item) 33 | await cache.setOne(item, ...args) 34 | } else { 35 | await cache.setOne(item) 36 | } 37 | } 38 | }, 39 | 40 | async get (...args) { 41 | let key 42 | if (cache.resolveKey) { 43 | key = await cache.resolveKey(...args) 44 | } else { 45 | key = args[0] 46 | } 47 | 48 | let item = cache.lru.get(key) 49 | 50 | // Refresh 51 | if (!item && cache.refresh) { 52 | const refreshArgs = cache.resolveKey ? args : key 53 | item = await cache.refresh(...refreshArgs) 54 | cache.set(item, ...args) 55 | } 56 | 57 | return item 58 | }, 59 | 60 | ...finalOptions, 61 | } 62 | 63 | return cache 64 | } 65 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/server/src/utils/cache.js: -------------------------------------------------------------------------------- 1 | 2 | import LRU from 'lru-cache' 3 | 4 | export function cacheFactory (options) { 5 | const finalOptions = Object.assign({}, { 6 | max: 100, 7 | maxAge: 1000 * 60 * 60, 8 | key: 'id', 9 | refresh: null, 10 | resolveKey: null, 11 | }, options) 12 | 13 | const cache = { 14 | lru: LRU({ 15 | max: finalOptions.max, 16 | maxAge: finalOptions.maxAge, 17 | }), 18 | 19 | async set (item, ...args) { 20 | let key 21 | if (args && cache.resolveKey) { 22 | key = await cache.resolveKey(...args) 23 | } else { 24 | key = item[cache.key] 25 | } 26 | cache.lru.set(key, item) 27 | }, 28 | 29 | async put (items, mapKeyArgs = null) { 30 | for (const item of items) { 31 | if (mapKeyArgs) { 32 | const args = await mapKeyArgs(item) 33 | await cache.setOne(item, ...args) 34 | } else { 35 | await cache.setOne(item) 36 | } 37 | } 38 | }, 39 | 40 | async get (...args) { 41 | let key 42 | if (cache.resolveKey) { 43 | key = await cache.resolveKey(...args) 44 | } else { 45 | key = args[0] 46 | } 47 | 48 | let item = cache.lru.get(key) 49 | 50 | // Refresh 51 | if (!item && cache.refresh) { 52 | const refreshArgs = cache.resolveKey ? args : key 53 | item = await cache.refresh(...refreshArgs) 54 | cache.set(item, ...args) 55 | } 56 | 57 | return item 58 | }, 59 | 60 | ...finalOptions, 61 | } 62 | 63 | return cache 64 | } 65 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/store/maps.js: -------------------------------------------------------------------------------- 1 | export default { 2 | namespaced: true, 3 | 4 | state () { 5 | return { 6 | center: { 7 | lat: 48.8538302, 8 | lng: 2.2982161, 9 | }, 10 | userPosition: null, 11 | zoom: 15, 12 | } 13 | }, 14 | 15 | getters: { 16 | center: state => state.center, 17 | userPosition: state => state.userPosition, 18 | zoom: state => state.zoom, 19 | }, 20 | 21 | mutations: { 22 | center (state, value) { 23 | state.center = value 24 | }, 25 | userPosition (state, value) { 26 | state.userPosition = value 27 | }, 28 | zoom (state, value) { 29 | state.zoom = value 30 | }, 31 | }, 32 | 33 | actions: { 34 | async centerOnUser ({ dispatch, getters }) { 35 | const position = getters.userPosition 36 | if (position) { 37 | dispatch('setCenter', position) 38 | } 39 | }, 40 | 41 | setBounds ({ dispatch }, value) { 42 | console.log('updateBounds') 43 | dispatch('posts/fetchPosts', { 44 | mapBounds: value, 45 | }, { 46 | root: true, 47 | }) 48 | }, 49 | 50 | setCenter ({ commit }, value) { 51 | commit('center', value) 52 | }, 53 | 54 | setUserPosition ({ dispatch, commit, getters }, value) { 55 | const position = getters.userPosition 56 | commit('userPosition', value) 57 | // Initial center on user position 58 | if (!position) { 59 | dispatch('centerOnUser') 60 | } 61 | }, 62 | 63 | setZoom ({ commit }, value) { 64 | commit('zoom', value) 65 | }, 66 | }, 67 | } 68 | -------------------------------------------------------------------------------- /chapter08/.meteor/versions: -------------------------------------------------------------------------------- 1 | akryum:vue-component@0.12.3 2 | akryum:vue-component-dev-client@0.3.0 3 | akryum:vue-component-dev-server@0.0.11 4 | akryum:vue-stylus@0.0.6 5 | allow-deny@1.1.0 6 | autoupdate@1.3.12 7 | babel-compiler@6.24.7 8 | babel-runtime@1.1.1 9 | base64@1.0.10 10 | binary-heap@1.0.10 11 | blaze-tools@1.0.10 12 | boilerplate-generator@1.3.1 13 | caching-compiler@1.1.9 14 | caching-html-compiler@1.1.2 15 | callback-hook@1.0.10 16 | check@1.2.5 17 | ddp@1.4.0 18 | ddp-client@2.2.0 19 | ddp-common@1.3.0 20 | ddp-server@2.1.1 21 | deps@1.0.12 22 | diff-sequence@1.0.7 23 | dynamic-import@0.2.1 24 | ecmascript@0.9.0 25 | ecmascript-runtime@0.5.0 26 | ecmascript-runtime-client@0.5.0 27 | ecmascript-runtime-server@0.5.0 28 | ejson@1.1.0 29 | es5-shim@4.6.15 30 | geojson-utils@1.0.10 31 | hot-code-push@1.0.4 32 | html-tools@1.0.11 33 | htmljs@1.0.11 34 | http@1.3.0 35 | id-map@1.0.9 36 | launch-screen@1.1.1 37 | livedata@1.0.18 38 | logging@1.1.19 39 | meteor@1.8.2 40 | meteor-base@1.2.0 41 | minifier-css@1.2.16 42 | minifier-js@2.2.2 43 | minimongo@1.4.2 44 | mobile-experience@1.0.5 45 | mobile-status-bar@1.0.14 46 | modules@0.11.0 47 | modules-runtime@0.9.1 48 | mongo@1.3.0 49 | mongo-dev-server@1.1.0 50 | mongo-id@1.0.6 51 | npm-mongo@2.2.33 52 | ordered-dict@1.0.9 53 | promise@0.10.0 54 | random@1.0.10 55 | reactive-var@1.0.11 56 | reload@1.1.11 57 | retry@1.0.9 58 | routepolicy@1.0.12 59 | shell-server@0.3.0 60 | spacebars-compiler@1.1.3 61 | standard-minifier-css@1.3.5 62 | standard-minifier-js@2.2.3 63 | static-html@1.2.2 64 | templating-tools@1.1.2 65 | tracker@1.1.3 66 | underscore@1.0.10 67 | url@1.1.0 68 | webapp@1.4.0 69 | webapp-hashing@1.0.9 70 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/server/src/socket.js: -------------------------------------------------------------------------------- 1 | import * as Users from './connectors/users' 2 | import * as Posts from './connectors/posts' 3 | 4 | export const holders = {} 5 | 6 | export default function (io) { 7 | io.on('connection', socket => { 8 | const user = socket.request.user 9 | console.log('new user socket', user) 10 | 11 | const holder = { 12 | socket, 13 | selectedPostId: null, 14 | mapBounds: null, 15 | } 16 | 17 | holders[user._id] = holder 18 | 19 | const listeners = { 20 | like (id, userId) { 21 | if (id === holder.selectedPostId) { 22 | socket.emit('like', userId) 23 | } 24 | }, 25 | unlike (id, userId) { 26 | if (id === holder.selectedPostId) { 27 | socket.emit('unlike', userId) 28 | } 29 | }, 30 | comment (id, newComment) { 31 | if (id === holder.selectedPostId) { 32 | socket.emit('comment', newComment) 33 | } 34 | }, 35 | post (post) { 36 | if (geolib.isPointInside({ 37 | latitude: post.position.lat, 38 | longitude: post.position.lng, 39 | }, holder.mapBounds)) { 40 | socket.emit('post', post) 41 | } 42 | }, 43 | } 44 | 45 | for (const event in listeners) { 46 | Posts.events.on(event, listeners[event]) 47 | } 48 | 49 | socket.on('unselect', () => { 50 | holder.selectedPostId = null 51 | }) 52 | 53 | socket.on('disconnect', () => { 54 | for (const event in listeners) { 55 | Posts.events.removeListener(event, listeners[event]) 56 | } 57 | delete holders[user._id] 58 | }) 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/mixins/RemoteData.js: -------------------------------------------------------------------------------- 1 | export default function (resources) { 2 | return { 3 | data () { 4 | let initData = { 5 | remoteDataLoading: 0, 6 | } 7 | 8 | // Initialize data properties 9 | initData.remoteErrors = {} 10 | for (const key in resources) { 11 | initData[key] = null 12 | initData.remoteErrors[key] = null 13 | } 14 | 15 | return initData 16 | }, 17 | 18 | computed: { 19 | remoteDataBusy () { 20 | return this.$data.remoteDataLoading !== 0 21 | }, 22 | 23 | hasRemoteErrors () { 24 | return Object.keys(this.$data.remoteErrors).some( 25 | key => this.$data.remoteErrors[key] 26 | ) 27 | }, 28 | }, 29 | 30 | methods: { 31 | async fetchResource (key, url) { 32 | this.$data.remoteDataLoading++ 33 | this.$data.remoteErrors[key] = null 34 | try { 35 | this.$data[key] = await this.$fetch(url) 36 | } catch (e) { 37 | console.error(e) 38 | this.$data.remoteErrors[key] = e 39 | } 40 | this.$data.remoteDataLoading-- 41 | }, 42 | }, 43 | 44 | created () { 45 | for (const key in resources) { 46 | let url = resources[key] 47 | // If the value is a function 48 | // We watch its result 49 | if (typeof url === 'function') { 50 | this.$watch(url, (val) => { 51 | this.fetchResource(key, val) 52 | }, { 53 | immediate: true, 54 | }) 55 | } else { 56 | this.fetchResource(key, url) 57 | } 58 | } 59 | }, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/components/PageHome.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 59 | 60 | 79 | -------------------------------------------------------------------------------- /Chapter03/chapter3-full/svg/ground0.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 34 | 38 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Chapter03/chapter3-download/svg/ground0.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 34 | 38 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/server.js: -------------------------------------------------------------------------------- 1 | // Reify allow us to load files that use 'import/export' syntax 2 | // inside the node js environment 3 | require('reify') 4 | 5 | const express = require('express') 6 | const { default: langs } = require('./i18n') 7 | const { createBundleRenderer } = require('vue-server-renderer') 8 | const fs = require('fs') 9 | const path = require('path') 10 | 11 | const isProd = process.env.NODE_ENV === 'production' 12 | const resolve = file => path.resolve(__dirname, file) 13 | const templatePath = resolve('./index.template.html') 14 | 15 | // Create Express server app 16 | const server = express() 17 | 18 | // Vue bundle renderer 19 | let renderer 20 | // In development: wait for webpack compilation 21 | // when receiving a SSR request 22 | let readyPromise 23 | 24 | if (isProd) { 25 | // TODO production 26 | } else { 27 | // TODO development 28 | } 29 | 30 | // Serve static files 31 | const serve = (path, cache) => express.static(resolve(path), { 32 | maxAge: cache && isProd ? 1000 * 60 * 60 * 24 * 30 : 0, 33 | }) 34 | 35 | // Serve dist files 36 | server.use('/dist', serve('./dist', true)) 37 | 38 | // Render the Vue app using the bundle renderer 39 | function renderApp (req, res) { 40 | // TODO render 41 | } 42 | 43 | // Process SSR requests 44 | let ssr 45 | if (isProd) { 46 | ssr = renderApp 47 | } else { 48 | // In development: wait for webpack compilation 49 | // when receiving a SSR request 50 | ssr = (req, res) => { 51 | readyPromise.then(() => renderApp(req, res)) 52 | } 53 | } 54 | server.get('*', ssr) 55 | 56 | // Listening 57 | const port = process.env.PORT || 8080 58 | server.listen(port, () => { 59 | console.log(`server started at localhost:${port}`) 60 | }) 61 | -------------------------------------------------------------------------------- /Chapter03/chapter3-download/svg/ground1.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 34 | 38 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Chapter03/chapter3-full/svg/ground1.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 34 | 38 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/styles/transitions.styl: -------------------------------------------------------------------------------- 1 | 2 | .fade-enter-active, 3 | .fade-leave-active { 4 | transition: opacity .15s linear; 5 | } 6 | 7 | .fade-enter, 8 | .fade-leave-to { 9 | opacity: 0; 10 | } 11 | 12 | .zoom-enter-active, 13 | .zoom-leave-active { 14 | transform-origin: center center; 15 | transition: transform .15s cubic-bezier(0.0, 0.0, 0.2, 1); 16 | } 17 | 18 | .zoom-enter, 19 | .zoom-leave-to { 20 | transform: scale(0); 21 | } 22 | 23 | .right-pane-enter-active, 24 | .right-pane-leave-active, 25 | .left-pane-enter-active, 26 | .left-pane-leave-active 27 | > .background 28 | transition opacity .3s linear 29 | 30 | > .pane 31 | transition transform .8s cubic-bezier(0, 1, 0, 1) 32 | 33 | .right-pane-enter-active, 34 | .left-pane-enter-active 35 | > .pane > 36 | .header 37 | transition transform .6s cubic-bezier(0, 1, 0, 1) .1s 38 | 39 | .content 40 | transition transform .8s cubic-bezier(0, 1, 0, 1) .2s 41 | 42 | .footer 43 | transition transform .8s cubic-bezier(0, 1, 0, 1) .3s 44 | 45 | .right-pane-leave-active, 46 | .left-pane-leave-active 47 | pointer-events none 48 | 49 | .right-pane-enter, 50 | .right-pane-leave-to, 51 | .left-pane-enter, 52 | .left-pane-leave-to 53 | > .background 54 | opacity 0 55 | 56 | .right-pane-enter, 57 | .right-pane-leave-to 58 | > .pane 59 | transform translateX(100%) 60 | 61 | .left-pane-enter, 62 | .left-pane-leave-to 63 | > .pane 64 | transform translateX(-100%) 65 | 66 | .right-pane-enter 67 | > .pane > 68 | .header, 69 | .content, 70 | .footer 71 | transform translateX(100%) 72 | 73 | .left-pane-enter 74 | > .pane > 75 | .header, 76 | .content, 77 | .footer 78 | transform translateX(-100%) 79 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/styles/transitions.styl: -------------------------------------------------------------------------------- 1 | 2 | .fade-enter-active, 3 | .fade-leave-active { 4 | transition: opacity .15s linear; 5 | } 6 | 7 | .fade-enter, 8 | .fade-leave-to { 9 | opacity: 0; 10 | } 11 | 12 | .zoom-enter-active, 13 | .zoom-leave-active { 14 | transform-origin: center center; 15 | transition: transform .15s cubic-bezier(0.0, 0.0, 0.2, 1); 16 | } 17 | 18 | .zoom-enter, 19 | .zoom-leave-to { 20 | transform: scale(0); 21 | } 22 | 23 | .right-pane-enter-active, 24 | .right-pane-leave-active, 25 | .left-pane-enter-active, 26 | .left-pane-leave-active 27 | > .background 28 | transition opacity .3s linear 29 | 30 | > .pane 31 | transition transform .8s cubic-bezier(0, 1, 0, 1) 32 | 33 | .right-pane-enter-active, 34 | .left-pane-enter-active 35 | > .pane > 36 | .header 37 | transition transform .6s cubic-bezier(0, 1, 0, 1) .1s 38 | 39 | .content 40 | transition transform .8s cubic-bezier(0, 1, 0, 1) .2s 41 | 42 | .footer 43 | transition transform .8s cubic-bezier(0, 1, 0, 1) .3s 44 | 45 | .right-pane-leave-active, 46 | .left-pane-leave-active 47 | pointer-events none 48 | 49 | .right-pane-enter, 50 | .right-pane-leave-to, 51 | .left-pane-enter, 52 | .left-pane-leave-to 53 | > .background 54 | opacity 0 55 | 56 | .right-pane-enter, 57 | .right-pane-leave-to 58 | > .pane 59 | transform translateX(100%) 60 | 61 | .left-pane-enter, 62 | .left-pane-leave-to 63 | > .pane 64 | transform translateX(-100%) 65 | 66 | .right-pane-enter 67 | > .pane > 68 | .header, 69 | .content, 70 | .footer 71 | transform translateX(100%) 72 | 73 | .left-pane-enter 74 | > .pane > 75 | .header, 76 | .content, 77 | .footer 78 | transform translateX(-100%) 79 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "id": 1, 5 | "title": "Blue Socks", 6 | "price": 2.99, 7 | "originalPrice": 3.99, 8 | "rating": 4.3, 9 | "img": "http://lorempixel.com/400/400/abstract/1/" 10 | }, 11 | { 12 | "id": 2, 13 | "title": "Green Socks", 14 | "price": 3.99, 15 | "rating": 3.9, 16 | "img": "http://lorempixel.com/400/400/abstract/2/" 17 | }, 18 | { 19 | "id": 3, 20 | "title": "Red Socks", 21 | "price": 3.99, 22 | "rating": 4, 23 | "img": "http://lorempixel.com/400/400/abstract/3/" 24 | }, 25 | { 26 | "id": 4, 27 | "title": "Blue Shirt", 28 | "price": 16.99, 29 | "originalPrice": 19.99, 30 | "rating": 3.4, 31 | "img": "http://lorempixel.com/400/400/abstract/4/" 32 | }, 33 | { 34 | "id": 5, 35 | "title": "Green Shirt", 36 | "price": 19.99, 37 | "rating": 4.2, 38 | "img": "http://lorempixel.com/400/400/abstract/5/" 39 | }, 40 | { 41 | "id": 6, 42 | "title": "Red Shirt", 43 | "price": 19.99, 44 | "rating": 4.1, 45 | "img": "http://lorempixel.com/400/400/abstract/6/" 46 | }, 47 | { 48 | "id": 7, 49 | "title": "Grey Shirt", 50 | "price": 6.99, 51 | "originalPrice": 19.99, 52 | "rating": 2.6, 53 | "img": "http://lorempixel.com/400/400/abstract/7/" 54 | }, 55 | { 56 | "id": 8, 57 | "title": "Black Backpack", 58 | "price": 39.99, 59 | "rating": 4.7, 60 | "img": "http://lorempixel.com/400/400/abstract/8/" 61 | } 62 | ], 63 | "comments": [ 64 | { 65 | "id": 1, 66 | "text": "This is awesome!", 67 | "itemId": 1 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "id": 1, 5 | "title": "Blue Socks", 6 | "price": 2.99, 7 | "originalPrice": 3.99, 8 | "rating": 4.3, 9 | "img": "http://lorempixel.com/400/400/abstract/1/" 10 | }, 11 | { 12 | "id": 2, 13 | "title": "Green Socks", 14 | "price": 3.99, 15 | "rating": 3.9, 16 | "img": "http://lorempixel.com/400/400/abstract/2/" 17 | }, 18 | { 19 | "id": 3, 20 | "title": "Red Socks", 21 | "price": 3.99, 22 | "rating": 4, 23 | "img": "http://lorempixel.com/400/400/abstract/3/" 24 | }, 25 | { 26 | "id": 4, 27 | "title": "Blue Shirt", 28 | "price": 16.99, 29 | "originalPrice": 19.99, 30 | "rating": 3.4, 31 | "img": "http://lorempixel.com/400/400/abstract/4/" 32 | }, 33 | { 34 | "id": 5, 35 | "title": "Green Shirt", 36 | "price": 19.99, 37 | "rating": 4.2, 38 | "img": "http://lorempixel.com/400/400/abstract/5/" 39 | }, 40 | { 41 | "id": 6, 42 | "title": "Red Shirt", 43 | "price": 19.99, 44 | "rating": 4.1, 45 | "img": "http://lorempixel.com/400/400/abstract/6/" 46 | }, 47 | { 48 | "id": 7, 49 | "title": "Grey Shirt", 50 | "price": 6.99, 51 | "originalPrice": 19.99, 52 | "rating": 2.6, 53 | "img": "http://lorempixel.com/400/400/abstract/7/" 54 | }, 55 | { 56 | "id": 8, 57 | "title": "Black Backpack", 58 | "price": 39.99, 59 | "rating": 4.7, 60 | "img": "http://lorempixel.com/400/400/abstract/8/" 61 | } 62 | ], 63 | "comments": [ 64 | { 65 | "id": 1, 66 | "text": "This is awesome!", 67 | "itemId": 1 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /Chapter03/chapter3-full/transitions.css: -------------------------------------------------------------------------------- 1 | .fade-enter-active, 2 | .fade-leave-active { 3 | transition: opacity .5s; 4 | } 5 | 6 | .fade-enter, 7 | .fade-leave-to { 8 | opacity: 0; 9 | } 10 | 11 | .zoom-enter-active, 12 | .zoom-leave-active { 13 | transition: opacity .3s, transform .3s; 14 | } 15 | 16 | .zoom-enter, 17 | .zoom-leave-to { 18 | opacity: 0; 19 | transform: scale(.7); 20 | } 21 | 22 | /* Hand */ 23 | 24 | .hand-enter-active, 25 | .hand-leave-active { 26 | transition: opacity .5s; 27 | } 28 | 29 | .hand-enter, 30 | .hand-leave-to { 31 | opacity: 0; 32 | } 33 | 34 | .hand-enter-active .wrapper, 35 | .hand-leave-active .wrapper { 36 | transition: transform .8s cubic-bezier(.08,.74,.34,1); 37 | transform-origin: bottom center; 38 | } 39 | 40 | .hand-enter .wrapper, 41 | .hand-leave-to .wrapper { 42 | transform: rotateX(90deg); 43 | } 44 | 45 | .hand-enter-active .card, 46 | .hand-leave-active .card { 47 | transition: margin .8s cubic-bezier(.08,.74,.34,1); 48 | } 49 | 50 | .hand-enter .card, 51 | .hand-leave-to .card { 52 | margin: 0 -100px; 53 | } 54 | 55 | /* Card */ 56 | 57 | .card { 58 | /* Used for enter, move and mouse over animations */ 59 | transition: all .3s; 60 | } 61 | 62 | .card-enter { 63 | opacity: 0; 64 | /* Slide from the right */ 65 | transform: scale(.8) translateX(100px); 66 | } 67 | 68 | .card-leave-active { 69 | /* We need different timings for the leave transition */ 70 | transition: all 1s, opacity .5s .5s; 71 | /* Keep it in the same horizontal position */ 72 | position: absolute !important; 73 | /* Make it painted over the other cards */ 74 | z-index: 10; 75 | /* Unclickable during the transition */ 76 | pointer-events: none; 77 | } 78 | 79 | .card-leave-to { 80 | opacity: 0; 81 | /* Zoom the card upwards */ 82 | transform: translateX(-106px) translateY(-300px) scale(1.5); 83 | } 84 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/components/StoreCart.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 67 | 68 | 84 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/components/StoreCart.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 67 | 68 | 84 | -------------------------------------------------------------------------------- /Chapter03/chapter3-full/svg/food-bubble.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 34 | 39 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Chapter03/chapter3-download/svg/food-bubble.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 34 | 39 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/components/PageLocale.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 66 | 67 | 81 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/components/PageLocale.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 66 | 67 | 81 | -------------------------------------------------------------------------------- /Chapter07/chapter7-full/src/components/StoreItemInfos.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 39 | 40 | 83 | -------------------------------------------------------------------------------- /Chapter07/chapter7-download/src/components/StoreItemInfos.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 39 | 40 | 83 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/demo/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: './src/main.js', 6 | output: { 7 | path: path.resolve(__dirname, './dist'), 8 | publicPath: '/dist/', 9 | filename: 'build.js' 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.css$/, 15 | use: [ 16 | 'vue-style-loader', 17 | 'css-loader' 18 | ], 19 | }, { 20 | test: /\.vue$/, 21 | loader: 'vue-loader', 22 | options: { 23 | loaders: { 24 | } 25 | // other vue-loader options go here 26 | } 27 | }, 28 | { 29 | test: /\.js$/, 30 | loader: 'babel-loader', 31 | exclude: /node_modules/ 32 | }, 33 | { 34 | test: /\.(png|jpg|gif|svg)$/, 35 | loader: 'file-loader', 36 | options: { 37 | name: '[name].[ext]?[hash]' 38 | } 39 | } 40 | ] 41 | }, 42 | resolve: { 43 | alias: { 44 | 'vue$': 'vue/dist/vue.esm.js' 45 | }, 46 | extensions: ['*', '.js', '.vue', '.json'] 47 | }, 48 | devServer: { 49 | historyApiFallback: true, 50 | noInfo: true, 51 | overlay: true 52 | }, 53 | performance: { 54 | hints: false 55 | }, 56 | devtool: '#eval-source-map' 57 | } 58 | 59 | if (process.env.NODE_ENV === 'production') { 60 | module.exports.devtool = '#source-map' 61 | // http://vue-loader.vuejs.org/en/workflow/production.html 62 | module.exports.plugins = (module.exports.plugins || []).concat([ 63 | new webpack.DefinePlugin({ 64 | 'process.env': { 65 | NODE_ENV: '"production"' 66 | } 67 | }), 68 | new webpack.optimize.UglifyJsPlugin({ 69 | sourceMap: true, 70 | compress: { 71 | warnings: false 72 | } 73 | }), 74 | new webpack.LoaderOptionsPlugin({ 75 | minimize: true 76 | }) 77 | ]) 78 | } 79 | -------------------------------------------------------------------------------- /Chapter04/chapter4-full/movies/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: './src/main.js', 6 | output: { 7 | path: path.resolve(__dirname, './dist'), 8 | publicPath: '/dist/', 9 | filename: 'build.js' 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.css$/, 15 | use: [ 16 | 'vue-style-loader', 17 | 'css-loader' 18 | ], 19 | }, { 20 | test: /\.vue$/, 21 | loader: 'vue-loader', 22 | options: { 23 | loaders: { 24 | } 25 | // other vue-loader options go here 26 | } 27 | }, 28 | { 29 | test: /\.js$/, 30 | loader: 'babel-loader', 31 | exclude: /node_modules/ 32 | }, 33 | { 34 | test: /\.(png|jpg|gif|svg)$/, 35 | loader: 'file-loader', 36 | options: { 37 | name: '[name].[ext]?[hash]' 38 | } 39 | } 40 | ] 41 | }, 42 | resolve: { 43 | alias: { 44 | 'vue$': 'vue/dist/vue.esm.js' 45 | }, 46 | extensions: ['*', '.js', '.vue', '.json'] 47 | }, 48 | devServer: { 49 | historyApiFallback: true, 50 | noInfo: true, 51 | overlay: true 52 | }, 53 | performance: { 54 | hints: false 55 | }, 56 | devtool: '#eval-source-map' 57 | } 58 | 59 | if (process.env.NODE_ENV === 'production') { 60 | module.exports.devtool = '#source-map' 61 | // http://vue-loader.vuejs.org/en/workflow/production.html 62 | module.exports.plugins = (module.exports.plugins || []).concat([ 63 | new webpack.DefinePlugin({ 64 | 'process.env': { 65 | NODE_ENV: '"production"' 66 | } 67 | }), 68 | new webpack.optimize.UglifyJsPlugin({ 69 | sourceMap: true, 70 | compress: { 71 | warnings: false 72 | } 73 | }), 74 | new webpack.LoaderOptionsPlugin({ 75 | minimize: true 76 | }) 77 | ]) 78 | } 79 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: './src/main.js', 6 | output: { 7 | path: path.resolve(__dirname, './dist'), 8 | publicPath: '/dist/', 9 | filename: 'build.js' 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.css$/, 15 | use: [ 16 | 'vue-style-loader', 17 | 'css-loader' 18 | ], 19 | }, { 20 | test: /\.vue$/, 21 | loader: 'vue-loader', 22 | options: { 23 | loaders: { 24 | } 25 | // other vue-loader options go here 26 | } 27 | }, 28 | { 29 | test: /\.js$/, 30 | loader: 'babel-loader', 31 | exclude: /node_modules/ 32 | }, 33 | { 34 | test: /\.(png|jpg|gif|svg)$/, 35 | loader: 'file-loader', 36 | options: { 37 | name: '[name].[ext]?[hash]' 38 | } 39 | } 40 | ] 41 | }, 42 | resolve: { 43 | alias: { 44 | 'vue$': 'vue/dist/vue.esm.js' 45 | }, 46 | extensions: ['*', '.js', '.vue', '.json'] 47 | }, 48 | devServer: { 49 | historyApiFallback: true, 50 | noInfo: true, 51 | overlay: true 52 | }, 53 | performance: { 54 | hints: false 55 | }, 56 | devtool: '#eval-source-map' 57 | } 58 | 59 | if (process.env.NODE_ENV === 'production') { 60 | module.exports.devtool = '#source-map' 61 | // http://vue-loader.vuejs.org/en/workflow/production.html 62 | module.exports.plugins = (module.exports.plugins || []).concat([ 63 | new webpack.DefinePlugin({ 64 | 'process.env': { 65 | NODE_ENV: '"production"' 66 | } 67 | }), 68 | new webpack.optimize.UglifyJsPlugin({ 69 | sourceMap: true, 70 | compress: { 71 | warnings: false 72 | } 73 | }), 74 | new webpack.LoaderOptionsPlugin({ 75 | minimize: true 76 | }) 77 | ]) 78 | } 79 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: './src/main.js', 6 | output: { 7 | path: path.resolve(__dirname, './dist'), 8 | publicPath: '/dist/', 9 | filename: 'build.js' 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.css$/, 15 | use: [ 16 | 'vue-style-loader', 17 | 'css-loader' 18 | ], 19 | }, { 20 | test: /\.vue$/, 21 | loader: 'vue-loader', 22 | options: { 23 | loaders: { 24 | } 25 | // other vue-loader options go here 26 | } 27 | }, 28 | { 29 | test: /\.js$/, 30 | loader: 'babel-loader', 31 | exclude: /node_modules/ 32 | }, 33 | { 34 | test: /\.(png|jpg|gif|svg)$/, 35 | loader: 'file-loader', 36 | options: { 37 | name: '[name].[ext]?[hash]' 38 | } 39 | } 40 | ] 41 | }, 42 | resolve: { 43 | alias: { 44 | 'vue$': 'vue/dist/vue.esm.js' 45 | }, 46 | extensions: ['*', '.js', '.vue', '.json'] 47 | }, 48 | devServer: { 49 | historyApiFallback: true, 50 | noInfo: true, 51 | overlay: true 52 | }, 53 | performance: { 54 | hints: false 55 | }, 56 | devtool: '#eval-source-map' 57 | } 58 | 59 | if (process.env.NODE_ENV === 'production') { 60 | module.exports.devtool = '#source-map' 61 | // http://vue-loader.vuejs.org/en/workflow/production.html 62 | module.exports.plugins = (module.exports.plugins || []).concat([ 63 | new webpack.DefinePlugin({ 64 | 'process.env': { 65 | NODE_ENV: '"production"' 66 | } 67 | }), 68 | new webpack.optimize.UglifyJsPlugin({ 69 | sourceMap: true, 70 | compress: { 71 | warnings: false 72 | } 73 | }), 74 | new webpack.LoaderOptionsPlugin({ 75 | minimize: true 76 | }) 77 | ]) 78 | } 79 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/server/src/index.js: -------------------------------------------------------------------------------- 1 | import cookieParser from 'cookie-parser' 2 | import bodyParser from 'body-parser' 3 | import express from 'express' 4 | import passport from 'passport' 5 | import session from 'express-session' 6 | import cors from 'cors' 7 | import uuid from 'uuid/v4' 8 | import http from 'http' 9 | import SocketIo from 'socket.io' 10 | import socket from './socket' 11 | import NedbStore from 'nedb-session-store' 12 | import path from 'path' 13 | import PassportSocketIo from 'passport.socketio' 14 | 15 | import './auth' 16 | 17 | import routes from './routes' 18 | 19 | import { CLIENT_ORIGIN, SECRET, PORT, DB_PATH } from './config' 20 | 21 | const corsOptions = { 22 | origin: CLIENT_ORIGIN, 23 | credentials: true, 24 | } 25 | 26 | const NedbSessionStore = NedbStore(session) 27 | const sessionStore = new NedbSessionStore({ 28 | filename: path.join(DB_PATH, 'session-store.db') 29 | }) 30 | 31 | const app = express() 32 | const server = http.Server(app) 33 | 34 | app.use(cors(corsOptions)) 35 | 36 | app.use(cookieParser(SECRET)) 37 | 38 | app.use(bodyParser.urlencoded({ extended: true })) 39 | app.use(bodyParser.json()) 40 | 41 | app.use(session({ 42 | genid: () => uuid(), 43 | key: 'express.sid', 44 | secret: SECRET, 45 | resave: true, 46 | saveUninitialized: true, 47 | cookie: { 48 | maxAge: 3 * 60 * 60 * 1000, 49 | secure: process.env.NODE_ENV === 'production', 50 | }, 51 | store: sessionStore, 52 | })) 53 | 54 | app.use(passport.initialize()) 55 | app.use(passport.session()) 56 | 57 | 58 | routes(app) 59 | 60 | server.listen(PORT, () => { 61 | console.log(`Server listening on port ${PORT}`) 62 | }) 63 | 64 | const io = SocketIo(server) 65 | 66 | io.use(PassportSocketIo.authorize({ 67 | cookieParser, 68 | key: 'express.sid', 69 | secret: SECRET, 70 | store: sessionStore, 71 | success: (data, accept) => { 72 | console.log('socket.io auth success') 73 | accept() 74 | }, 75 | })) 76 | 77 | socket(io) 78 | -------------------------------------------------------------------------------- /Chapter05/chapter5-full/client/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import state from './state' 4 | 5 | import Home from './components/Home.vue' 6 | import FAQ from './components/FAQ.vue' 7 | import Login from './components/Login.vue' 8 | import TicketsLayout from './components/TicketsLayout.vue' 9 | import Tickets from './components/Tickets.vue' 10 | import NewTicket from './components/NewTicket.vue' 11 | import Ticket from './components/Ticket.vue' 12 | import NotFound from './components/NotFound.vue' 13 | 14 | Vue.use(VueRouter) 15 | 16 | const routes = [ 17 | { path: '/', name: 'home', component: Home }, 18 | { path: '/faq', name: 'faq', component: FAQ }, 19 | { path: '/login', name: 'login', component: Login, meta: { guest: true } }, 20 | { path: '/tickets', component: TicketsLayout, meta: { private: true }, children: [ 21 | { path: '', name: 'tickets', component: Tickets }, 22 | { path: 'new', name: 'new-ticket', component: NewTicket }, 23 | { path: ':id', name: 'ticket', component: Ticket, props: true }, 24 | ] }, 25 | { path: '*', component: NotFound }, 26 | ] 27 | 28 | const router = new VueRouter({ 29 | routes, 30 | mode: 'history', 31 | scrollBehavior (to, from, savedPosition) { 32 | if (savedPosition) { 33 | return savedPosition 34 | } 35 | if (to.hash) { 36 | return { selector: to.hash } 37 | } 38 | return { x: 0, y: 0 } 39 | }, 40 | }) 41 | 42 | router.beforeEach((to, from, next) => { 43 | console.log('to', to.name) 44 | // if (to.meta.private && !state.user) { 45 | if (to.matched.some(r => r.meta.private) && !state.user) { 46 | next({ 47 | name: 'login', 48 | params: { 49 | wantedRoute: to.fullPath, 50 | }, 51 | }) 52 | return 53 | } 54 | // if (to.meta.guest && state.user) { 55 | if (to.matched.some(r => r.meta.guest) && state.user) { 56 | next({ name: 'home' }) 57 | return 58 | } 59 | next() 60 | }) 61 | 62 | export default router 63 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import { $fetch } from '../plugins/fetch' 4 | import router from '../router' 5 | 6 | import maps from './maps' 7 | import posts from './posts' 8 | 9 | Vue.use(Vuex) 10 | 11 | const store = new Vuex.Store({ 12 | strict: process.env.NODE_ENV !== 'production', 13 | 14 | state () { 15 | return { 16 | user: null, 17 | } 18 | }, 19 | 20 | getters: { 21 | user: state => state.user, 22 | 23 | userPicture: (state, getters) => { 24 | const user = getters.user 25 | if (user) { 26 | const photos = user.profile.photos 27 | if (photos.length !== 0) { 28 | return photos[0].value 29 | } 30 | } 31 | }, 32 | }, 33 | 34 | mutations: { 35 | user: (state, user) => { 36 | state.user = user 37 | }, 38 | }, 39 | 40 | actions: { 41 | async init ({ dispatch }) { 42 | await dispatch('login') 43 | }, 44 | 45 | async login ({ commit, dispatch }) { 46 | try { 47 | const user = await $fetch('user') 48 | console.log('user', user) 49 | commit('user', user) 50 | 51 | if (user) { 52 | // Redirect to the wanted route or home 53 | router.replace(router.currentRoute.params.wantedRoute || { name: 'home' }) 54 | 55 | dispatch('logged-in') 56 | } 57 | } catch (e) { 58 | console.warn(e) 59 | } 60 | }, 61 | 62 | logout ({ commit, dispatch }) { 63 | commit('user', null) 64 | 65 | $fetch('logout') 66 | 67 | // If the route is private 68 | // We go to the login screen 69 | if (router.currentRoute.matched.some(r => r.meta.private)) { 70 | router.replace({ name: 'login', params: { 71 | wantedRoute: router.currentRoute.fullPath, 72 | }}) 73 | } 74 | }, 75 | }, 76 | 77 | modules: { 78 | maps, 79 | posts, 80 | }, 81 | }) 82 | 83 | export default store 84 | -------------------------------------------------------------------------------- /Chapter06/chapter6-full/client/src/components/content/CreatePost.vue: -------------------------------------------------------------------------------- 1 |