├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .stylintrc ├── LICENSE ├── README.md ├── build ├── css-utils.js ├── env-utils.js ├── hot-reload.js ├── script.build.js ├── script.clean.js ├── script.dev.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── config ├── dev.env.js ├── index.js └── prod.env.js ├── docker-compose.yml ├── dockerfile ├── package.json ├── src ├── App.vue ├── api │ └── messages.js ├── assets │ └── quasar-logo.png ├── components │ ├── Error404.vue │ ├── Home.vue │ ├── Index.vue │ ├── Message.vue │ ├── Messages.vue │ └── styles │ │ └── main.scss ├── index.html ├── main.js ├── router.js ├── statics │ └── favicon.ico ├── store │ ├── actions.js │ ├── getters.js │ ├── index.js │ ├── modules │ │ ├── index.js │ │ ├── main.js │ │ └── messages.js │ ├── mutation-types.js │ └── mutations.js └── themes │ ├── app.ios.styl │ ├── app.mat.styl │ └── app.variables.styl ├── templates ├── component.vue ├── layout.vue └── view.vue └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["es2015", {"modules": false}], "stage-2"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | dist/*.js 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | sourceType: 'module' 5 | }, 6 | env: { 7 | browser: true 8 | }, 9 | globals: { 10 | 'cordova': true, 11 | 'DEV': true, 12 | 'PROD': true, 13 | '__THEME': true 14 | }, 15 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 16 | extends: 'standard', 17 | // required to lint *.vue files 18 | plugins: [ 19 | 'html' 20 | ], 21 | // add your custom rules here 22 | 'rules': { 23 | // allow paren-less arrow functions 24 | 'arrow-parens': 0, 25 | 'one-var': 0, 26 | // allow debugger during development 27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 28 | 'brace-style': [2, 'stroustrup', { 'allowSingleLine': true }] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | npm-debug.log.* 6 | selenium-debug.log 7 | test/unit/coverage 8 | test/e2e/reports 9 | cordova/platforms 10 | cordova/plugins 11 | thumbs.db 12 | !.gitkeep 13 | -------------------------------------------------------------------------------- /.stylintrc: -------------------------------------------------------------------------------- 1 | { 2 | "blocks": "never", 3 | "brackets": "never", 4 | "colons": "never", 5 | "colors": "always", 6 | "commaSpace": "always", 7 | "commentSpace": "always", 8 | "cssLiteral": "never", 9 | "depthLimit": false, 10 | "duplicates": true, 11 | "efficient": "always", 12 | "extendPref": false, 13 | "globalDupe": true, 14 | "indentPref": 2, 15 | "leadingZero": "never", 16 | "maxErrors": false, 17 | "maxWarnings": false, 18 | "mixed": false, 19 | "namingConvention": false, 20 | "namingConventionStrict": false, 21 | "none": "never", 22 | "noImportant": false, 23 | "parenSpace": "never", 24 | "placeholder": false, 25 | "prefixVarsWithDollar": "always", 26 | "quotePref": "single", 27 | "semicolons": "never", 28 | "sortOrder": false, 29 | "stackedProperties": "never", 30 | "trailingWhitespace": "never", 31 | "universal": "never", 32 | "valid": true, 33 | "zeroUnits": "never", 34 | "zIndexNormalize": false 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Nicolas Giethlen 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # quasu 2 | Starter app with Vue JS, Quasar framework, Vuex and plenty of cool stuff 3 | 4 | ## Features 5 | 6 | * Vue JS 2 with: 7 | * Vue Router 8 | * Vuex state management 9 | * State persistance with vuex-persistedstate 10 | * Axios for XHR 11 | * Offline detection 12 | * Quasar Framework and UI kit (iOS and Android themes included) 13 | * Webpack 2 14 | * Electron JS and Cordova wrappers 15 | * Sass and Stylus styles 16 | 17 | ## Installation 18 | 19 | You'll need to have `quasar-cli` installed globally: 20 | 21 | ``` 22 | sudo npm install -g quasar-cli 23 | ``` 24 | 25 | Clone this repo: 26 | 27 | ``` 28 | git clone https://github.com/Chmood/quasu.git 29 | ``` 30 | 31 | Enter the project and install dependencies: 32 | 33 | ``` 34 | cd quasu && yarn install 35 | ``` 36 | 37 | ## Developpement server 38 | 39 | Launch the magic: 40 | 41 | ``` 42 | quasar dev [theme] 43 | ``` 44 | 45 | _Theme can be `mat` (default) or `ios`_ 46 | -------------------------------------------------------------------------------- /build/css-utils.js: -------------------------------------------------------------------------------- 1 | var 2 | ExtractTextPlugin = require('extract-text-webpack-plugin'), 3 | autoprefixer = require('autoprefixer') 4 | 5 | module.exports.postcss = [autoprefixer()] 6 | 7 | module.exports.styleLoaders = function (options) { 8 | options = options || {} 9 | 10 | function generateLoaders (loaders) { 11 | if (options.postcss) { 12 | loaders.splice(1, 0, 'postcss') 13 | } 14 | 15 | var sourceLoader = loaders.map(function (loader) { 16 | var extraParamChar 17 | if (/\?/.test(loader)) { 18 | loader = loader.replace(/\?/, '-loader?') 19 | extraParamChar = '&' 20 | } 21 | else { 22 | loader = loader + '-loader' 23 | extraParamChar = '?' 24 | } 25 | return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '') 26 | }).join('!') 27 | 28 | if (options.extract) { 29 | return ExtractTextPlugin.extract({ 30 | use: sourceLoader, 31 | fallback: 'vue-style-loader' 32 | }) 33 | } 34 | else { 35 | return ['vue-style-loader', sourceLoader].join('!') 36 | } 37 | } 38 | 39 | return { 40 | css: generateLoaders(['css']), 41 | less: generateLoaders(['css', 'less']), 42 | sass: generateLoaders(['css', 'sass?indentedSyntax']), 43 | scss: generateLoaders(['css', 'sass']), 44 | styl: generateLoaders(['css', 'stylus']), 45 | stylus: generateLoaders(['css', 'stylus']) 46 | } 47 | } 48 | 49 | module.exports.styleRules = function (options) { 50 | var output = [] 51 | var loaders = exports.styleLoaders(options) 52 | for (var extension in loaders) { 53 | var loader = loaders[extension] 54 | output.push({ 55 | test: new RegExp('\\.' + extension + '$'), 56 | loader: loader 57 | }) 58 | } 59 | return output 60 | } 61 | -------------------------------------------------------------------------------- /build/env-utils.js: -------------------------------------------------------------------------------- 1 | var 2 | config = require('../config'), 3 | theme = process.argv[2] || config.defaultTheme 4 | 5 | module.exports = { 6 | dev: process.env.NODE_ENV === 'development', 7 | prod: process.env.NODE_ENV === 'production', 8 | 9 | platform: { 10 | theme: theme, 11 | cordovaAssets: './cordova/platforms/' + (theme === 'mat' ? 'android' : 'ios') + '/platform_www' 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /build/hot-reload.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /build/script.build.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'production' 2 | 3 | require('colors') 4 | 5 | var 6 | shell = require('shelljs'), 7 | path = require('path'), 8 | env = require('./env-utils'), 9 | webpack = require('webpack'), 10 | webpackConfig = require('./webpack.prod.conf'), 11 | targetPath = path.join(__dirname, '../dist') 12 | 13 | console.log(' WARNING!'.bold) 14 | console.log(' Do NOT use VueRouter\'s "history" mode if') 15 | console.log(' building for Cordova or Electron.\n') 16 | 17 | require('./script.clean.js') 18 | console.log((' Building Quasar App with "' + env.platform.theme + '" theme...\n').bold) 19 | 20 | shell.mkdir('-p', targetPath) 21 | shell.cp('-R', 'src/statics', targetPath) 22 | 23 | webpack(webpackConfig, function (err, stats) { 24 | if (err) throw err 25 | process.stdout.write(stats.toString({ 26 | colors: true, 27 | modules: false, 28 | children: false, 29 | chunks: false, 30 | chunkModules: false 31 | }) + '\n') 32 | 33 | console.log(( 34 | '\n Build complete with "' + env.platform.theme.bold + '" theme in ' + 35 | '"/dist"'.bold + ' folder.\n').cyan) 36 | 37 | console.log(' Built files are meant to be served over an HTTP server.'.bold) 38 | console.log(' Opening index.html over file:// won\'t work.'.bold) 39 | }) 40 | -------------------------------------------------------------------------------- /build/script.clean.js: -------------------------------------------------------------------------------- 1 | var 2 | shell = require('shelljs'), 3 | path = require('path') 4 | 5 | shell.rm('-rf', path.resolve(__dirname, '../dist')) 6 | console.log(' Cleaned build artifacts.\n') 7 | -------------------------------------------------------------------------------- /build/script.dev.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'development' 2 | 3 | require('colors') 4 | 5 | var 6 | path = require('path'), 7 | express = require('express'), 8 | webpack = require('webpack'), 9 | env = require('./env-utils'), 10 | config = require('../config'), 11 | opn = require('opn'), 12 | proxyMiddleware = require('http-proxy-middleware'), 13 | webpackConfig = require('./webpack.dev.conf'), 14 | app = express(), 15 | port = process.env.PORT || config.dev.port, 16 | uri = 'http://localhost:' + port 17 | 18 | console.log(' Starting dev server with "' + (process.argv[2] || env.platform.theme).bold + '" theme...') 19 | console.log(' Will listen at ' + uri.bold) 20 | if (config.dev.openBrowser) { 21 | console.log(' Browser will open when build is ready.\n') 22 | } 23 | 24 | var compiler = webpack(webpackConfig) 25 | 26 | // Define HTTP proxies to your custom API backend 27 | // https://github.com/chimurai/http-proxy-middleware 28 | var proxyTable = config.dev.proxyTable 29 | 30 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 31 | publicPath: webpackConfig.output.publicPath, 32 | quiet: true 33 | }) 34 | 35 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 36 | log: function () {} 37 | }) 38 | 39 | // force page reload when html-webpack-plugin template changes 40 | compiler.plugin('compilation', function (compilation) { 41 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 42 | hotMiddleware.publish({ action: 'reload' }) 43 | cb() 44 | }) 45 | }) 46 | 47 | // proxy requests like API. See /config/index.js -> dev.proxyTable 48 | // https://github.com/chimurai/http-proxy-middleware 49 | Object.keys(proxyTable).forEach(function (context) { 50 | var options = proxyTable[context] 51 | if (typeof options === 'string') { 52 | options = { target: options } 53 | } 54 | app.use(proxyMiddleware(context, options)) 55 | }) 56 | 57 | // handle fallback for HTML5 history API 58 | app.use(require('connect-history-api-fallback')()) 59 | 60 | // serve webpack bundle output 61 | app.use(devMiddleware) 62 | 63 | // enable hot-reload and state-preserving 64 | // compilation error display 65 | app.use(hotMiddleware) 66 | 67 | // serve pure static assets 68 | var staticsPath = path.posix.join(webpackConfig.output.publicPath, 'statics/') 69 | app.use(staticsPath, express.static('./src/statics')) 70 | 71 | // try to serve Cordova statics for Play App 72 | app.use(express.static(env.platform.cordovaAssets)) 73 | 74 | module.exports = app.listen(port, function (err) { 75 | if (err) { 76 | console.log(err) 77 | return 78 | } 79 | 80 | // open browser if set so in /config/index.js 81 | if (config.dev.openBrowser) { 82 | devMiddleware.waitUntilValid(function () { 83 | opn(uri) 84 | }) 85 | } 86 | }) 87 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var 2 | path = require('path'), 3 | webpack = require('webpack'), 4 | config = require('../config'), 5 | cssUtils = require('./css-utils'), 6 | env = require('./env-utils'), 7 | merge = require('webpack-merge'), 8 | projectRoot = path.resolve(__dirname, '../'), 9 | ProgressBarPlugin = require('progress-bar-webpack-plugin'), 10 | useCssSourceMap = 11 | (env.dev && config.dev.cssSourceMap) || 12 | (env.prod && config.build.productionSourceMap) 13 | 14 | function resolve (dir) { 15 | return path.join(__dirname, '..', dir) 16 | } 17 | 18 | module.exports = { 19 | entry: { 20 | app: './src/main.js' 21 | }, 22 | output: { 23 | path: path.resolve(__dirname, '../dist'), 24 | publicPath: config[env.prod ? 'build' : 'dev'].publicPath, 25 | filename: 'js/[name].js', 26 | chunkFilename: 'js/[id].[chunkhash].js' 27 | }, 28 | resolve: { 29 | extensions: ['.js', '.vue', '.json'], 30 | modules: [ 31 | resolve('src'), 32 | resolve('node_modules') 33 | ], 34 | alias: config.aliases 35 | }, 36 | module: { 37 | rules: [ 38 | { // eslint 39 | enforce: 'pre', 40 | test: /\.(vue|js)$/, 41 | loader: 'eslint-loader', 42 | include: projectRoot, 43 | exclude: /node_modules/, 44 | options: { 45 | formatter: require('eslint-friendly-formatter') 46 | } 47 | }, 48 | { 49 | test: /\.js$/, 50 | loader: 'babel-loader', 51 | include: projectRoot, 52 | exclude: /node_modules/ 53 | }, 54 | { 55 | test: /\.vue$/, 56 | loader: 'vue-loader', 57 | options: { 58 | postcss: cssUtils.postcss, 59 | loaders: merge({js: 'babel-loader'}, cssUtils.styleLoaders({ 60 | sourceMap: useCssSourceMap, 61 | extract: env.prod 62 | })) 63 | } 64 | }, 65 | { 66 | test: /\.json$/, 67 | loader: 'json-loader' 68 | }, 69 | { 70 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 71 | loader: 'url-loader', 72 | options: { 73 | limit: 10000, 74 | name: 'img/[name].[hash:7].[ext]' 75 | } 76 | }, 77 | { 78 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 79 | loader: 'url-loader', 80 | options: { 81 | limit: 10000, 82 | name: 'fonts/[name].[hash:7].[ext]' 83 | } 84 | } 85 | ] 86 | }, 87 | plugins: [ 88 | /* 89 | Take note! 90 | Uncomment if you wish to load only one Moment locale: 91 | 92 | new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/), 93 | */ 94 | 95 | new webpack.DefinePlugin({ 96 | 'process.env': config[env.prod ? 'build' : 'dev'].env, 97 | 'DEV': env.dev, 98 | 'PROD': env.prod, 99 | '__THEME': '"' + env.platform.theme + '"' 100 | }), 101 | new webpack.LoaderOptionsPlugin({ 102 | minimize: env.prod, 103 | options: { 104 | context: path.resolve(__dirname, '../src'), 105 | postcss: cssUtils.postcss 106 | } 107 | }), 108 | new ProgressBarPlugin({ 109 | format: config.progressFormat 110 | }) 111 | ], 112 | performance: { 113 | hints: false 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var 2 | config = require('../config'), 3 | webpack = require('webpack'), 4 | merge = require('webpack-merge'), 5 | cssUtils = require('./css-utils'), 6 | baseWebpackConfig = require('./webpack.base.conf'), 7 | HtmlWebpackPlugin = require('html-webpack-plugin'), 8 | FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 9 | 10 | // add hot-reload related code to entry chunks 11 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 12 | baseWebpackConfig.entry[name] = ['./build/hot-reload'].concat(baseWebpackConfig.entry[name]) 13 | }) 14 | 15 | module.exports = merge(baseWebpackConfig, { 16 | // eval-source-map is faster for development 17 | devtool: '#cheap-module-eval-source-map', 18 | devServer: { 19 | historyApiFallback: true, 20 | noInfo: true 21 | }, 22 | module: { 23 | rules: cssUtils.styleRules({ 24 | sourceMap: config.dev.cssSourceMap, 25 | postcss: true 26 | }) 27 | }, 28 | plugins: [ 29 | new webpack.HotModuleReplacementPlugin(), 30 | new webpack.NoEmitOnErrorsPlugin(), 31 | new HtmlWebpackPlugin({ 32 | filename: 'index.html', 33 | template: 'src/index.html', 34 | inject: true 35 | }), 36 | new FriendlyErrorsPlugin({ 37 | clearConsole: config.dev.clearConsoleOnRebuild 38 | }) 39 | ], 40 | performance: { 41 | hints: false 42 | } 43 | }) 44 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var 2 | path = require('path'), 3 | config = require('../config'), 4 | cssUtils = require('./css-utils'), 5 | webpack = require('webpack'), 6 | merge = require('webpack-merge'), 7 | baseWebpackConfig = require('./webpack.base.conf'), 8 | ExtractTextPlugin = require('extract-text-webpack-plugin'), 9 | HtmlWebpackPlugin = require('html-webpack-plugin') 10 | 11 | var webpackConfig = merge(baseWebpackConfig, { 12 | module: { 13 | rules: cssUtils.styleRules({ 14 | sourceMap: config.build.productionSourceMap, 15 | extract: true, 16 | postcss: true 17 | }) 18 | }, 19 | devtool: config.build.productionSourceMap ? '#source-map' : false, 20 | plugins: [ 21 | new webpack.optimize.UglifyJsPlugin({ 22 | sourceMap: config.build.productionSourceMap, 23 | minimize: true, 24 | compress: { 25 | warnings: false 26 | } 27 | }), 28 | // extract css into its own file 29 | new ExtractTextPlugin({ 30 | filename: '[name].[contenthash].css' 31 | }), 32 | new HtmlWebpackPlugin({ 33 | filename: config.build.index, 34 | template: 'src/index.html', 35 | inject: true, 36 | minify: { 37 | removeComments: true, 38 | collapseWhitespace: true, 39 | removeAttributeQuotes: true 40 | // more options: 41 | // https://github.com/kangax/html-minifier#options-quick-reference 42 | }, 43 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 44 | chunksSortMode: 'dependency' 45 | }), 46 | // split vendor js into its own file 47 | new webpack.optimize.CommonsChunkPlugin({ 48 | name: 'vendor', 49 | minChunks: function (module, count) { 50 | // any required modules inside node_modules are extracted to vendor 51 | return ( 52 | module.resource && 53 | /\.js$/.test(module.resource) && 54 | module.resource.indexOf( 55 | path.join(__dirname, '../node_modules') 56 | ) === 0 57 | ) 58 | } 59 | }), 60 | // extract webpack runtime and module manifest to its own file in order to 61 | // prevent vendor hash from being updated whenever app bundle is updated 62 | new webpack.optimize.CommonsChunkPlugin({ 63 | name: 'manifest', 64 | chunks: ['vendor'] 65 | }) 66 | ] 67 | }) 68 | 69 | if (config.build.productionGzip) { 70 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 71 | 72 | webpackConfig.plugins.push( 73 | new CompressionWebpackPlugin({ 74 | asset: '[path].gz[query]', 75 | algorithm: 'gzip', 76 | test: new RegExp( 77 | '\\.(' + 78 | config.build.productionGzipExtensions.join('|') + 79 | ')$' 80 | ), 81 | threshold: 10240, 82 | minRatio: 0.8 83 | }) 84 | ) 85 | } 86 | 87 | module.exports = webpackConfig 88 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | module.exports = { 4 | // Webpack aliases 5 | aliases: { 6 | quasar: path.resolve(__dirname, '../node_modules/quasar-framework/'), 7 | src: path.resolve(__dirname, '../src'), 8 | assets: path.resolve(__dirname, '../src/assets'), 9 | components: path.resolve(__dirname, '../src/components') 10 | }, 11 | 12 | // Progress Bar Webpack plugin format 13 | // https://github.com/clessg/progress-bar-webpack-plugin#options 14 | progressFormat: ' [:bar] ' + ':percent'.bold + ' (:msg)', 15 | 16 | // Default theme to build with ('ios' or 'mat') 17 | defaultTheme: 'mat', 18 | 19 | build: { 20 | env: require('./prod.env'), 21 | index: path.resolve(__dirname, '../dist/index.html'), 22 | publicPath: '/', 23 | productionSourceMap: false, 24 | // Gzip off by default as many popular static hosts such as 25 | // Surge or Netlify already gzip all static assets for you. 26 | // Before setting to `true`, make sure to: 27 | // npm install --save-dev compression-webpack-plugin 28 | productionGzip: false, 29 | productionGzipExtensions: ['js', 'css'] 30 | }, 31 | dev: { 32 | env: require('./dev.env'), 33 | cssSourceMap: true, 34 | // auto open browser or not 35 | openBrowser: true, 36 | publicPath: '/', 37 | port: 8080, 38 | 39 | // If for example you are using Quasar Play 40 | // to generate a QR code then on each dev (re)compilation 41 | // you need to avoid clearing out the console, so set this 42 | // to "false", otherwise you can set it to "true" to always 43 | // have only the messages regarding your last (re)compilation. 44 | clearConsoleOnRebuild: false, 45 | 46 | // Proxy your API if using any. 47 | // Also see /build/script.dev.js and search for "proxy api requests" 48 | // https://github.com/chimurai/http-proxy-middleware 49 | proxyTable: {} 50 | } 51 | } 52 | 53 | /* 54 | * proxyTable example: 55 | * 56 | proxyTable: { 57 | // proxy all requests starting with /api 58 | '/api': { 59 | target: 'https://some.address.com/api', 60 | changeOrigin: true, 61 | pathRewrite: { 62 | '^/api': '' 63 | } 64 | } 65 | } 66 | */ 67 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | app: 5 | build: . 6 | image: your-docker-org/your-app-name 7 | command: sh 8 | volumes: 9 | - .:/opt/app 10 | - /opt/app/node_modules 11 | ports: 12 | - 8080:8080 13 | tty: true 14 | -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | #change this to your own repo, should you have uploaded your image! 2 | FROM quasarframework/client-dev:latest 3 | 4 | MAINTAINER Your Name 5 | 6 | WORKDIR /opt/app 7 | 8 | COPY package.json /opt/app/ 9 | RUN npm install 10 | 11 | COPY . /opt/app 12 | 13 | EXPOSE 8080 14 | 15 | CMD /bin/sh 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quasar-app", 3 | "version": "0.0.1", 4 | "description": "A Quasar App", 5 | "author": "Your Name", 6 | "scripts": { 7 | "clean": "node build/script.clean.js", 8 | "dev": "node build/script.dev.js", 9 | "build": "node build/script.build.js", 10 | "lint": "eslint --ext .js,.vue src" 11 | }, 12 | "dependencies": { 13 | "axios": "^0.16.0", 14 | "babel-runtime": "^6.0.0", 15 | "fastclick": "^1.0.6", 16 | "material-design-icons": "^3.0.1", 17 | "moment": "^2.15.0", 18 | "quasar-framework": "^0.13.4", 19 | "roboto-fontface": "^0.7.0", 20 | "vue": "^2.2.2", 21 | "vue-online": "^1.1.9", 22 | "vue-router": "^2.0.0", 23 | "vuex": "^2.2.1", 24 | "vuex-persistedstate": "^1.3.0" 25 | }, 26 | "devDependencies": { 27 | "autoprefixer": "^6.4.0", 28 | "babel-core": "^6.0.0", 29 | "babel-eslint": "^7.0.0", 30 | "babel-loader": "^6.0.0", 31 | "babel-plugin-transform-runtime": "^6.0.0", 32 | "babel-preset-es2015": "^6.0.0", 33 | "babel-preset-stage-2": "^6.0.0", 34 | "colors": "^1.1.2", 35 | "connect-history-api-fallback": "^1.1.0", 36 | "css-loader": "^0.26.0", 37 | "eslint": "^3.0.1", 38 | "eslint-config-standard": "^6.2.0", 39 | "eslint-friendly-formatter": "^2.0.5", 40 | "eslint-loader": "^1.3.0", 41 | "eslint-plugin-html": "^2.0.1", 42 | "eslint-plugin-promise": "^3.3.0", 43 | "eslint-plugin-standard": "^2.0.0", 44 | "eventsource-polyfill": "^0.9.6", 45 | "express": "^4.13.3", 46 | "extract-text-webpack-plugin": "^2.0.0-beta.4", 47 | "file-loader": "^0.10.0", 48 | "friendly-errors-webpack-plugin": "^1.1.3", 49 | "html-webpack-plugin": "^2.8.1", 50 | "http-proxy-middleware": "^0.17.0", 51 | "json-loader": "^0.5.4", 52 | "node-sass": "^4.5.2", 53 | "opn": "^4.0.2", 54 | "postcss-loader": "^1.0.0", 55 | "progress-bar-webpack-plugin": "^1.9.0", 56 | "sass-loader": "^6.0.3", 57 | "shelljs": "^0.7.0", 58 | "stylus": "^0.54.5", 59 | "stylus-loader": "^2.1.1", 60 | "url-loader": "^0.5.7", 61 | "vue-loader": "11.1.4", 62 | "vue-style-loader": "^2.0.0", 63 | "vue-template-compiler": "^2.2.2", 64 | "webpack": "^2.2.1", 65 | "webpack-dev-middleware": "^1.8.4", 66 | "webpack-hot-middleware": "^2.17.0", 67 | "webpack-merge": "^4.0.0" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/api/messages.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const rootURL = 'https://jsonplaceholder.typicode.com' 4 | 5 | /** 6 | * Mocking client-server processing 7 | */ 8 | // const _messages = [ 9 | // { 10 | // id: 1, 11 | // date: new Date(), 12 | // author: 'John Doe', 13 | // authorAvatar: 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/00/009ca8ac288a2b9a056df6ba035185e18a8287d5.jpg', 14 | // title: 'Title of the message #1', 15 | // content: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam!', 16 | // read: false 17 | // }, 18 | // { 19 | // id: 2, 20 | // date: new Date(), 21 | // author: 'Mary Jane', 22 | // authorAvatar: 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/00/009cb35003ceae993714d14ef254d747952318b6.jpg', 23 | // title: 'Here comes the very long title of the message #2 which should span on multiple lines', 24 | // content: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam!', 25 | // read: false 26 | // }, 27 | // { 28 | // id: 5, 29 | // date: new Date(), 30 | // author: 'Lilly Pop', 31 | // authorAvatar: 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/00/009cdfa72175f6c734f50690b49377b6484358b1.jpg', 32 | // title: 'Title of the message #3', 33 | // content: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam!', 34 | // read: true 35 | // }, 36 | // { 37 | // id: 3, 38 | // date: new Date(), 39 | // author: 'Eliz Williams', 40 | // authorAvatar: 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/00/009ca8ac288a2b9a056df6ba035185e18a8287d5.jpg', 41 | // title: 'Title of the message #4', 42 | // content: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam!', 43 | // read: true 44 | // }, 45 | // { 46 | // id: 4, 47 | // date: new Date(), 48 | // author: 'Una Kravets', 49 | // authorAvatar: 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/00/009cb35003ceae993714d14ef254d747952318b6.jpg', 50 | // title: 'Here comes the very long title of the message #5 which should span on multiple lines', 51 | // content: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam!', 52 | // read: true 53 | // } 54 | // ] 55 | 56 | export default { 57 | getMessages (cb) { 58 | // Make a request for a user with a given ID 59 | axios.get(rootURL + '/comments') 60 | .then(function (response) { 61 | // cb(data) 62 | const data = response.data 63 | const messages = [] 64 | 65 | for (let d of data) { 66 | if (d.id < 50) { 67 | const m = {} 68 | 69 | m.id = d.id 70 | m.date = new Date() 71 | m.author = d.email 72 | // m.authorAvatar = d. 73 | m.title = d.name 74 | m.content = d.body 75 | m.read = Math.random() > 0.5 76 | 77 | messages.push(m) 78 | } 79 | } 80 | 81 | cb(messages) 82 | }) 83 | .catch(function (error) { 84 | console.error(error) 85 | }) 86 | 87 | // setTimeout(() => cb(_messages), 3000) 88 | }, 89 | 90 | postMessage (message, cb, errorCb) { 91 | setTimeout(() => { 92 | // simulate random checkout failure. 93 | (Math.random() > 0.5 || navigator.userAgent.indexOf('PhantomJS') > -1) 94 | ? cb() 95 | : errorCb() 96 | }, 1000) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/assets/quasar-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chmood/quasu/b53274b5b60363b67f56536ce1cd10a3c521f0d5/src/assets/quasar-logo.png -------------------------------------------------------------------------------- /src/components/Error404.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 41 | 42 | 59 | -------------------------------------------------------------------------------- /src/components/Home.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 47 | -------------------------------------------------------------------------------- /src/components/Index.vue: -------------------------------------------------------------------------------- 1 | 117 | 118 | 220 | 221 | 232 | -------------------------------------------------------------------------------- /src/components/Message.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 124 | 125 | 196 | -------------------------------------------------------------------------------- /src/components/Messages.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 136 | 137 | 200 | -------------------------------------------------------------------------------- /src/components/styles/main.scss: -------------------------------------------------------------------------------- 1 | // VARIABLES 2 | 3 | $gutter: 16px; 4 | 5 | // COMPUTED 6 | 7 | $gutter-half: $gutter / 2; 8 | $gutter-double: $gutter * 2; 9 | $gutter-quarter: $gutter / 4; 10 | $gutter-quad: $gutter * 4; 11 | $gutter-triple: $gutter * 3; 12 | $gutter-and-half: $gutter * 1.5; 13 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Quasar App 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // === DEFAULT / CUSTOM STYLE === 2 | // WARNING! always comment out ONE of the two require() calls below. 3 | // 1. use next line to activate CUSTOM STYLE (./src/themes) 4 | require(`./themes/app.${__THEME}.styl`) 5 | // 2. or, use next line to activate DEFAULT QUASAR STYLE 6 | // require(`quasar/dist/quasar.${__THEME}.css`) 7 | // ============================== 8 | 9 | import Vue from 'vue' 10 | import Quasar from 'quasar' 11 | import router from './router' 12 | 13 | import store from './store/index' 14 | 15 | Vue.use(Quasar) // Install Quasar Framework 16 | 17 | Quasar.start(() => { 18 | /* eslint-disable no-new */ 19 | new Vue({ 20 | el: '#q-app', 21 | store, // inject store to all children 22 | router, // inject router to all children 23 | render: h => h(require('./App')) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | 4 | Vue.use(VueRouter) 5 | 6 | function load (component) { 7 | return () => System.import(`components/${component}.vue`) 8 | } 9 | 10 | export default new VueRouter({ 11 | /* 12 | * NOTE! VueRouter "history" mode DOESN'T works for Cordova builds, 13 | * it is only to be used only for websites. 14 | * 15 | * If you decide to go with "history" mode, please also open /config/index.js 16 | * and set "build.publicPath" to something other than an empty string. 17 | * Example: '/' instead of current '' 18 | * 19 | * If switching back to default "hash" mode, don't forget to set the 20 | * build publicPath back to '' so Cordova builds work again. 21 | */ 22 | mode: 'history', // No more # in the URL 23 | 24 | routes: [ 25 | { 26 | path: '/', // Default route 27 | component: load('Index'), 28 | children: [ // Sub-routes 29 | { 30 | path: '', // default subroute 31 | component: load('Home') 32 | }, 33 | { 34 | path: 'messages', 35 | component: load('Messages') 36 | } 37 | ] 38 | }, 39 | { path: '/foo', component: load('Foo') }, // example simple route 40 | { path: '*', component: load('Error404') } // Not found 41 | ] 42 | }) 43 | -------------------------------------------------------------------------------- /src/statics/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chmood/quasu/b53274b5b60363b67f56536ce1cd10a3c521f0d5/src/statics/favicon.ico -------------------------------------------------------------------------------- /src/store/actions.js: -------------------------------------------------------------------------------- 1 | // GLOBAL ACTIONS 2 | 3 | import * as types from './mutation-types' 4 | 5 | export const globalFetchMessages = ({ commit }) => { 6 | commit(types.FETCH_MESSAGES) 7 | } 8 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | // GLOBAL GETTERS 2 | 3 | // export const messages = state => state.messages.messages 4 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import * as mutations from './mutations' 5 | import * as actions from './actions' 6 | import * as getters from './getters' 7 | import modules from './modules' 8 | 9 | import createPersistedState from 'vuex-persistedstate' 10 | 11 | Vue.use(Vuex) 12 | 13 | export default new Vuex.Store({ 14 | actions, 15 | mutations, 16 | getters, 17 | modules, 18 | plugins: [createPersistedState( 19 | // key : The key to store the persisted state under. (default: vuex) 20 | // paths : An array of any paths to partially persist the state. If no paths are given, the complete state is persisted. (default: []) 21 | // reducer : A function that will be called to reduce the state to persist based on the given paths. Defaults to include the values. 22 | // subscriber : A function called to setup mutation subscription. Defaults to store => handler => store.subscribe(handler) 23 | // 24 | // storage : Instead for (or in combination with) getState and setState. Defaults to localStorage (or internal storage for Server Side Rendering). 25 | // 26 | // getState : A function that will be called to rehydrate a previously persisted state. Defaults to using storage. 27 | // setState : A function that will be called to persist the given state. Defaults to using storage. 28 | )], 29 | strict: process.env.NODE_ENV !== 'production' 30 | }) 31 | -------------------------------------------------------------------------------- /src/store/modules/index.js: -------------------------------------------------------------------------------- 1 | const files = require.context('.', false, /\.js$/) 2 | const modules = {} 3 | 4 | files.keys().forEach((key) => { 5 | if (key === './index.js') return 6 | modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default 7 | }) 8 | 9 | export default modules 10 | -------------------------------------------------------------------------------- /src/store/modules/main.js: -------------------------------------------------------------------------------- 1 | import * as types from '../mutation-types' 2 | 3 | // Initial state 4 | const state = { 5 | isOnline: true 6 | } 7 | 8 | // Mutations 9 | const mutations = { 10 | [types.SET_IS_ONLINE] (state, { online }) { state.isOnline = online } 11 | } 12 | 13 | // Getters 14 | const getters = { 15 | isOnline: state => state.isOnline 16 | } 17 | 18 | // Actions 19 | const actions = {} 20 | 21 | export default { 22 | state, 23 | getters, 24 | actions, 25 | mutations 26 | } 27 | -------------------------------------------------------------------------------- /src/store/modules/messages.js: -------------------------------------------------------------------------------- 1 | import APIMessages from '../../api/messages' 2 | import * as types from '../mutation-types' 3 | 4 | // Initial state 5 | const state = { 6 | messages: [] 7 | } 8 | 9 | // Mutations 10 | const mutations = { 11 | [types.DELETE_MESSAGE] (state, { message }) { 12 | state.messages.splice(state.messages.indexOf(message), 1) 13 | }, 14 | [types.TOGGLE_MESSAGE] (state, { message }) { 15 | message.read = !message.read 16 | }, 17 | [types.TOGGLE_ALL_MESSAGES] (state, { read }) { 18 | state.messages.forEach((message) => { 19 | message.read = read 20 | }) 21 | }, 22 | [types.FETCH_MESSAGES] (state, { messages }) { 23 | state.messages = messages 24 | } 25 | } 26 | 27 | // Getters 28 | const getters = { 29 | messages: state => state.messages 30 | } 31 | 32 | // Actions 33 | const actions = { 34 | fetchMessages ({ commit }) { 35 | APIMessages.getMessages(messages => { 36 | commit(types.FETCH_MESSAGES, { messages }) 37 | }) 38 | } 39 | } 40 | 41 | export default { 42 | state, 43 | getters, 44 | actions, 45 | mutations 46 | } 47 | -------------------------------------------------------------------------------- /src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | // MUTATIONS TYPES 2 | 3 | // Main 4 | 5 | export const SET_IS_ONLINE = 'SET_IS_ONLINE' 6 | 7 | // Messages 8 | 9 | export const FETCH_MESSAGES = 'FETCH_MESSAGES' 10 | export const DELETE_MESSAGE = 'DELETE_MESSAGE' 11 | export const TOGGLE_MESSAGE = 'TOGGLE_MESSAGE' 12 | export const TOGGLE_ALL_MESSAGES = 'TOGGLE_ALL_MESSAGES' 13 | -------------------------------------------------------------------------------- /src/store/mutations.js: -------------------------------------------------------------------------------- 1 | // GLOBAL MUTATIONS 2 | -------------------------------------------------------------------------------- /src/themes/app.ios.styl: -------------------------------------------------------------------------------- 1 | // This file is included in the build if src/main.js imports it. 2 | // Otherwise the default iOS CSS file is bundled. 3 | // Check "DEFAULT / CUSTOM STYLE" in src/main.js 4 | 5 | // App Shared Variables 6 | // -------------------------------------------------- 7 | // Shared Stylus variables go in the app.variables.styl file 8 | @import 'app.variables' 9 | 10 | // App iOS Design Variables 11 | // -------------------------------------------------- 12 | // iOS Design only Stylus variables can go here 13 | // http://quasar-framework.org/api/css-stylus-variables.html 14 | 15 | $typography-font-family ?= '-apple-system', 'Helvetica Neue', Helvetica, Arial, sans-serif 16 | 17 | $toolbar-color ?= #424242 18 | $toolbar-background ?= $light 19 | $toolbar-active-color ?= $light 20 | $toolbar-faded-color ?= composite-color($toolbar-color) 21 | 22 | // Quasar iOS Design Stylus 23 | // -------------------------------------------------- 24 | // Custom App variables must be declared before importing Quasar. 25 | // Quasar will use its default values when a custom variable isn't provided. 26 | @import '~quasar-framework/dist/quasar.ios.styl' 27 | -------------------------------------------------------------------------------- /src/themes/app.mat.styl: -------------------------------------------------------------------------------- 1 | // This file is included in the build if src/main.js imports it. 2 | // Otherwise the default Material CSS file is bundled. 3 | // Check "DEFAULT / CUSTOM STYLE" in src/main.js 4 | 5 | // App Shared Variables 6 | // -------------------------------------------------- 7 | // Shared Stylus variables go in the app.variables.styl file 8 | @import 'app.variables' 9 | 10 | // App Material Design Variables 11 | // -------------------------------------------------- 12 | // Material Design only Stylus variables can go here 13 | // http://quasar-framework.org/api/css-stylus-variables.html 14 | 15 | $typography-font-family ?= 'Roboto' 16 | 17 | $toolbar-color ?= white 18 | $toolbar-background ?= $primary 19 | $toolbar-active-color ?= $primary 20 | $toolbar-faded-color ?= composite-color($primary) 21 | 22 | // Quasar Material Design Stylus 23 | // -------------------------------------------------- 24 | // Custom App variables must be declared before importing Quasar. 25 | // Quasar will use its default values when a custom variable isn't provided. 26 | @import '~quasar-framework/dist/quasar.mat.styl' 27 | -------------------------------------------------------------------------------- /src/themes/app.variables.styl: -------------------------------------------------------------------------------- 1 | // This file is included in the build if src/main.js imports 2 | // either app.mat.styl or app.ios.styl. 3 | // Check "DEFAULT / CUSTOM STYLE" in src/main.js 4 | 5 | // App Shared Variables 6 | // -------------------------------------------------- 7 | // To customize the look and feel of this app, you can override 8 | // the Stylus variables found in Quasar's source Stylus files. Setting 9 | // variables before Quasar's Stylus will use these variables rather than 10 | // Quasar's default Stylus variable values. Stylus variables specific 11 | // to the themes belong in either the app.ios.styl or app.mat.styl files. 12 | 13 | 14 | // App Shared Color Variables 15 | // -------------------------------------------------- 16 | // It's highly recommended to change the default colors 17 | // to match your app's branding. 18 | 19 | $primary = #027be3 20 | $secondary = #26A69A 21 | $tertiary = #555 22 | 23 | $neutral = #E0E1E2 24 | $positive = #21BA45 25 | $negative = #DB2828 26 | $info = #31CCEC 27 | $warning = #F2C037 28 | 29 | $light = #f4f4f4 30 | $dark = #333 31 | $faded = #777 32 | 33 | $text-color = lighten(black, 17%) 34 | $background-color = white 35 | 36 | $link-color = lighten($primary, 25%) 37 | $link-color-active = $primary 38 | -------------------------------------------------------------------------------- /templates/component.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 15 | -------------------------------------------------------------------------------- /templates/layout.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 53 | 54 | 56 | -------------------------------------------------------------------------------- /templates/view.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | 19 | 21 | --------------------------------------------------------------------------------